From cd07912073c951b4bbb871ed2653af1be2cfc714 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:55:11 +0200 Subject: Adding upstream version 2.1.30. Signed-off-by: Daniel Baumann --- src/common.c | 770 +++ src/common.h | 626 ++ src/config.h.in | 74 + src/context.c | 1277 ++++ src/context.h | 668 ++ src/dict.h | 122 + src/diff.c | 2151 +++++++ src/diff.h | 62 + src/hash_table.c | 880 +++ src/hash_table.h | 279 + src/in.c | 329 + src/in.h | 252 + src/in_internal.h | 50 + src/json.c | 1047 ++++ src/json.h | 139 + src/libyang.h | 167 + src/log.c | 773 +++ src/log.h | 404 ++ src/lyb.c | 125 + src/lyb.h | 199 + src/out.c | 768 +++ src/out.h | 307 + src/out_internal.h | 110 + src/parser_common.c | 3567 +++++++++++ src/parser_data.h | 461 ++ src/parser_internal.h | 392 ++ src/parser_json.c | 1819 ++++++ src/parser_lyb.c | 1792 ++++++ src/parser_schema.h | 179 + src/parser_xml.c | 1816 ++++++ src/parser_yang.c | 4827 +++++++++++++++ src/parser_yin.c | 4012 ++++++++++++ src/path.c | 1193 ++++ src/path.h | 263 + src/plugins.c | 550 ++ src/plugins.h | 94 + src/plugins_exts.c | 680 ++ src/plugins_exts.h | 1048 ++++ src/plugins_exts/metadata.c | 243 + src/plugins_exts/metadata.h | 66 + src/plugins_exts/nacm.c | 223 + src/plugins_exts/schema_mount.c | 1332 ++++ src/plugins_exts/structure.c | 558 ++ src/plugins_exts/yangdata.c | 277 + src/plugins_internal.h | 85 + src/plugins_types.c | 1043 ++++ src/plugins_types.h | 1214 ++++ src/plugins_types/binary.c | 466 ++ src/plugins_types/bits.c | 510 ++ src/plugins_types/boolean.c | 165 + src/plugins_types/date_and_time.c | 339 + src/plugins_types/decimal64.c | 239 + src/plugins_types/empty.c | 103 + src/plugins_types/enumeration.c | 202 + src/plugins_types/identityref.c | 352 ++ src/plugins_types/instanceid.c | 382 ++ src/plugins_types/instanceid_keys.c | 229 + src/plugins_types/integer.c | 585 ++ src/plugins_types/ipv4_address.c | 377 ++ src/plugins_types/ipv4_address_no_zone.c | 221 + src/plugins_types/ipv4_prefix.c | 337 + src/plugins_types/ipv6_address.c | 378 ++ src/plugins_types/ipv6_address_no_zone.c | 312 + src/plugins_types/ipv6_prefix.c | 351 ++ src/plugins_types/leafref.c | 140 + src/plugins_types/node_instanceid.c | 320 + src/plugins_types/string.c | 109 + src/plugins_types/union.c | 585 ++ src/plugins_types/xpath1.0.c | 521 ++ src/printer_data.c | 159 + src/printer_data.h | 196 + src/printer_internal.h | 218 + src/printer_json.c | 1010 +++ src/printer_lyb.c | 1335 ++++ src/printer_schema.c | 222 + src/printer_schema.h | 232 + src/printer_tree.c | 4673 ++++++++++++++ src/printer_xml.c | 607 ++ src/printer_yang.c | 2657 ++++++++ src/printer_yin.c | 1501 +++++ src/schema_compile.c | 1798 ++++++ src/schema_compile.h | 397 ++ src/schema_compile_amend.c | 2547 ++++++++ src/schema_compile_amend.h | 181 + src/schema_compile_node.c | 4218 +++++++++++++ src/schema_compile_node.h | 202 + src/schema_features.c | 714 +++ src/schema_features.h | 67 + src/set.c | 247 + src/set.h | 181 + src/tree.h | 250 + src/tree_data.c | 2943 +++++++++ src/tree_data.h | 2601 ++++++++ src/tree_data_common.c | 1626 +++++ src/tree_data_free.c | 241 + src/tree_data_hash.c | 237 + src/tree_data_internal.h | 590 ++ src/tree_data_new.c | 1914 ++++++ src/tree_edit.h | 306 + src/tree_schema.c | 2178 +++++++ src/tree_schema.h | 2248 +++++++ src/tree_schema_common.c | 2617 ++++++++ src/tree_schema_free.c | 1737 ++++++ src/tree_schema_free.h | 221 + src/tree_schema_internal.h | 735 +++ src/validation.c | 2029 ++++++ src/validation.h | 107 + src/version.h.in | 23 + src/xml.c | 1402 +++++ src/xml.h | 209 + src/xpath.c | 9883 ++++++++++++++++++++++++++++++ src/xpath.h | 517 ++ 112 files changed, 104512 insertions(+) create mode 100644 src/common.c create mode 100644 src/common.h create mode 100644 src/config.h.in create mode 100644 src/context.c create mode 100644 src/context.h create mode 100644 src/dict.h create mode 100644 src/diff.c create mode 100644 src/diff.h create mode 100644 src/hash_table.c create mode 100644 src/hash_table.h create mode 100644 src/in.c create mode 100644 src/in.h create mode 100644 src/in_internal.h create mode 100644 src/json.c create mode 100644 src/json.h create mode 100644 src/libyang.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/lyb.c create mode 100644 src/lyb.h create mode 100644 src/out.c create mode 100644 src/out.h create mode 100644 src/out_internal.h create mode 100644 src/parser_common.c create mode 100644 src/parser_data.h create mode 100644 src/parser_internal.h create mode 100644 src/parser_json.c create mode 100644 src/parser_lyb.c create mode 100644 src/parser_schema.h create mode 100644 src/parser_xml.c create mode 100644 src/parser_yang.c create mode 100644 src/parser_yin.c create mode 100644 src/path.c create mode 100644 src/path.h create mode 100644 src/plugins.c create mode 100644 src/plugins.h create mode 100644 src/plugins_exts.c create mode 100644 src/plugins_exts.h create mode 100644 src/plugins_exts/metadata.c create mode 100644 src/plugins_exts/metadata.h create mode 100644 src/plugins_exts/nacm.c create mode 100644 src/plugins_exts/schema_mount.c create mode 100644 src/plugins_exts/structure.c create mode 100644 src/plugins_exts/yangdata.c create mode 100644 src/plugins_internal.h create mode 100644 src/plugins_types.c create mode 100644 src/plugins_types.h create mode 100644 src/plugins_types/binary.c create mode 100644 src/plugins_types/bits.c create mode 100644 src/plugins_types/boolean.c create mode 100644 src/plugins_types/date_and_time.c create mode 100644 src/plugins_types/decimal64.c create mode 100644 src/plugins_types/empty.c create mode 100644 src/plugins_types/enumeration.c create mode 100644 src/plugins_types/identityref.c create mode 100644 src/plugins_types/instanceid.c create mode 100644 src/plugins_types/instanceid_keys.c create mode 100644 src/plugins_types/integer.c create mode 100644 src/plugins_types/ipv4_address.c create mode 100644 src/plugins_types/ipv4_address_no_zone.c create mode 100644 src/plugins_types/ipv4_prefix.c create mode 100644 src/plugins_types/ipv6_address.c create mode 100644 src/plugins_types/ipv6_address_no_zone.c create mode 100644 src/plugins_types/ipv6_prefix.c create mode 100644 src/plugins_types/leafref.c create mode 100644 src/plugins_types/node_instanceid.c create mode 100644 src/plugins_types/string.c create mode 100644 src/plugins_types/union.c create mode 100644 src/plugins_types/xpath1.0.c create mode 100644 src/printer_data.c create mode 100644 src/printer_data.h create mode 100644 src/printer_internal.h create mode 100644 src/printer_json.c create mode 100644 src/printer_lyb.c create mode 100644 src/printer_schema.c create mode 100644 src/printer_schema.h create mode 100644 src/printer_tree.c create mode 100644 src/printer_xml.c create mode 100644 src/printer_yang.c create mode 100644 src/printer_yin.c create mode 100644 src/schema_compile.c create mode 100644 src/schema_compile.h create mode 100644 src/schema_compile_amend.c create mode 100644 src/schema_compile_amend.h create mode 100644 src/schema_compile_node.c create mode 100644 src/schema_compile_node.h create mode 100644 src/schema_features.c create mode 100644 src/schema_features.h create mode 100644 src/set.c create mode 100644 src/set.h create mode 100644 src/tree.h create mode 100644 src/tree_data.c create mode 100644 src/tree_data.h create mode 100644 src/tree_data_common.c create mode 100644 src/tree_data_free.c create mode 100644 src/tree_data_hash.c create mode 100644 src/tree_data_internal.h create mode 100644 src/tree_data_new.c create mode 100644 src/tree_edit.h create mode 100644 src/tree_schema.c create mode 100644 src/tree_schema.h create mode 100644 src/tree_schema_common.c create mode 100644 src/tree_schema_free.c create mode 100644 src/tree_schema_free.h create mode 100644 src/tree_schema_internal.h create mode 100644 src/validation.c create mode 100644 src/validation.h create mode 100644 src/version.h.in create mode 100644 src/xml.c create mode 100644 src/xml.h create mode 100644 src/xpath.c create mode 100644 src/xpath.h (limited to 'src') diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..38f51ea --- /dev/null +++ b/src/common.c @@ -0,0 +1,770 @@ +/** + * @file common.c + * @author Michal Vasko + * @brief common internal definitions for libyang + * + * Copyright (c) 2018 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 + +#include "common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif +#include +#include + +#include "compat.h" +#include "tree_schema_internal.h" +#include "xml.h" + +void * +ly_realloc(void *ptr, size_t size) +{ + void *new_mem; + + new_mem = realloc(ptr, size); + if (!new_mem) { + free(ptr); + } + + return new_mem; +} + +char * +ly_strnchr(const char *s, int c, size_t len) +{ + for ( ; len && (*s != (char)c); ++s, --len) {} + return len ? (char *)s : NULL; +} + +int +ly_strncmp(const char *refstr, const char *str, size_t str_len) +{ + int rc = strncmp(refstr, str, str_len); + + if (!rc && (refstr[str_len] == '\0')) { + return 0; + } else { + return rc ? rc : 1; + } +} + +LY_ERR +ly_strntou8(const char *nptr, size_t len, uint8_t *ret) +{ + uint8_t num = 0, dig, dec_pow; + + if (len > 3) { + /* overflow for sure */ + return LY_EDENIED; + } + + dec_pow = 1; + for ( ; len && isdigit(nptr[len - 1]); --len) { + dig = nptr[len - 1] - 48; + + if (LY_OVERFLOW_MUL(UINT8_MAX, dig, dec_pow)) { + return LY_EDENIED; + } + dig *= dec_pow; + + if (LY_OVERFLOW_ADD(UINT8_MAX, num, dig)) { + return LY_EDENIED; + } + num += dig; + + dec_pow *= 10; + } + + if (len) { + return LY_EVALID; + } + *ret = num; + return LY_SUCCESS; +} + +LY_ERR +ly_value_prefix_next(const char *str_begin, const char *str_end, uint32_t *len, ly_bool *is_prefix, const char **str_next) +{ + const char *stop, *prefix; + size_t bytes_read; + uint32_t c; + ly_bool prefix_found; + LY_ERR ret = LY_SUCCESS; + + assert(len && is_prefix && str_next); + +#define IS_AT_END(PTR, STR_END) (STR_END ? PTR == STR_END : !(*PTR)) + + *str_next = NULL; + *is_prefix = 0; + *len = 0; + + if (!str_begin || !(*str_begin) || (str_begin == str_end)) { + return ret; + } + + stop = str_begin; + prefix = NULL; + prefix_found = 0; + + do { + /* look for the beginning of the YANG value */ + do { + LY_CHECK_RET(ly_getutf8(&stop, &c, &bytes_read)); + } while (!is_xmlqnamestartchar(c) && !IS_AT_END(stop, str_end)); + + if (IS_AT_END(stop, str_end)) { + break; + } + + /* maybe the prefix was found */ + prefix = stop - bytes_read; + + /* look for the the end of the prefix */ + do { + LY_CHECK_RET(ly_getutf8(&stop, &c, &bytes_read)); + } while (is_xmlqnamechar(c) && !IS_AT_END(stop, str_end)); + + prefix_found = c == ':' ? 1 : 0; + + /* if it wasn't the prefix, keep looking */ + } while (!IS_AT_END(stop, str_end) && !prefix_found); + + if ((str_begin == prefix) && prefix_found) { + /* prefix found at the beginning of the input string */ + *is_prefix = 1; + *str_next = IS_AT_END(stop, str_end) ? NULL : stop; + *len = (stop - bytes_read) - str_begin; + } else if ((str_begin != prefix) && (prefix_found)) { + /* there is a some string before prefix */ + *str_next = prefix; + *len = prefix - str_begin; + } else { + /* no prefix found */ + *len = stop - str_begin; + } + +#undef IS_AT_END + + return ret; +} + +LY_ERR +ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read) +{ + uint32_t c, aux; + size_t len; + + if (bytes_read) { + (*bytes_read) = 0; + } + + c = (*input)[0]; + LY_CHECK_RET(!c, LY_EINVAL); + + if (!(c & 0x80)) { + /* one byte character */ + len = 1; + + if ((c < 0x20) && (c != 0x9) && (c != 0xa) && (c != 0xd)) { + return LY_EINVAL; + } + } else if ((c & 0xe0) == 0xc0) { + /* two bytes character */ + len = 2; + + aux = (*input)[1]; + if ((aux & 0xc0) != 0x80) { + return LY_EINVAL; + } + c = ((c & 0x1f) << 6) | (aux & 0x3f); + + if (c < 0x80) { + return LY_EINVAL; + } + } else if ((c & 0xf0) == 0xe0) { + /* three bytes character */ + len = 3; + + c &= 0x0f; + for (uint64_t i = 1; i <= 2; i++) { + aux = (*input)[i]; + if ((aux & 0xc0) != 0x80) { + return LY_EINVAL; + } + + c = (c << 6) | (aux & 0x3f); + } + + if ((c < 0x800) || ((c > 0xd7ff) && (c < 0xe000)) || (c > 0xfffd)) { + return LY_EINVAL; + } + } else if ((c & 0xf8) == 0xf0) { + /* four bytes character */ + len = 4; + + c &= 0x07; + for (uint64_t i = 1; i <= 3; i++) { + aux = (*input)[i]; + if ((aux & 0xc0) != 0x80) { + return LY_EINVAL; + } + + c = (c << 6) | (aux & 0x3f); + } + + if ((c < 0x1000) || (c > 0x10ffff)) { + return LY_EINVAL; + } + } else { + return LY_EINVAL; + } + + (*utf8_char) = c; + (*input) += len; + if (bytes_read) { + (*bytes_read) = len; + } + return LY_SUCCESS; +} + +LY_ERR +ly_pututf8(char *dst, uint32_t value, size_t *bytes_written) +{ + if (value < 0x80) { + /* one byte character */ + if ((value < 0x20) && + (value != 0x09) && + (value != 0x0a) && + (value != 0x0d)) { + return LY_EINVAL; + } + + dst[0] = value; + (*bytes_written) = 1; + } else if (value < 0x800) { + /* two bytes character */ + dst[0] = 0xc0 | (value >> 6); + dst[1] = 0x80 | (value & 0x3f); + (*bytes_written) = 2; + } else if (value < 0xfffe) { + /* three bytes character */ + if (((value & 0xf800) == 0xd800) || + ((value >= 0xfdd0) && (value <= 0xfdef))) { + /* exclude surrogate blocks %xD800-DFFF */ + /* exclude noncharacters %xFDD0-FDEF */ + return LY_EINVAL; + } + + dst[0] = 0xe0 | (value >> 12); + dst[1] = 0x80 | ((value >> 6) & 0x3f); + dst[2] = 0x80 | (value & 0x3f); + + (*bytes_written) = 3; + } else if (value < 0x10fffe) { + if ((value & 0xffe) == 0xffe) { + /* exclude noncharacters %xFFFE-FFFF, %x1FFFE-1FFFF, %x2FFFE-2FFFF, %x3FFFE-3FFFF, %x4FFFE-4FFFF, + * %x5FFFE-5FFFF, %x6FFFE-6FFFF, %x7FFFE-7FFFF, %x8FFFE-8FFFF, %x9FFFE-9FFFF, %xAFFFE-AFFFF, + * %xBFFFE-BFFFF, %xCFFFE-CFFFF, %xDFFFE-DFFFF, %xEFFFE-EFFFF, %xFFFFE-FFFFF, %x10FFFE-10FFFF */ + return LY_EINVAL; + } + /* four bytes character */ + dst[0] = 0xf0 | (value >> 18); + dst[1] = 0x80 | ((value >> 12) & 0x3f); + dst[2] = 0x80 | ((value >> 6) & 0x3f); + dst[3] = 0x80 | (value & 0x3f); + + (*bytes_written) = 4; + } else { + return LY_EINVAL; + } + return LY_SUCCESS; +} + +/** + * @brief Static table of the UTF8 characters lengths according to their first byte. + */ +static const unsigned char utf8_char_length_table[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1 +}; + +size_t +ly_utf8len(const char *str, size_t bytes) +{ + size_t len = 0; + const char *ptr = str; + + while (((size_t)(ptr - str) < bytes) && *ptr) { + ++len; + ptr += utf8_char_length_table[((unsigned char)(*ptr))]; + } + return len; +} + +size_t +LY_VCODE_INSTREXP_len(const char *str) +{ + size_t len = 0; + + if (!str) { + return len; + } else if (!str[0]) { + return 1; + } + for (len = 1; len < LY_VCODE_INSTREXP_MAXLEN && str[len]; ++len) {} + return len; +} + +#ifdef HAVE_MMAP +LY_ERR +ly_mmap(struct ly_ctx *ctx, int fd, size_t *length, void **addr) +{ + struct stat sb; + long pagesize; + size_t m; + + assert(length); + assert(addr); + assert(fd >= 0); + + if (fstat(fd, &sb) == -1) { + LOGERR(ctx, LY_ESYS, "Failed to stat the file descriptor (%s) for the mmap().", strerror(errno)); + return LY_ESYS; + } + if (!S_ISREG(sb.st_mode)) { + LOGERR(ctx, LY_EINVAL, "File to mmap() is not a regular file."); + return LY_ESYS; + } + if (!sb.st_size) { + *addr = NULL; + return LY_SUCCESS; + } + pagesize = sysconf(_SC_PAGESIZE); + + m = sb.st_size % pagesize; + if (m && (pagesize - m >= 1)) { + /* there will be enough space (at least 1 byte) after the file content mapping to provide zeroed NULL-termination byte */ + *length = sb.st_size + 1; + *addr = mmap(NULL, *length, PROT_READ, MAP_PRIVATE, fd, 0); + } else { + /* there will not be enough bytes after the file content mapping for the additional bytes and some of them + * would overflow into another page that would not be zerroed and any access into it would generate SIGBUS. + * Therefore we have to do the following hack with double mapping. First, the required number of bytes + * (including the additinal bytes) is required as anonymous and thus they will be really provided (actually more + * because of using whole pages) and also initialized by zeros. Then, the file is mapped to the same address + * where the anonymous mapping starts. */ + *length = sb.st_size + pagesize; + *addr = mmap(NULL, *length, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + *addr = mmap(*addr, sb.st_size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fd, 0); + } + if (*addr == MAP_FAILED) { + LOGERR(ctx, LY_ESYS, "mmap() failed (%s).", strerror(errno)); + return LY_ESYS; + } + + return LY_SUCCESS; +} + +LY_ERR +ly_munmap(void *addr, size_t length) +{ + if (munmap(addr, length)) { + return LY_ESYS; + } + return LY_SUCCESS; +} + +#else + +LY_ERR +ly_mmap(struct ly_ctx *ctx, int fd, size_t *length, void **addr) +{ + struct stat sb; + size_t m; + + assert(length); + assert(addr); + assert(fd >= 0); + +#if _WIN32 + if (_setmode(fd, _O_BINARY) == -1) { + LOGERR(ctx, LY_ESYS, "Failed to switch the file descriptor to binary mode.", strerror(errno)); + return LY_ESYS; + } +#endif + + if (fstat(fd, &sb) == -1) { + LOGERR(ctx, LY_ESYS, "Failed to stat the file descriptor (%s) for the mmap().", strerror(errno)); + return LY_ESYS; + } + if (!S_ISREG(sb.st_mode)) { + LOGERR(ctx, LY_EINVAL, "File to mmap() is not a regular file."); + return LY_ESYS; + } + if (!sb.st_size) { + *addr = NULL; + return LY_SUCCESS; + } + /* On Windows, the mman-win32 mmap() emulation uses CreateFileMapping and MapViewOfFile, and these functions + * do not allow mapping more than "length of file" bytes for PROT_READ. Remapping existing mappings is not allowed, either. + * At that point the path of least resistance is just reading the file in as-is. */ + m = sb.st_size + 1; + char *buf = calloc(m, 1); + + if (!buf) { + LOGERR(ctx, LY_ESYS, "ly_mmap: malloc() failed (%s).", strerror(errno)); + } + *addr = buf; + *length = m; + + lseek(fd, 0, SEEK_SET); + ssize_t to_read = m - 1; + + while (to_read > 0) { + ssize_t n = read(fd, buf, to_read); + + if (n == 0) { + return LY_SUCCESS; + } else if (n < 0) { + if (errno == EINTR) { + continue; // can I get this on Windows? + } + LOGERR(ctx, LY_ESYS, "ly_mmap: read() failed (%s).", strerror(errno)); + } + to_read -= n; + buf += n; + } + return LY_SUCCESS; +} + +LY_ERR +ly_munmap(void *addr, size_t length) +{ + (void)length; + free(addr); + return LY_SUCCESS; +} + +#endif + +LY_ERR +ly_strcat(char **dest, const char *format, ...) +{ + va_list fp; + char *addition = NULL; + size_t len; + + va_start(fp, format); + len = vasprintf(&addition, format, fp); + len += (*dest ? strlen(*dest) : 0) + 1; + + if (*dest) { + *dest = ly_realloc(*dest, len); + if (!*dest) { + va_end(fp); + return LY_EMEM; + } + *dest = strcat(*dest, addition); + free(addition); + } else { + *dest = addition; + } + + va_end(fp); + return LY_SUCCESS; +} + +LY_ERR +ly_parse_int(const char *val_str, size_t val_len, int64_t min, int64_t max, int base, int64_t *ret) +{ + LY_ERR rc = LY_SUCCESS; + char *ptr, *str; + int64_t i; + + LY_CHECK_ARG_RET(NULL, val_str, val_str[0], val_len, LY_EINVAL); + + /* duplicate the value */ + str = strndup(val_str, val_len); + LY_CHECK_RET(!str, LY_EMEM); + + /* parse the value to avoid accessing following bytes */ + errno = 0; + i = strtoll(str, &ptr, base); + if (errno || (ptr == str)) { + /* invalid string */ + rc = LY_EVALID; + } else if ((i < min) || (i > max)) { + /* invalid number */ + rc = LY_EDENIED; + } else if (*ptr) { + while (isspace(*ptr)) { + ++ptr; + } + if (*ptr) { + /* invalid characters after some number */ + rc = LY_EVALID; + } + } + + /* cleanup */ + free(str); + if (!rc) { + *ret = i; + } + return rc; +} + +LY_ERR +ly_parse_uint(const char *val_str, size_t val_len, uint64_t max, int base, uint64_t *ret) +{ + LY_ERR rc = LY_SUCCESS; + char *ptr, *str; + uint64_t u; + + LY_CHECK_ARG_RET(NULL, val_str, val_str[0], val_len, LY_EINVAL); + + /* duplicate the value to avoid accessing following bytes */ + str = strndup(val_str, val_len); + LY_CHECK_RET(!str, LY_EMEM); + + /* parse the value */ + errno = 0; + u = strtoull(str, &ptr, base); + if (errno || (ptr == str)) { + /* invalid string */ + rc = LY_EVALID; + } else if ((u > max) || (u && (str[0] == '-'))) { + /* invalid number */ + rc = LY_EDENIED; + } else if (*ptr) { + while (isspace(*ptr)) { + ++ptr; + } + if (*ptr) { + /* invalid characters after some number */ + rc = LY_EVALID; + } + } + + /* cleanup */ + free(str); + if (!rc) { + *ret = u; + } + return rc; +} + +/** + * @brief Parse an identifier. + * + * ;; An identifier MUST NOT start with (('X'|'x') ('M'|'m') ('L'|'l')) + * identifier = (ALPHA / "_") + * *(ALPHA / DIGIT / "_" / "-" / ".") + * + * @param[in,out] id Identifier to parse. When returned, it points to the first character which is not part of the identifier. + * @return LY_ERR value: LY_SUCCESS or LY_EINVAL in case of invalid starting character. + */ +static LY_ERR +lys_parse_id(const char **id) +{ + assert(id && *id); + + if (!is_yangidentstartchar(**id)) { + return LY_EINVAL; + } + ++(*id); + + while (is_yangidentchar(**id)) { + ++(*id); + } + return LY_SUCCESS; +} + +LY_ERR +ly_parse_nodeid(const char **id, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len) +{ + assert(id && *id); + assert(prefix && prefix_len); + assert(name && name_len); + + *prefix = *id; + *prefix_len = 0; + *name = NULL; + *name_len = 0; + + LY_CHECK_RET(lys_parse_id(id)); + if (**id == ':') { + /* there is prefix */ + *prefix_len = *id - *prefix; + ++(*id); + *name = *id; + + LY_CHECK_RET(lys_parse_id(id)); + *name_len = *id - *name; + } else { + /* there is no prefix, so what we have as prefix now is actually the name */ + *name = *prefix; + *name_len = *id - *name; + *prefix = NULL; + } + + return LY_SUCCESS; +} + +LY_ERR +ly_parse_instance_predicate(const char **pred, size_t limit, LYD_FORMAT format, + const char **prefix, size_t *prefix_len, const char **id, size_t *id_len, const char **value, size_t *value_len, + const char **errmsg) +{ + LY_ERR ret = LY_EVALID; + const char *in = *pred; + size_t offset = 1; + uint8_t expr = 0; /* 0 - position predicate; 1 - leaf-list-predicate; 2 - key-predicate */ + char quot; + + assert(in[0] == '['); + + *prefix = *id = *value = NULL; + *prefix_len = *id_len = *value_len = 0; + + /* leading *WSP */ + for ( ; isspace(in[offset]); offset++) {} + + if (isdigit(in[offset])) { + /* pos: "[" *WSP positive-integer-value *WSP "]" */ + if (in[offset] == '0') { + /* zero */ + *errmsg = "The position predicate cannot be zero."; + goto error; + } + + /* positive-integer-value */ + *value = &in[offset++]; + for ( ; isdigit(in[offset]); offset++) {} + *value_len = &in[offset] - *value; + + } else if (in[offset] == '.') { + /* leaf-list-predicate: "[" *WSP "." *WSP "=" *WSP quoted-string *WSP "]" */ + *id = &in[offset]; + *id_len = 1; + offset++; + expr = 1; + } else if (in[offset] == '-') { + /* typically negative value */ + *errmsg = "Invalid instance predicate format (negative position or invalid node-identifier)."; + goto error; + } else { + /* key-predicate: "[" *WSP node-identifier *WSP "=" *WSP quoted-string *WSP "]" */ + in = &in[offset]; + if (ly_parse_nodeid(&in, prefix, prefix_len, id, id_len)) { + *errmsg = "Invalid node-identifier."; + goto error; + } + if ((format == LYD_XML) && !(*prefix)) { + /* all node names MUST be qualified with explicit namespace prefix */ + *errmsg = "Missing prefix of a node name."; + goto error; + } + offset = in - *pred; + in = *pred; + expr = 2; + } + + if (expr) { + /* *WSP "=" *WSP quoted-string *WSP "]" */ + for ( ; isspace(in[offset]); offset++) {} + + if (in[offset] != '=') { + if (expr == 1) { + *errmsg = "Unexpected character instead of \'=\' in leaf-list-predicate."; + } else { /* 2 */ + *errmsg = "Unexpected character instead of \'=\' in key-predicate."; + } + goto error; + } + offset++; + for ( ; isspace(in[offset]); offset++) {} + + /* quoted-string */ + quot = in[offset++]; + if ((quot != '\'') && (quot != '\"')) { + *errmsg = "String value is not quoted."; + goto error; + } + *value = &in[offset]; + for ( ; offset < limit && (in[offset] != quot || (offset && in[offset - 1] == '\\')); offset++) {} + if (in[offset] == quot) { + *value_len = &in[offset] - *value; + offset++; + } else { + *errmsg = "Value is not terminated quoted-string."; + goto error; + } + } + + /* *WSP "]" */ + for ( ; isspace(in[offset]); offset++) {} + if (in[offset] != ']') { + if (expr == 0) { + *errmsg = "Predicate (pos) is not terminated by \']\' character."; + } else if (expr == 1) { + *errmsg = "Predicate (leaf-list-predicate) is not terminated by \']\' character."; + } else { /* 2 */ + *errmsg = "Predicate (key-predicate) is not terminated by \']\' character."; + } + goto error; + } + offset++; + + if (offset <= limit) { + *pred = &in[offset]; + return LY_SUCCESS; + } + + /* we read after the limit */ + *errmsg = "Predicate is incomplete."; + *prefix = *id = *value = NULL; + *prefix_len = *id_len = *value_len = 0; + offset = limit; + ret = LY_EINVAL; + +error: + *pred = &in[offset]; + return ret; +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..264fe81 --- /dev/null +++ b/src/common.h @@ -0,0 +1,626 @@ +/** + * @file common.h + * @author Radek Krejci + * @brief common internal definitions for libyang + * + * Copyright (c) 2015 - 2018 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_COMMON_H_ +#define LY_COMMON_H_ + +#include +#include +#include +#include + +#include "compat.h" +#include "config.h" +#include "context.h" +#include "hash_table.h" +#include "log.h" +#include "schema_compile.h" +#include "set.h" +#include "tree_data.h" + +struct ly_ctx; +struct ly_in; +struct lysc_node; + +#if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__ +# define THREAD_LOCAL _Thread_local +#elif defined __GNUC__ || \ + defined __SUNPRO_C || \ + defined __xlC__ +# define THREAD_LOCAL __thread +#elif defined _MSC_VER +# define THREAD_LOCAL __declspec(thread) +#else +# error "Cannot define THREAD_LOCAL" +#endif + +#define GETMACRO1(_1, NAME, ...) NAME +#define GETMACRO2(_1, _2, NAME, ...) NAME +#define GETMACRO3(_1, _2, _3, NAME, ...) NAME +#define GETMACRO4(_1, _2, _3, _4, NAME, ...) NAME +#define GETMACRO5(_1, _2, _3, _4, _5, NAME, ...) NAME +#define GETMACRO6(_1, _2, _3, _4, _5, _6, NAME, ...) NAME + +/****************************************************************************** + * Logger + *****************************************************************************/ + +extern ATOMIC_T ly_ll; +extern ATOMIC_T ly_log_opts; + +struct ly_log_location_s { + uint64_t line; /**< One-time line value being reset after use - replaces whatever is in inputs */ + struct ly_set inputs; /**< Set of const struct ly_in *in pointers providing the input handler with the line information (LIFO) */ + struct ly_set scnodes; /**< Set of const struct lysc_node *scnode pointers providing the compiled schema node to generate path (LIFO) */ + struct ly_set dnodes; /**< Set of const struct lyd_node *dnode pointers providing the data node to generate path (LIFO) */ + struct ly_set paths; /**< Set of path strings (LIFO) */ +}; + +/** + * @brief Print a log message and store it into the context (if provided). + * + * @param[in] ctx libyang context to store the error record. If not provided, the error is just printed. + * @param[in] level Log message level (error, warning, etc.) + * @param[in] no Error type code. + * @param[in] format Format string to print. + */ +void ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char *format, ...); + +/** + * @brief Print Validation error and store it into the context (if provided). + * + * @param[in] ctx libyang context to store the error record. If not provided, the error is just printed. + * @param[in] apptag Optional specific error-app-tag. + * @param[in] code Validation error code. + * @param[in] format Format string to print. + */ +void ly_vlog(const struct ly_ctx *ctx, const char *apptag, LY_VECODE code, const char *format, ...); + +/** + * @brief Logger's location data setter. + * + * @param[in] scnode Compiled schema node. + * @param[in] dnode Data node. + * @param[in] path Direct path string to print. + * @param[in] in Input handler (providing line number) + * @param[in] line One-time line value to be reset when used. + */ +void ly_log_location(const struct lysc_node *scnode, const struct lyd_node *dnode, + const char *path, const struct ly_in *in, uint64_t line); + +/** + * @brief Revert the specific logger's location data by number of changes made by ::ly_log_location(). + * + * @param[in] scnode_steps Number of items in ::ly_log_location_s.scnodes to forget. + * @param[in] dnode_steps Number of items in ::ly_log_location_s.dnodes to forget. + * @param[in] path_steps Number of path strings in ::ly_log_location_s.paths to forget. + * @param[in] in_steps Number of input handlers ::ly_log_location_s.inputs to forget. + */ +void ly_log_location_revert(uint32_t scnode_steps, uint32_t dnode_steps, uint32_t path_steps, uint32_t in_steps); + +/** + * @brief Update location data for logger, not provided arguments (NULLs) are kept (does not override). + * + * @param[in] SCNODE Compiled schema node. + * @param[in] DNODE Data node. + * @param[in] PATH Direct path string to print. + * @param[in] IN Input handler (providing line number) + */ +#define LOG_LOCSET(SCNODE, DNODE, PATH, IN) \ + ly_log_location(SCNODE, DNODE, PATH, IN, 0) + +/** + * @brief Update location data for logger, not provided arguments (NULLs) are kept (does not override). + * + * @param[in] SCNODE_STEPS Number of the compiled schema nodes to remove from the stack. + * @param[in] DNODE_STEPS Number of the data nodes to remove from the stack. + * @param[in] PATH_STEPS Number of the direct path strings to remove from the stack. + * @param[in] IN_STEPS Number of the input handlers (providing line number) to remove from the stack. + */ +#define LOG_LOCBACK(SCNODE_STEPS, DNODE_STEPS, PATH_STEPS, IN_STEPS) \ + ly_log_location_revert(SCNODE_STEPS, DNODE_STEPS, PATH_STEPS, IN_STEPS) + +#define LOGERR(ctx, errno, ...) ly_log(ctx, LY_LLERR, errno, __VA_ARGS__) +#define LOGWRN(ctx, ...) ly_log(ctx, LY_LLWRN, 0, __VA_ARGS__) +#define LOGVRB(...) ly_log(NULL, LY_LLVRB, 0, __VA_ARGS__) + +#ifdef NDEBUG +# define LOGDBG(dbg_group, ...) +#else +void ly_log_dbg(uint32_t group, const char *format, ...); +# define LOGDBG(dbg_group, ...) ly_log_dbg(dbg_group, __VA_ARGS__); +#endif + +/** + * Simple EMEM message, it can be safely stored in ::ly_err_item structures without problems when freeing. + */ +#define LY_EMEM_MSG "Memory allocation failed." + +#ifdef LOGMEM +/* overwrite shadow definition from tree_edit.h */ +#undef LOGMEM +#endif +#define LOGMEM(CTX) LOGERR(CTX, LY_EMEM, "Memory allocation failed (%s()).", __func__) + +#define LOGINT(CTX) LOGERR(CTX, LY_EINT, "Internal error (%s:%d).", __FILE__, __LINE__) +#define LOGARG(CTX, ARG) LOGERR(CTX, LY_EINVAL, "Invalid argument %s (%s()).", #ARG, __func__) +#define LOGVAL(CTX, ...) ly_vlog(CTX, NULL, __VA_ARGS__) +#define LOGVAL_APPTAG(CTX, APPTAG, ...) ly_vlog(CTX, APPTAG, __VA_ARGS__) +#define LOGVAL_LINE(CTX, LINE, ...) \ + ly_log_location(NULL, NULL, NULL, NULL, LINE); \ + ly_vlog(CTX, NULL, __VA_ARGS__) + +/** + * @brief Print Validation error from struct ly_err_item. + * + * String ::ly_err_item.msg cannot be used directly because it may contain the % character, + * which is incorrectly interpreted in this situation as a conversion specification. + * + * @param[in] CTX libyang context to store the error record. If not provided, the error is just printed. + * @param[in] ERRITEM pointer to ly_err_item that contains an error message. + */ +#define LOGVAL_ERRITEM(CTX, ERRITEM) ly_vlog(CTX, ERRITEM->apptag, ERRITEM->vecode, "%s", ERRITEM->msg) + +#define LOGMEM_RET(CTX) LOGMEM(CTX); return LY_EMEM +#define LOGINT_RET(CTX) LOGINT(CTX); return LY_EINT +#define LOGARG_RET(CTX) LOGARG(CTX); return LY_EINVAL + +/* + * Common code to check return value and perform appropriate action. + */ +#define LY_CHECK_GOTO(COND, GOTO) if ((COND)) {goto GOTO;} +#define LY_CHECK_ERR_GOTO(COND, ERR, GOTO) if ((COND)) {ERR; goto GOTO;} +#define LY_CHECK_RET1(RETVAL) {LY_ERR ret__ = RETVAL;if (ret__ != LY_SUCCESS) {return ret__;}} +#define LY_CHECK_RET2(COND, RETVAL) if ((COND)) {return RETVAL;} +#define LY_CHECK_RET(...) GETMACRO2(__VA_ARGS__, LY_CHECK_RET2, LY_CHECK_RET1, DUMMY)(__VA_ARGS__) +#define LY_CHECK_ERR_RET(COND, ERR, RETVAL) if ((COND)) {ERR; return RETVAL;} + +#define LY_CHECK_ARG_GOTO1(CTX, ARG, GOTO) if (!(ARG)) {LOGARG(CTX, ARG);goto GOTO;} +#define LY_CHECK_ARG_GOTO2(CTX, ARG1, ARG2, GOTO) LY_CHECK_ARG_GOTO1(CTX, ARG1, GOTO);LY_CHECK_ARG_GOTO1(CTX, ARG2, GOTO) +#define LY_CHECK_ARG_GOTO3(CTX, ARG1, ARG2, ARG3, GOTO) LY_CHECK_ARG_GOTO2(CTX, ARG1, ARG2, GOTO);LY_CHECK_ARG_GOTO1(CTX, ARG3, GOTO) +#define LY_CHECK_ARG_GOTO4(CTX, ARG1, ARG2, ARG3, ARG4, GOTO) LY_CHECK_ARG_GOTO3(CTX, ARG1, ARG2, ARG3, GOTO);\ + LY_CHECK_ARG_GOTO1(CTX, ARG4, GOTO) +#define LY_CHECK_ARG_GOTO(CTX, ...) GETMACRO5(__VA_ARGS__, LY_CHECK_ARG_GOTO4, LY_CHECK_ARG_GOTO3, LY_CHECK_ARG_GOTO2, \ + LY_CHECK_ARG_GOTO1)(CTX, __VA_ARGS__) + +#define LY_CHECK_ARG_RET1(CTX, ARG, RETVAL) if (!(ARG)) {LOGARG(CTX, ARG);return RETVAL;} +#define LY_CHECK_ARG_RET2(CTX, ARG1, ARG2, RETVAL) LY_CHECK_ARG_RET1(CTX, ARG1, RETVAL);LY_CHECK_ARG_RET1(CTX, ARG2, RETVAL) +#define LY_CHECK_ARG_RET3(CTX, ARG1, ARG2, ARG3, RETVAL) LY_CHECK_ARG_RET2(CTX, ARG1, ARG2, RETVAL);LY_CHECK_ARG_RET1(CTX, ARG3, RETVAL) +#define LY_CHECK_ARG_RET4(CTX, ARG1, ARG2, ARG3, ARG4, RETVAL) LY_CHECK_ARG_RET3(CTX, ARG1, ARG2, ARG3, RETVAL);\ + LY_CHECK_ARG_RET1(CTX, ARG4, RETVAL) +#define LY_CHECK_ARG_RET5(CTX, ARG1, ARG2, ARG3, ARG4, ARG5, RETVAL) LY_CHECK_ARG_RET4(CTX, ARG1, ARG2, ARG3, ARG4, RETVAL);\ + LY_CHECK_ARG_RET1(CTX, ARG5, RETVAL) +#define LY_CHECK_ARG_RET(CTX, ...) GETMACRO6(__VA_ARGS__, LY_CHECK_ARG_RET5, LY_CHECK_ARG_RET4, LY_CHECK_ARG_RET3, \ + LY_CHECK_ARG_RET2, LY_CHECK_ARG_RET1, DUMMY) (CTX, __VA_ARGS__) + +#define LY_CHECK_CTX_EQUAL_RET2(CTX1, CTX2, RETVAL) if ((CTX1) && (CTX2) && ((CTX1) != (CTX2))) \ + {LOGERR(CTX1, LY_EINVAL, "Different contexts mixed in a single function call."); return RETVAL;} +#define LY_CHECK_CTX_EQUAL_RET3(CTX1, CTX2, CTX3, RETVAL) LY_CHECK_CTX_EQUAL_RET2(CTX1, CTX2, RETVAL); \ + LY_CHECK_CTX_EQUAL_RET2(CTX2, CTX3, RETVAL); LY_CHECK_CTX_EQUAL_RET2(CTX1, CTX3, RETVAL) +#define LY_CHECK_CTX_EQUAL_RET(CTX, ...) GETMACRO3(__VA_ARGS__, LY_CHECK_CTX_EQUAL_RET3, LY_CHECK_CTX_EQUAL_RET2, \ + DUMMY) (CTX, __VA_ARGS__) + +/* count sequence size for LY_VCODE_INCHILDSTMT validation error code */ +size_t LY_VCODE_INSTREXP_len(const char *str); +/* default maximum characters to print in LY_VCODE_INCHILDSTMT */ +#define LY_VCODE_INSTREXP_MAXLEN 20 + +#define LY_VCODE_INCHAR LYVE_SYNTAX, "Invalid character 0x%x." +#define LY_VCODE_INSTREXP LYVE_SYNTAX, "Invalid character sequence \"%.*s\", expected %s." +#define LY_VCODE_EOF LYVE_SYNTAX, "Unexpected end-of-input." +#define LY_VCODE_NTERM LYVE_SYNTAX, "%s not terminated." +#define LY_VCODE_NSUPP LYVE_SYNTAX, "%s not supported." +#define LY_VCODE_MOD_SUBOMD LYVE_SYNTAX, "Invalid keyword \"%s\", expected \"module\" or \"submodule\"." +#define LY_VCODE_TRAILING_MOD LYVE_SYNTAX, "Trailing garbage \"%.*s%s\" after module, expected end-of-input." +#define LY_VCODE_TRAILING_SUBMOD LYVE_SYNTAX, "Trailing garbage \"%.*s%s\" after submodule, expected end-of-input." + +#define LY_VCODE_INVAL_MINMAX LYVE_SEMANTICS, "Invalid combination of min-elements and max-elements: min value %u is bigger than the max value %u." +#define LY_VCODE_NAME_COL LYVE_SEMANTICS, "Name collision between %s of name \"%s\"." +#define LY_VCODE_NAME2_COL LYVE_SEMANTICS, "Name collision between %s and %s of name \"%s\"." + +#define LY_VCODE_INSTMT LYVE_SYNTAX_YANG, "Invalid keyword \"%s\"." +#define LY_VCODE_INCHILDSTMT LYVE_SYNTAX_YANG, "Invalid keyword \"%s\" as a child of \"%s\"." +#define LY_VCODE_INCHILDSTMT2 LYVE_SYNTAX_YANG, "Invalid keyword \"%s\" as a child of \"%s\" - the statement is allowed only in YANG 1.1 modules." +#define LY_VCODE_INCHILDSTMSCOMB LYVE_SYNTAX_YANG, "Invalid combination of keywords \"%s\" and \"%s\" as substatements of \"%s\"." +#define LY_VCODE_DUPSTMT LYVE_SYNTAX_YANG, "Duplicate keyword \"%s\"." +#define LY_VCODE_DUPIDENT LYVE_SYNTAX_YANG, "Duplicate identifier \"%s\" of %s statement." +#define LY_VCODE_DUPIDENT2 LYVE_SYNTAX_YANG, "Duplicate identifier \"%s\" of %s statement - %s." +#define LY_VCODE_INVAL LYVE_SYNTAX_YANG, "Invalid value \"%.*s\" of \"%s\"." +#define LY_VCODE_MISSTMT LYVE_SYNTAX_YANG, "Missing mandatory keyword \"%s\" as a child of \"%s\"." +#define LY_VCODE_MISSCHILDSTMT LYVE_SYNTAX_YANG, "Missing %s substatement for %s%s." +#define LY_VCODE_INORD LYVE_SYNTAX_YANG, "Invalid keyword \"%s\", it cannot appear after \"%s\"." +#define LY_VCODE_OOB LYVE_SYNTAX_YANG, "Value \"%.*s\" is out of \"%s\" bounds." +#define LY_VCODE_INDEV LYVE_SYNTAX_YANG, "Deviate \"%s\" does not support keyword \"%s\"." +#define LY_VCODE_INREGEXP LYVE_SYNTAX_YANG, "Regular expression \"%s\" is not valid (\"%s\": %s)." + +#define LY_VCODE_INSUBELEM2 LYVE_SYNTAX_YIN, "Invalid sub-elemnt \"%s\" of \"%s\" element - this sub-element is allowed only in modules with version 1.1 or newer." +#define LY_VCODE_INVAL_YIN LYVE_SYNTAX_YIN, "Invalid value \"%s\" of \"%s\" attribute in \"%s\" element." +#define LY_VCODE_UNEXP_SUBELEM LYVE_SYNTAX_YIN, "Unexpected sub-element \"%.*s\" of \"%s\" element." +#define LY_VCODE_INDEV_YIN LYVE_SYNTAX_YIN, "Deviate of this type doesn't allow \"%s\" as it's sub-element." +#define LY_VCODE_INORDER_YIN LYVE_SYNTAX_YIN, "Invalid order of %s\'s sub-elements \"%s\" can't appear after \"%s\"." +#define LY_VCODE_OOB_YIN LYVE_SYNTAX_YIN, "Value \"%s\" of \"%s\" attribute in \"%s\" element is out of bounds." +#define LY_VCODE_INCHILDSTMSCOMB_YIN LYVE_SYNTAX_YIN, "Invalid combination of sub-elemnts \"%s\" and \"%s\" in \"%s\" element." +#define LY_VCODE_DUP_ATTR LYVE_SYNTAX_YIN, "Duplicit definition of \"%s\" attribute in \"%s\" element." +#define LY_VCODE_UNEXP_ATTR LYVE_SYNTAX_YIN, "Unexpected attribute \"%.*s\" of \"%s\" element." +#define LY_VCODE_MAND_SUBELEM LYVE_SYNTAX_YIN, "Missing mandatory sub-element \"%s\" of \"%s\" element." +#define LY_VCODE_FIRT_SUBELEM LYVE_SYNTAX_YIN, "Sub-element \"%s\" of \"%s\" element must be defined as it's first sub-element." +#define LY_VCODE_SUBELEM_REDEF LYVE_SYNTAX_YIN, "Redefinition of \"%s\" sub-element in \"%s\" element." + +#define LY_VCODE_XP_EOE LYVE_XPATH, "Unterminated string delimited with %c (%.15s)." +#define LY_VCODE_XP_INEXPR LYVE_XPATH, "Invalid character '%c'[%u] of expression \'%s\'." +#define LY_VCODE_XP_EOF LYVE_XPATH, "Unexpected XPath expression end." +#define LY_VCODE_XP_INTOK LYVE_XPATH, "Unexpected XPath token \"%s\" (\"%.15s\")." +#define LY_VCODE_XP_INTOK2 LYVE_XPATH, "Unexpected XPath token \"%s\" (\"%.15s\"), expected \"%s\"." +#define LY_VCODE_XP_INFUNC LYVE_XPATH, "Unknown XPath function \"%.*s\"." +#define LY_VCODE_XP_INARGCOUNT LYVE_XPATH, "Invalid number of arguments (%d) for the XPath function %.*s." +#define LY_VCODE_XP_INARGTYPE LYVE_XPATH, "Wrong type of argument #%d (%s) for the XPath function %s." +#define LY_VCODE_XP_INCTX LYVE_XPATH, "Invalid context type %s in %s." +#define LY_VCODE_XP_INOP_1 LYVE_XPATH, "Cannot apply XPath operation %s on %s." +#define LY_VCODE_XP_INOP_2 LYVE_XPATH, "Cannot apply XPath operation %s on %s and %s." +#define LY_VCODE_XP_INMOD LYVE_XPATH, "Unknown/non-implemented module \"%.*s\"." +#define LY_VCODE_XP_DEPTH LYVE_XPATH, "The maximum nesting of expressions has been exceeded." + +#define LY_VCODE_DEV_NOT_PRESENT LYVE_REFERENCE, "Invalid deviation %s \"%s\" property \"%s\" which is not present." + +#define LY_VCODE_NOWHEN LYVE_DATA, "When condition \"%s\" not satisfied." +#define LY_VCODE_NOMAND LYVE_DATA, "Mandatory node \"%s\" instance does not exist." +#define LY_VCODE_DUP LYVE_DATA, "Duplicate instance of \"%s\"." +#define LY_VCODE_DUPCASE LYVE_DATA, "Data for both cases \"%s\" and \"%s\" exist." +#define LY_VCODE_UNEXPNODE LYVE_DATA, "Unexpected data %s node \"%s\" found." +#define LY_VCODE_NOKEY LYVE_DATA, "List instance is missing its key \"%s\"." + +#define LY_ERRMSG_NOPATTERN /* LYVE_DATA */ "Unsatisfied pattern - \"%.*s\" does not conform to %s\"%s\"." +#define LY_ERRMSG_NOLENGTH /* LYVE_DATA */ "Unsatisfied length - string \"%.*s\" length is not allowed." +#define LY_ERRMSG_NORANGE /* LYVE_DATA */ "Unsatisfied range - value \"%.*s\" is out of the allowed range." + +/* RFC 7950 section 15 errors */ +#define LY_VCODE_NOUNIQ LYVE_DATA, "Unique data leaf(s) \"%s\" not satisfied in \"%s\" and \"%s\"." +#define LY_VCODE_NOMAX LYVE_DATA, "Too many \"%s\" instances." +#define LY_VCODE_NOMIN LYVE_DATA, "Too few \"%s\" instances." +#define LY_VCODE_NOMUST LYVE_DATA, "Must condition \"%s\" not satisfied." +#define LY_VCODE_NOMAND_CHOIC LYVE_DATA, "Mandatory choice \"%s\" data do not exist." + +/* RFC 7950 section 15 error messages used in type plugin validation callbacks */ +#define LY_ERRMSG_NOLREF_VAL /* LYVE_DATA */ "Invalid leafref value \"%s\" - no target instance \"%s\" with the same value." +#define LY_ERRMSG_NOINST /* LYVE_DATA */ "Invalid instance-identifier \"%s\" value - required instance not found." + +/****************************************************************************** + * Context + *****************************************************************************/ + +/** + * @brief Context of the YANG schemas + */ +struct ly_ctx { + struct dict_table dict; /**< dictionary to effectively store strings used in the context related structures */ + struct ly_set search_paths; /**< set of directories where to search for schema's imports/includes */ + struct ly_set list; /**< set of loaded YANG schemas */ + ly_module_imp_clb imp_clb; /**< optional callback for retrieving missing included or imported models */ + void *imp_clb_data; /**< optional private data for ::ly_ctx.imp_clb */ + struct lys_glob_unres unres; /**< global unres, should be empty unless there are modules prepared for + compilation if ::LY_CTX_EXPLICIT_COMPILE flag is set */ + uint16_t change_count; /**< count of changes of the context, on some changes it could be incremented + more times */ + uint16_t flags; /**< context settings, see @ref contextoptions */ + + ly_ext_data_clb ext_clb; /**< optional callback for providing extension-specific run-time data for extensions */ + void *ext_clb_data; /**< optional private data for ::ly_ctx.ext_clb */ + pthread_key_t errlist_key; /**< key for the thread-specific list of errors related to the context */ + pthread_mutex_t lyb_hash_lock; /**< lock for storing LYB schema hashes in schema nodes */ +}; + +/** + * @brief Get the (only) implemented YANG module specified by its name. + * + * @param[in] ctx Context where to search. + * @param[in] name Name of the YANG module to get. + * @param[in] name_len Optional length of the @p name. If zero, NULL-terminated name is expected. + * @return The only implemented YANG module revision of the given name in the given context. NULL if there is no + * implemented module of the given name. + */ +struct lys_module *ly_ctx_get_module_implemented2(const struct ly_ctx *ctx, const char *name, size_t name_len); + +/****************************************************************************** + * Generic useful functions. + *****************************************************************************/ + +/** + * @brief Insert string into dictionary. + * + * @param[in] CTX libyang context. + * @param[in] STRING string to store. + * @param[in] LEN length of the string in WORD to store. + * @param[in,out] DYNAMIC Set to 1 if @p STRING is dynamically allocated, 0 otherwise. + * If set to 1, zerocopy version of lydict_insert is used. + * @param[out] TARGET pointer is set to @p STRING value stored in the dictionary. + */ +#define INSERT_STRING_RET(CTX, STRING, LEN, DYNAMIC, TARGET) \ + if (DYNAMIC) { \ + LY_CHECK_RET(lydict_insert_zc(CTX, (char *)(STRING), &(TARGET))); \ + } else { \ + LY_CHECK_RET(lydict_insert(CTX, LEN ? (STRING) : "", LEN, &(TARGET))); \ + } \ + DYNAMIC = 0 + +/** + * @brief Wrapper for realloc() call. The only difference is that if it fails to + * allocate the requested memory, the original memory is freed as well. + * + * @param[in] ptr Memory to reallocate. + * @param[in] size New size of the memory block. + * + * @return Pointer to the new memory, NULL on error. + */ +void *ly_realloc(void *ptr, size_t size); + +/** + * @brief Just like strchr() function except limit the number of examined characters. + * + * @param[in] s String to search in. + * @param[in] c Character to search for. + * @param[in] len Limit the search to this number of characters in @p s. + * @return Pointer to first @p c occurrence in @p s, NULL if not found in first @p len characters. + */ +char *ly_strnchr(const char *s, int c, size_t len); + +/** + * @brief Compare NULL-terminated @p refstr with @p str_len bytes from @p str. + * + * @param[in] refstr NULL-terminated string which must match @p str_len bytes from @p str followed by NULL-byte. + * @param[in] str String to compare. + * @param[in] str_len Number of bytes to take into comparison from @p str. + * @return An integer less than, equal to, or greater than zero if @p refstr matches, + * respectively, to be less than, to match, or be greater than @p str. + */ +int ly_strncmp(const char *refstr, const char *str, size_t str_len); + +/** + * @brief Similar functionality to strtoul() except number length in the string + * must be specified and the whole number must be parsed for success. + * + * @param[in] nptr Number string. + * @param[in] len Number string length starting at @p nptr. + * @param[out] ret Parsed number. + * @return LY_EDENIED on overflow. + * @return LY_EVALID on encountering a non-digit character. + * @return LY_SUCCESS on success. + */ +LY_ERR ly_strntou8(const char *nptr, size_t len, uint8_t *ret); + +/** + * @brief Get all possible value prefixes from an YANG value by iteratively returning specific substrings. + * + * The function looks for possible prefix ending in a colon at the beginning of @p str_begin. + * If @p str_begin does not contain the prefix at the beginning, then either: + * 1. Returns the entire input string if the input string does not contain the prefix at all. + * 2. Returns a substring before the prefix. The substring is terminated by any character + * that is not allowed to be present in prefix (except colon). + * + * Examples of inputs and outputs are shown in the table below. + * Output string @p str_next is used in the next iteration as input parameter @p str_begin. + @verbatim + | INPUT | OUTPUT | + | | iteration 1 | iteration 2 | iteration 3 | + |------------------------------ |------------------|------------------|-----------------| + | /namespace_prefix:some_string | / | namespace_prefix | some_string | + | namespace_prefix:some_string | namespace_prefix | some_string | NULL | + | /some_string | /some_string | NULL | NULL | + @endverbatim + * + * + * @param[in] str_begin Begin of the input string. + * @param[in] str_end Length of the @p str_begin. If set to NULL then the @p str_begin must be NULL-terminated string. + * @param[out] len Number of bytes (length) of the found prefix/substring starting at @p str_begin. + * @param[out] is_prefix Type of substring found. Set to True for prefix, otherwise False. + * @param[out] str_next Remaining string starting after prefix/substring and ending with @p str_end. + * If the @p is_prefix is set to True then the colon character is skipped. + * If no string remains, it is set to NULL. + * @return LY_ERR value. + */ +LY_ERR ly_value_prefix_next(const char *str_begin, const char *str_end, uint32_t *len, ly_bool *is_prefix, const char **str_next); + +/** + * @brief Wrapper around strlen() to handle NULL strings. + */ +#define ly_strlen(STR) (STR ? strlen(STR) : 0) + +/** + * @brief Compile-time strlen() for string constants. + * + * Use to avoid magic numbers usage + */ +#define ly_strlen_const(STR) (sizeof STR - 1) + +/** + * @brief Macro to simply put couple of string length and the string as + * printf's arguments for %.*s. Use only with constant strings. + */ +#define LY_PRI_LENSTR(STR) (int)ly_strlen_const(STR), STR + +#define ly_sizeofarray(ARRAY) (sizeof ARRAY / sizeof *ARRAY) + +/** + * @brief Check for overflow during the addition of two unsigned integers. + */ +#define LY_OVERFLOW_ADD(MAX, X, Y) (X > MAX - Y) + +/** + * @brief Check for overflow during the multiplication of two unsigned integers. + */ +#define LY_OVERFLOW_MUL(MAX, X, Y) (X > MAX / Y) + +/* + * Numerical bases for use in functions like strtoll() instead of magic numbers + */ +#define LY_BASE_DEC 10 /**< Decimal numeral base */ +#define LY_BASE_OCT 8 /**< Octal numeral base */ +#define LY_BASE_HEX 16 /**< Hexadecimal numeral base */ + +/** + * Maximal length of (needed storage for) a number encoded as a string. + * + * Applies not only for standard numbers, but also for YANG's decimal64. + */ +#define LY_NUMBER_MAXLEN 22 + +/** + * @brief Get UTF8 code point of the next character in the input string. + * + * @param[in,out] input Input string to process, updated according to the processed/read data. + * @param[out] utf8_char UTF8 code point of the next character. + * @param[out] bytes_read Number of bytes used to encode the read utf8_char. + * @return LY_ERR value + */ +LY_ERR ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read); + +/** + * Store UTF-8 character specified as 4byte integer into the dst buffer. + * + * UTF-8 mapping: + * 00000000 -- 0000007F: 0xxxxxxx + * 00000080 -- 000007FF: 110xxxxx 10xxxxxx + * 00000800 -- 0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx + * 00010000 -- 001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + * + * Includes checking for valid characters (following RFC 7950, sec 9.4) + * + * @param[in, out] dst Destination buffer to store the UTF-8 character, must provide enough space (up to 4 bytes) for storing the UTF-8 character. + * @param[in] value 32b value of the UTF-8 character to store. + * @param[out] bytes_written Number of bytes written into @p dst (size of the written UTF-8 character). + * @return LY_SUCCESS on success + * @return LY_EINVAL in case of invalid UTF-8 @p value to store. + */ +LY_ERR ly_pututf8(char *dst, uint32_t value, size_t *bytes_written); + +/** + * @brief Get number of characters in the @p str, taking multibyte characters into account. + * @param[in] str String to examine. + * @param[in] bytes Number of valid bytes that are supposed to be taken into account in @p str. + * This parameter is useful mainly for non NULL-terminated strings. In case of NULL-terminated + * string, strlen() can be used. + * @return Number of characters in (possibly) multibyte characters string. + */ +size_t ly_utf8len(const char *str, size_t bytes); + +/** + * @brief Parse signed integer with possible limitation. + * @param[in] val_str String value containing signed integer, note that + * nothing else than whitespaces are expected after the value itself. + * @param[in] val_len Length of the @p val_str string. + * @param[in] min Limitation for the value which must not be lower than min. + * @param[in] max Limitation for the value which must not be higher than max. + * @param[in] base Numeric base for parsing: + * 0 - to accept decimal, octal, hexadecimal (e.g. in default value) + * 10 - to accept only decimal (e.g. data instance value) + * @param[out] ret Resulting value. + * @return LY_ERR value: + * LY_EDENIED - the value breaks the limits, + * LY_EVALID - string contains invalid value, + * LY_SUCCESS - successful parsing. + */ +LY_ERR ly_parse_int(const char *val_str, size_t val_len, int64_t min, int64_t max, int base, int64_t *ret); + +/** + * @brief Parse unsigned integer with possible limitation. + * @param[in] val_str String value containing unsigned integer, note that + * nothing else than whitespaces are expected after the value itself. + * @param[in] val_len Length of the @p val_str string. + * @param[in] max Limitation for the value which must not be higher than max. + * @param[in] base Numeric base for parsing: + * 0 - to accept decimal, octal, hexadecimal (e.g. in default value) + * 10 - to accept only decimal (e.g. data instance value) + * @param[out] ret Resulting value. + * @return LY_ERR value: + * LY_EDENIED - the value breaks the limits, + * LY_EVALID - string contains invalid value, + * LY_SUCCESS - successful parsing. + */ +LY_ERR ly_parse_uint(const char *val_str, size_t val_len, uint64_t max, int base, uint64_t *ret); + +/** + * @brief Parse a node-identifier. + * + * node-identifier = [prefix ":"] identifier + * + * @param[in, out] id Identifier to parse. When returned, it points to the first character which is not part of the identifier. + * @param[out] prefix Node's prefix, NULL if there is not any. + * @param[out] prefix_len Length of the node's prefix, 0 if there is not any. + * @param[out] name Node's name. + * @param[out] name_len Length of the node's name. + * @return LY_ERR value: LY_SUCCESS or LY_EINVAL in case of invalid character in the id. + */ +LY_ERR ly_parse_nodeid(const char **id, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len); + +/** + * @brief parse instance-identifier's predicate, supports key-predicate, leaf-list-predicate and pos rules from YANG ABNF Grammar. + * + * @param[in, out] pred Predicate string (including the leading '[') to parse. The string is updated according to what was parsed + * (even for error case, so it can be used to determine which substring caused failure). + * @param[in] limit Limiting length of the @p pred. Function expects NULL terminated string which is not overread. + * The limit value is not checked with each character, so it can be overread and the failure is detected later. + * @param[in] format Input format of the data containing the @p pred. + * @param[out] prefix Start of the node-identifier's prefix if any, NULL in case of pos or leaf-list-predicate rules. + * @param[out] prefix_len Length of the parsed @p prefix. + * @param[out] id Start of the node-identifier's identifier string, NULL in case of pos rule, "." in case of leaf-list-predicate rule. + * @param[out] id_len Length of the parsed @p id. + * @param[out] value Start of the quoted-string (without quotation marks), not NULL in case of success. + * @param[out] value_len Length of the parsed @p value. + * @param[out] errmsg Error message string in case of error. + * @return LY_SUCCESS in case a complete predicate was parsed. + * @return LY_EVALID in case of invalid predicate form. + * @return LY_EINVAL in case of reaching @p limit when parsing @p pred. + */ +LY_ERR ly_parse_instance_predicate(const char **pred, size_t limit, LYD_FORMAT format, + const char **prefix, size_t *prefix_len, const char **id, size_t *id_len, + const char **value, size_t *value_len, const char **errmsg); + +/** + * @brief mmap(2) wrapper to map input files into memory to unify parsing. + * + * The address space is allocate only for reading. + * + * @param[in] ctx libyang context for logging + * @param[in] fd Open file descriptor of a file to map. + * @param[out] length Allocated size. + * @param[out] addr Address where the file is mapped. + * @return LY_ERR value. + */ +LY_ERR ly_mmap(struct ly_ctx *ctx, int fd, size_t *length, void **addr); + +/** + * @brief munmap(2) wrapper to free the memory mapped by ::ly_mmap() + * + * @param[in] addr Address where the input file is mapped. + * @param[in] length Allocated size of the address space. + * @return LY_ERR value. + */ +LY_ERR ly_munmap(void *addr, size_t length); + +/** + * @brief Concatenate formating string to the @p dest. + * + * @param[in, out] dest String to be concatenated by @p format. + * Note that the input string can be reallocated during concatenation. + * @param[in] format Formating string (as for printf) which is supposed to be added after @p dest. + * @return LY_SUCCESS or LY_EMEM. + */ +LY_ERR ly_strcat(char **dest, const char *format, ...); + +#ifndef _WIN32 +# define PATH_SEPARATOR ":" +#else +# define PATH_SEPARATOR ";" +#endif + +#endif /* LY_COMMON_H_ */ diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..af3d1f0 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,74 @@ +/** + * @file config.h + * @author Radek Krejci + * @brief Various variables provided by cmake and compile time options. + * + * Copyright (c) 2021 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_CONFIG_H_ +#define LY_CONFIG_H_ + +#ifdef _WIN32 +/* headers are broken on Windows, which means that some of them simply *have* to come first */ +# include +# include +#endif + +/** size of fixed_mem in lyd_value, minimum is 8 (B) */ +#define LYD_VALUE_FIXED_MEM_SIZE @LYD_VALUE_SIZE@ + +/** plugins */ +#define LYPLG_SUFFIX "@CMAKE_SHARED_MODULE_SUFFIX@" +#define LYPLG_SUFFIX_LEN (sizeof LYPLG_SUFFIX - 1) +#define LYPLG_TYPE_DIR "@PLUGINS_DIR_TYPES@" +#define LYPLG_EXT_DIR "@PLUGINS_DIR_EXTENSIONS@" + +/** atomic compiler operations, to be able to use uint32_t */ +#ifndef _WIN32 +# define LY_ATOMIC_INC_BARRIER(var) __sync_fetch_and_add(&(var), 1) +# define LY_ATOMIC_DEC_BARRIER(var) __sync_fetch_and_sub(&(var), 1) +#else +# include +# define LY_ATOMIC_INC_BARRIER(var) InterlockedExchangeAdd(&(var), 1) +# define LY_ATOMIC_DEC_BARRIER(var) InterlockedExchangeAdd(&(var), -1) +#endif + +/** printf compiler attribute */ +#ifdef __GNUC__ +# define _FORMAT_PRINTF(FORM, ARGS) __attribute__((format (printf, FORM, ARGS))) +#else +# define _FORMAT_PRINTF(FORM, ARGS) +#endif + +/** Exporting symbols to a shared library and importing back afterwards + * + * - use LIBYANG_API_DECL to mark a declaration in the public header + * - use LIBYANG_API_DEF to mark a definition (in the source code for the actual implementaiton) + * */ +#ifdef _MSC_VER +# ifndef STATIC +# define LIBYANG_API_DEF __declspec(dllexport) +# ifdef LIBYANG_BUILD +# define LIBYANG_API_DECL __declspec(dllexport) +# else +# define LIBYANG_API_DECL __declspec(dllimport) +# endif +# endif +#else +/* + * If the compiler supports attribute to mark objects as hidden, mark all + * objects as hidden and export only objects explicitly marked to be part of + * the public API. + */ +# define LIBYANG_API_DEF __attribute__((visibility("default"))) +# define LIBYANG_API_DECL +#endif + +#endif /* LY_CONFIG_H_ */ diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000..47e63d4 --- /dev/null +++ b/src/context.c @@ -0,0 +1,1277 @@ +/** + * @file context.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Context implementations + * + * Copyright (c) 2015 - 2021 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 /* asprintf, strdup */ +#if defined (__NetBSD__) || defined (__OpenBSD__) +/* realpath */ +#define _XOPEN_SOURCE 1 +#define _XOPEN_SOURCE_EXTENDED 1 +#endif + +#include "context.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "hash_table.h" +#include "in.h" +#include "parser_data.h" +#include "plugins_internal.h" +#include "plugins_types.h" +#include "schema_compile.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_free.h" +#include "tree_schema_internal.h" + +#include "../models/ietf-datastores@2018-02-14.h" +#include "../models/ietf-inet-types@2013-07-15.h" +#include "../models/ietf-yang-library@2019-01-04.h" +#include "../models/ietf-yang-metadata@2016-08-05.h" +#include "../models/ietf-yang-schema-mount@2019-01-14.h" +#include "../models/ietf-yang-structure-ext@2020-06-17.h" +#include "../models/ietf-yang-types@2013-07-15.h" +#include "../models/yang@2022-06-16.h" +#define IETF_YANG_LIB_REV "2019-01-04" + +static struct internal_modules_s { + const char *name; + const char *revision; + const char *data; + ly_bool implemented; + LYS_INFORMAT format; +} internal_modules[] = { + {"ietf-yang-metadata", "2016-08-05", (const char *)ietf_yang_metadata_2016_08_05_yang, 0, LYS_IN_YANG}, + {"yang", "2022-06-16", (const char *)yang_2022_06_16_yang, 1, LYS_IN_YANG}, + {"ietf-inet-types", "2013-07-15", (const char *)ietf_inet_types_2013_07_15_yang, 0, LYS_IN_YANG}, + {"ietf-yang-types", "2013-07-15", (const char *)ietf_yang_types_2013_07_15_yang, 0, LYS_IN_YANG}, + {"ietf-yang-schema-mount", "2019-01-14", (const char *)ietf_yang_schema_mount_2019_01_14_yang, 1, LYS_IN_YANG}, + {"ietf-yang-structure-ext", "2020-06-17", (const char *)ietf_yang_structure_ext_2020_06_17_yang, 0, LYS_IN_YANG}, + /* ietf-datastores and ietf-yang-library must be right here at the end of the list! */ + {"ietf-datastores", "2018-02-14", (const char *)ietf_datastores_2018_02_14_yang, 1, LYS_IN_YANG}, + {"ietf-yang-library", IETF_YANG_LIB_REV, (const char *)ietf_yang_library_2019_01_04_yang, 1, LYS_IN_YANG} +}; + +#define LY_INTERNAL_MODS_COUNT sizeof(internal_modules) / sizeof(struct internal_modules_s) + +LIBYANG_API_DEF LY_ERR +ly_ctx_set_searchdir(struct ly_ctx *ctx, const char *search_dir) +{ + struct stat st; + char *new_dir = NULL; + + LY_CHECK_ARG_RET(ctx, ctx, LY_EINVAL); + + if (search_dir) { + new_dir = realpath(search_dir, NULL); + LY_CHECK_ERR_RET(!new_dir, + LOGERR(ctx, LY_ESYS, "Unable to use search directory \"%s\" (%s).", search_dir, strerror(errno)), + LY_EINVAL); + if (strcmp(search_dir, new_dir)) { + LOGVRB("Search directory string \"%s\" canonized to \"%s\".", search_dir, new_dir); + } + LY_CHECK_ERR_RET(access(new_dir, R_OK | X_OK), + LOGERR(ctx, LY_ESYS, "Unable to fully access search directory \"%s\" (%s).", new_dir, strerror(errno)); free(new_dir), + LY_EINVAL); + LY_CHECK_ERR_RET(stat(new_dir, &st), + LOGERR(ctx, LY_ESYS, "stat() failed for \"%s\" (%s).", new_dir, strerror(errno)); free(new_dir), + LY_ESYS); + LY_CHECK_ERR_RET(!S_ISDIR(st.st_mode), + LOGERR(ctx, LY_ESYS, "Given search directory \"%s\" is not a directory.", new_dir); free(new_dir), + LY_EINVAL); + /* avoid path duplication */ + for (uint32_t u = 0; u < ctx->search_paths.count; ++u) { + if (!strcmp(new_dir, ctx->search_paths.objs[u])) { + free(new_dir); + return LY_EEXIST; + } + } + if (ly_set_add(&ctx->search_paths, new_dir, 1, NULL)) { + free(new_dir); + return LY_EMEM; + } + + /* new searchdir - possibly more latest revision available */ + ly_ctx_reset_latests(ctx); + + return LY_SUCCESS; + } else { + /* consider that no change is not actually an error */ + return LY_SUCCESS; + } +} + +LIBYANG_API_DEF const char * const * +ly_ctx_get_searchdirs(const struct ly_ctx *ctx) +{ +#define LY_CTX_SEARCHDIRS_SIZE_STEP 8 + void **new; + + LY_CHECK_ARG_RET(ctx, ctx, NULL); + + if (ctx->search_paths.count == ctx->search_paths.size) { + /* not enough space for terminating NULL byte */ + new = realloc(((struct ly_ctx *)ctx)->search_paths.objs, + (ctx->search_paths.size + LY_CTX_SEARCHDIRS_SIZE_STEP) * sizeof *ctx->search_paths.objs); + LY_CHECK_ERR_RET(!new, LOGMEM(NULL), NULL); + ((struct ly_ctx *)ctx)->search_paths.size += LY_CTX_SEARCHDIRS_SIZE_STEP; + ((struct ly_ctx *)ctx)->search_paths.objs = new; + } + /* set terminating NULL byte to the strings list */ + ctx->search_paths.objs[ctx->search_paths.count] = NULL; + + return (const char * const *)ctx->search_paths.objs; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_unset_searchdir(struct ly_ctx *ctx, const char *value) +{ + LY_CHECK_ARG_RET(ctx, ctx, LY_EINVAL); + + if (!ctx->search_paths.count) { + return LY_SUCCESS; + } + + if (value) { + /* remove specific search directory */ + uint32_t index; + + for (index = 0; index < ctx->search_paths.count; ++index) { + if (!strcmp(value, ctx->search_paths.objs[index])) { + break; + } + } + if (index == ctx->search_paths.count) { + LOGARG(ctx, value); + return LY_EINVAL; + } else { + return ly_set_rm_index(&ctx->search_paths, index, free); + } + } else { + /* remove them all */ + ly_set_erase(&ctx->search_paths, free); + memset(&ctx->search_paths, 0, sizeof ctx->search_paths); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_unset_searchdir_last(struct ly_ctx *ctx, uint32_t count) +{ + LY_CHECK_ARG_RET(ctx, ctx, LY_EINVAL); + + for ( ; count > 0 && ctx->search_paths.count; --count) { + LY_CHECK_RET(ly_set_rm_index(&ctx->search_paths, ctx->search_paths.count - 1, free)) + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_load_module(struct ly_ctx *ctx, const char *name, const char *revision, const char **features) +{ + struct lys_module *mod = NULL; + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(ctx, ctx, name, NULL); + + /* load and parse */ + ret = lys_parse_load(ctx, name, revision, &ctx->unres.creating, &mod); + LY_CHECK_GOTO(ret, cleanup); + + /* implement */ + ret = _lys_set_implemented(mod, features, &ctx->unres); + LY_CHECK_GOTO(ret, cleanup); + + if (!(ctx->flags & LY_CTX_EXPLICIT_COMPILE)) { + /* create dep set for the module and mark all the modules that will be (re)compiled */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create(ctx, &ctx->unres.dep_sets, mod), cleanup); + + /* (re)compile the whole dep set (other dep sets will have no modules marked for compilation) */ + LY_CHECK_GOTO(ret = lys_compile_depset_all(ctx, &ctx->unres), cleanup); + + /* unres resolved */ + lys_unres_glob_erase(&ctx->unres); + } + +cleanup: + if (ret) { + lys_unres_glob_revert(ctx, &ctx->unres); + lys_unres_glob_erase(&ctx->unres); + mod = NULL; + } + return mod; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_new(const char *search_dir, uint16_t options, struct ly_ctx **new_ctx) +{ + struct ly_ctx *ctx = NULL; + struct lys_module *module; + char *search_dir_list, *sep, *dir; + const char **imp_f, *all_f[] = {"*", NULL}; + uint32_t i; + struct ly_in *in = NULL; + LY_ERR rc = LY_SUCCESS; + struct lys_glob_unres unres = {0}; + + LY_CHECK_ARG_RET(NULL, new_ctx, LY_EINVAL); + + ctx = calloc(1, sizeof *ctx); + LY_CHECK_ERR_GOTO(!ctx, LOGMEM(NULL); rc = LY_EMEM, cleanup); + + /* dictionary */ + lydict_init(&ctx->dict); + + /* plugins */ + LY_CHECK_ERR_GOTO(lyplg_init(), LOGINT(NULL); rc = LY_EINT, cleanup); + + /* initialize thread-specific keys */ + while ((pthread_key_create(&ctx->errlist_key, ly_err_free)) == EAGAIN) {} + + /* init LYB hash lock */ + pthread_mutex_init(&ctx->lyb_hash_lock, NULL); + + /* models list */ + ctx->flags = options; + if (search_dir) { + search_dir_list = strdup(search_dir); + LY_CHECK_ERR_GOTO(!search_dir_list, LOGMEM(NULL); rc = LY_EMEM, cleanup); + + for (dir = search_dir_list; (sep = strchr(dir, PATH_SEPARATOR[0])) != NULL && rc == LY_SUCCESS; dir = sep + 1) { + *sep = 0; + rc = ly_ctx_set_searchdir(ctx, dir); + if (rc == LY_EEXIST) { + /* ignore duplication */ + rc = LY_SUCCESS; + } + } + if (*dir && (rc == LY_SUCCESS)) { + rc = ly_ctx_set_searchdir(ctx, dir); + if (rc == LY_EEXIST) { + /* ignore duplication */ + rc = LY_SUCCESS; + } + } + free(search_dir_list); + + /* If ly_ctx_set_searchdir() failed, the error is already logged. Just exit */ + LY_CHECK_GOTO(rc, cleanup); + } + ctx->change_count = 1; + + if (!(options & LY_CTX_EXPLICIT_COMPILE)) { + /* use it for creating the initial context */ + ctx->flags |= LY_CTX_EXPLICIT_COMPILE; + } + + /* create dummy in */ + rc = ly_in_new_memory(internal_modules[0].data, &in); + LY_CHECK_GOTO(rc, cleanup); + + /* load internal modules */ + for (i = 0; i < ((options & LY_CTX_NO_YANGLIBRARY) ? (LY_INTERNAL_MODS_COUNT - 2) : LY_INTERNAL_MODS_COUNT); i++) { + ly_in_memory(in, internal_modules[i].data); + LY_CHECK_GOTO(rc = lys_parse_in(ctx, in, internal_modules[i].format, NULL, NULL, &unres.creating, &module), cleanup); + if (internal_modules[i].implemented || (ctx->flags & LY_CTX_ALL_IMPLEMENTED)) { + imp_f = (ctx->flags & LY_CTX_ENABLE_IMP_FEATURES) ? all_f : NULL; + LY_CHECK_GOTO(rc = lys_implement(module, imp_f, &unres), cleanup); + } + } + + if (!(options & LY_CTX_EXPLICIT_COMPILE)) { + /* compile now */ + LY_CHECK_GOTO(rc = ly_ctx_compile(ctx), cleanup); + ctx->flags &= ~LY_CTX_EXPLICIT_COMPILE; + } + +cleanup: + ly_in_free(in, 0); + lys_unres_glob_erase(&unres); + if (rc) { + ly_ctx_destroy(ctx); + } else { + *new_ctx = ctx; + } + return rc; +} + +static LY_ERR +ly_ctx_new_yl_legacy(struct ly_ctx *ctx, const struct lyd_node *yltree) +{ + struct lyd_node *module, *node; + struct ly_set *set; + const char **feature_arr = NULL; + const char *name = NULL, *revision = NULL; + struct ly_set features = {0}; + ly_bool imported = 0; + const struct lys_module *mod; + LY_ERR ret = LY_SUCCESS; + uint32_t i, j; + + LY_CHECK_RET(ret = lyd_find_xpath(yltree, "/ietf-yang-library:yang-library/modules-state/module", &set)); + + /* process the data tree */ + for (i = 0; i < set->count; ++i) { + module = set->dnodes[i]; + + /* initiate */ + revision = NULL; + name = NULL; + imported = 0; + + LY_LIST_FOR(lyd_child(module), node) { + if (!strcmp(node->schema->name, "name")) { + name = lyd_get_value(node); + } else if (!strcmp(node->schema->name, "revision")) { + revision = lyd_get_value(node); + } else if (!strcmp(node->schema->name, "feature")) { + LY_CHECK_GOTO(ret = ly_set_add(&features, node, 0, NULL), cleanup); + } else if (!strcmp(node->schema->name, "conformance-type") && + !strcmp(lyd_get_value(node), "import")) { + /* imported module - skip it, it will be loaded as a side effect + * of loading another module */ + imported = 1; + break; + } + } + + if (imported) { + continue; + } + + feature_arr = malloc((features.count + 1) * sizeof *feature_arr); + LY_CHECK_ERR_GOTO(!feature_arr, ret = LY_EMEM, cleanup); + + /* Parse features into an array of strings */ + for (j = 0; j < features.count; ++j) { + feature_arr[j] = lyd_get_value(features.dnodes[j]); + } + feature_arr[features.count] = NULL; + ly_set_clean(&features, free); + + /* use the gathered data to load the module */ + mod = ly_ctx_load_module(ctx, name, revision, feature_arr); + free(feature_arr); + if (!mod) { + LOGERR(ctx, LY_EINVAL, "Unable to load module specified by yang library data."); + ly_set_free(set, free); + return LY_EINVAL; + } + } + +cleanup: + ly_set_clean(&features, free); + ly_set_free(set, free); + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_new_ylpath(const char *search_dir, const char *path, LYD_FORMAT format, int options, struct ly_ctx **ctx) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_ctx *ctx_yl = NULL; + struct lyd_node *data_yl = NULL; + + LY_CHECK_ARG_RET(NULL, path, ctx, LY_EINVAL); + + /* create a seperate context for the data */ + LY_CHECK_GOTO(ret = ly_ctx_new(search_dir, 0, &ctx_yl), cleanup); + + /* parse yang library data tree */ + LY_CHECK_GOTO(ret = lyd_parse_data_path(ctx_yl, path, format, 0, LYD_VALIDATE_PRESENT, &data_yl), cleanup); + + /* create the new context */ + ret = ly_ctx_new_yldata(search_dir, data_yl, options, ctx); + +cleanup: + lyd_free_all(data_yl); + ly_ctx_destroy(ctx_yl); + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_new_ylmem(const char *search_dir, const char *data, LYD_FORMAT format, int options, struct ly_ctx **ctx) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_ctx *ctx_yl = NULL; + struct lyd_node *data_yl = NULL; + + LY_CHECK_ARG_RET(NULL, data, ctx, LY_EINVAL); + + /* create a seperate context for the data */ + LY_CHECK_GOTO(ret = ly_ctx_new(search_dir, 0, &ctx_yl), cleanup); + + /* parse yang library data tree */ + LY_CHECK_GOTO(ret = lyd_parse_data_mem(ctx_yl, data, format, 0, LYD_VALIDATE_PRESENT, &data_yl), cleanup); + + /* create the new context */ + ret = ly_ctx_new_yldata(search_dir, data_yl, options, ctx); + +cleanup: + lyd_free_all(data_yl); + ly_ctx_destroy(ctx_yl); + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_new_yldata(const char *search_dir, const struct lyd_node *tree, int options, struct ly_ctx **ctx) +{ + const char *name = NULL, *revision = NULL; + struct lyd_node *module, *node; + struct ly_set *set = NULL; + const char **feature_arr = NULL; + struct ly_set features = {0}; + LY_ERR ret = LY_SUCCESS; + const struct lys_module *mod; + struct ly_ctx *ctx_new = NULL; + ly_bool no_expl_compile = 0; + uint32_t i, j; + + LY_CHECK_ARG_RET(NULL, tree, ctx, LY_EINVAL); + + /* create a new context */ + if (*ctx == NULL) { + LY_CHECK_GOTO(ret = ly_ctx_new(search_dir, options, &ctx_new), cleanup); + } else { + ctx_new = *ctx; + } + + /* redundant to compile modules one-by-one */ + if (!(options & LY_CTX_EXPLICIT_COMPILE)) { + ctx_new->flags |= LY_CTX_EXPLICIT_COMPILE; + no_expl_compile = 1; + } + + LY_CHECK_GOTO(ret = lyd_find_xpath(tree, "/ietf-yang-library:yang-library/module-set[1]/module", &set), cleanup); + if (set->count == 0) { + /* perhaps a legacy data tree? */ + LY_CHECK_GOTO(ret = ly_ctx_new_yl_legacy(ctx_new, tree), cleanup); + } else { + /* process the data tree */ + for (i = 0; i < set->count; ++i) { + module = set->dnodes[i]; + + /* initiate */ + name = NULL; + revision = NULL; + + /* iterate over data */ + LY_LIST_FOR(lyd_child(module), node) { + if (!strcmp(node->schema->name, "name")) { + name = lyd_get_value(node); + } else if (!strcmp(node->schema->name, "revision")) { + revision = lyd_get_value(node); + } else if (!strcmp(node->schema->name, "feature")) { + LY_CHECK_GOTO(ret = ly_set_add(&features, node, 0, NULL), cleanup); + } + } + + feature_arr = malloc((features.count + 1) * sizeof *feature_arr); + LY_CHECK_ERR_GOTO(!feature_arr, ret = LY_EMEM, cleanup); + + /* parse features into an array of strings */ + for (j = 0; j < features.count; ++j) { + feature_arr[j] = lyd_get_value(features.dnodes[j]); + } + feature_arr[features.count] = NULL; + ly_set_clean(&features, NULL); + + /* use the gathered data to load the module */ + mod = ly_ctx_load_module(ctx_new, name, revision, feature_arr); + free(feature_arr); + if (!mod) { + LOGERR(*ctx ? *ctx : LYD_CTX(tree), LY_EINVAL, "Unable to load module %s@%s specified by yang library data.", + name, revision ? revision : ""); + ret = LY_EINVAL; + goto cleanup; + } + } + } + + /* compile */ + LY_CHECK_GOTO(ret = ly_ctx_compile(ctx_new), cleanup); + + if (no_expl_compile) { + /* unset flag */ + ctx_new->flags &= ~LY_CTX_EXPLICIT_COMPILE; + } + +cleanup: + ly_set_free(set, NULL); + ly_set_erase(&features, NULL); + if (*ctx == NULL) { + *ctx = ctx_new; + if (ret) { + ly_ctx_destroy(*ctx); + *ctx = NULL; + } + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_compile(struct ly_ctx *ctx) +{ + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(NULL, ctx, LY_EINVAL); + + /* create dep sets and mark all the modules that will be (re)compiled */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create(ctx, &ctx->unres.dep_sets, NULL), cleanup); + + /* (re)compile all the dep sets */ + LY_CHECK_GOTO(ret = lys_compile_depset_all(ctx, &ctx->unres), cleanup); + +cleanup: + if (ret) { + /* revert changes of modules */ + lys_unres_glob_revert(ctx, &ctx->unres); + } + lys_unres_glob_erase(&ctx->unres); + return ret; +} + +LIBYANG_API_DEF uint16_t +ly_ctx_get_options(const struct ly_ctx *ctx) +{ + LY_CHECK_ARG_RET(ctx, ctx, 0); + + return ctx->flags; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_set_options(struct ly_ctx *ctx, uint16_t option) +{ + LY_ERR lyrc = LY_SUCCESS; + struct lys_module *mod; + uint32_t i; + + LY_CHECK_ARG_RET(ctx, ctx, LY_EINVAL); + LY_CHECK_ERR_RET((option & LY_CTX_NO_YANGLIBRARY) && !(ctx->flags & LY_CTX_NO_YANGLIBRARY), + LOGARG(ctx, option), LY_EINVAL); + + if (!(ctx->flags & LY_CTX_SET_PRIV_PARSED) && (option & LY_CTX_SET_PRIV_PARSED)) { + ctx->flags |= LY_CTX_SET_PRIV_PARSED; + /* recompile the whole context to set the priv pointers */ + for (i = 0; i < ctx->list.count; ++i) { + mod = ctx->list.objs[i]; + if (mod->implemented) { + mod->to_compile = 1; + } + } + lyrc = ly_ctx_compile(ctx); + if (lyrc) { + ly_ctx_unset_options(ctx, LY_CTX_SET_PRIV_PARSED); + } + } + + /* set the option(s) */ + if (!lyrc) { + ctx->flags |= option; + } + + return lyrc; +} + +static LY_ERR +lysc_node_clear_priv_dfs_cb(struct lysc_node *node, void *UNUSED(data), ly_bool *UNUSED(dfs_continue)) +{ + node->priv = NULL; + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_unset_options(struct ly_ctx *ctx, uint16_t option) +{ + LY_ARRAY_COUNT_TYPE u, v; + const struct lysc_ext_instance *ext; + struct lysc_node *root; + + LY_CHECK_ARG_RET(ctx, ctx, LY_EINVAL); + LY_CHECK_ERR_RET(option & LY_CTX_NO_YANGLIBRARY, LOGARG(ctx, option), LY_EINVAL); + + if ((ctx->flags & LY_CTX_SET_PRIV_PARSED) && (option & LY_CTX_SET_PRIV_PARSED)) { + struct lys_module *mod; + uint32_t index; + + index = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &index))) { + if (!mod->compiled) { + continue; + } + + /* set NULL for all ::lysc_node.priv pointers in module */ + lysc_module_dfs_full(mod, lysc_node_clear_priv_dfs_cb, NULL); + + /* set NULL for all ::lysc_node.priv pointers in compiled extension instances */ + LY_ARRAY_FOR(mod->compiled->exts, u) { + ext = &mod->compiled->exts[u]; + LY_ARRAY_FOR(ext->substmts, v) { + if (ext->substmts[v].stmt & LY_STMT_DATA_NODE_MASK) { + LY_LIST_FOR(*(struct lysc_node **)ext->substmts[v].storage, root) { + lysc_tree_dfs_full(root, lysc_node_clear_priv_dfs_cb, NULL); + } + } + } + } + } + } + + /* unset the option(s) */ + ctx->flags &= ~option; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF uint16_t +ly_ctx_get_change_count(const struct ly_ctx *ctx) +{ + LY_CHECK_ARG_RET(ctx, ctx, 0); + + return ctx->change_count; +} + +LIBYANG_API_DEF ly_module_imp_clb +ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data) +{ + LY_CHECK_ARG_RET(ctx, ctx, NULL); + + if (user_data) { + *user_data = ctx->imp_clb_data; + } + return ctx->imp_clb; +} + +LIBYANG_API_DEF void +ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data) +{ + LY_CHECK_ARG_RET(ctx, ctx, ); + + ctx->imp_clb = clb; + ctx->imp_clb_data = user_data; +} + +LIBYANG_API_DEF ly_ext_data_clb +ly_ctx_set_ext_data_clb(struct ly_ctx *ctx, ly_ext_data_clb clb, void *user_data) +{ + ly_ext_data_clb prev; + + LY_CHECK_ARG_RET(ctx, ctx, NULL); + + prev = ctx->ext_clb; + ctx->ext_clb = clb; + ctx->ext_clb_data = user_data; + + return prev; +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module_iter(const struct ly_ctx *ctx, uint32_t *index) +{ + LY_CHECK_ARG_RET(ctx, ctx, index, NULL); + + if (*index < ctx->list.count) { + return ctx->list.objs[(*index)++]; + } else { + return NULL; + } +} + +/** + * @brief Iterate over the modules in the given context. Returned modules must match the given key at the offset of + * lysp_module and lysc_module structures (they are supposed to be placed at the same offset in both structures). + * + * @param[in] ctx Context where to iterate. + * @param[in] key Key value to search for. + * @param[in] key_size Optional length of the @p key. If zero, NULL-terminated key is expected. + * @param[in] key_offset Key's offset in struct lys_module to get value from the context's modules to match with the key. + * @param[in,out] Iterator to pass between the function calls. On the first call, the variable is supposed to be + * initiated to 0. After each call returning a module, the value is greater by 1 than the index of the returned + * module in the context. + * @return Module matching the given key, NULL if no such module found. + */ +static struct lys_module * +ly_ctx_get_module_by_iter(const struct ly_ctx *ctx, const char *key, size_t key_size, size_t key_offset, uint32_t *index) +{ + struct lys_module *mod; + const char *value; + + for ( ; *index < ctx->list.count; ++(*index)) { + mod = ctx->list.objs[*index]; + value = *(const char **)(((int8_t *)(mod)) + key_offset); + if ((!key_size && !strcmp(key, value)) || (key_size && !strncmp(key, value, key_size) && (value[key_size] == '\0'))) { + /* increment index for the next run */ + ++(*index); + return mod; + } + } + /* done */ + return NULL; +} + +/** + * @brief Unifying function for ly_ctx_get_module() and ly_ctx_get_module_ns() + * @param[in] ctx Context where to search. + * @param[in] key Name or Namespace as a search key. + * @param[in] key_offset Key's offset in struct lys_module to get value from the context's modules to match with the key. + * @param[in] revision Revision date to match. If NULL, the matching module must have no revision. To search for the latest + * revision module, use ly_ctx_get_module_latest_by(). + * @return Matching module if any. + */ +static struct lys_module * +ly_ctx_get_module_by(const struct ly_ctx *ctx, const char *key, size_t key_offset, const char *revision) +{ + struct lys_module *mod; + uint32_t index = 0; + + while ((mod = ly_ctx_get_module_by_iter(ctx, key, 0, key_offset, &index))) { + if (!revision) { + if (!mod->revision) { + /* found requested module without revision */ + return mod; + } + } else { + if (mod->revision && !strcmp(mod->revision, revision)) { + /* found requested module of the specific revision */ + return mod; + } + } + } + + return NULL; +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module_ns(const struct ly_ctx *ctx, const char *ns, const char *revision) +{ + LY_CHECK_ARG_RET(ctx, ctx, ns, NULL); + return ly_ctx_get_module_by(ctx, ns, offsetof(struct lys_module, ns), revision); +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module(const struct ly_ctx *ctx, const char *name, const char *revision) +{ + LY_CHECK_ARG_RET(ctx, ctx, name, NULL); + return ly_ctx_get_module_by(ctx, name, offsetof(struct lys_module, name), revision); +} + +/** + * @brief Unifying function for ly_ctx_get_module_latest() and ly_ctx_get_module_latest_ns() + * @param[in] ctx Context where to search. + * @param[in] key Name or Namespace as a search key. + * @param[in] key_offset Key's offset in struct lys_module to get value from the context's modules to match with the key. + * @return Matching module if any. + */ +static struct lys_module * +ly_ctx_get_module_latest_by(const struct ly_ctx *ctx, const char *key, size_t key_offset) +{ + struct lys_module *mod; + uint32_t index = 0; + + while ((mod = ly_ctx_get_module_by_iter(ctx, key, 0, key_offset, &index))) { + if (mod->latest_revision & LYS_MOD_LATEST_REV) { + return mod; + } + } + + return NULL; +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module_latest(const struct ly_ctx *ctx, const char *name) +{ + LY_CHECK_ARG_RET(ctx, ctx, name, NULL); + return ly_ctx_get_module_latest_by(ctx, name, offsetof(struct lys_module, name)); +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module_latest_ns(const struct ly_ctx *ctx, const char *ns) +{ + LY_CHECK_ARG_RET(ctx, ctx, ns, NULL); + return ly_ctx_get_module_latest_by(ctx, ns, offsetof(struct lys_module, ns)); +} + +/** + * @brief Unifying function for ly_ctx_get_module_implemented() and ly_ctx_get_module_implemented_ns() + * @param[in] ctx Context where to search. + * @param[in] key Name or Namespace as a search key. + * @param[in] key_size Optional length of the @p key. If zero, NULL-terminated key is expected. + * @param[in] key_offset Key's offset in struct lys_module to get value from the context's modules to match with the key. + * @return Matching module if any. + */ +static struct lys_module * +ly_ctx_get_module_implemented_by(const struct ly_ctx *ctx, const char *key, size_t key_size, size_t key_offset) +{ + struct lys_module *mod; + uint32_t index = 0; + + while ((mod = ly_ctx_get_module_by_iter(ctx, key, key_size, key_offset, &index))) { + if (mod->implemented) { + return mod; + } + } + + return NULL; +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module_implemented(const struct ly_ctx *ctx, const char *name) +{ + LY_CHECK_ARG_RET(ctx, ctx, name, NULL); + return ly_ctx_get_module_implemented_by(ctx, name, 0, offsetof(struct lys_module, name)); +} + +struct lys_module * +ly_ctx_get_module_implemented2(const struct ly_ctx *ctx, const char *name, size_t name_len) +{ + LY_CHECK_ARG_RET(ctx, ctx, name, NULL); + return ly_ctx_get_module_implemented_by(ctx, name, name_len, offsetof(struct lys_module, name)); +} + +LIBYANG_API_DEF struct lys_module * +ly_ctx_get_module_implemented_ns(const struct ly_ctx *ctx, const char *ns) +{ + LY_CHECK_ARG_RET(ctx, ctx, ns, NULL); + return ly_ctx_get_module_implemented_by(ctx, ns, 0, offsetof(struct lys_module, ns)); +} + +/** + * @brief Try to find a submodule in a module. + * + * @param[in] module Module where to search in. + * @param[in] submodule Name of the submodule to find. + * @param[in] revision Revision of the submodule to find. NULL for submodule with no revision. + * @param[in] latest Ignore @p revision and look for the latest revision. + * @return Pointer to the specified submodule if it is present in the context. + */ +static const struct lysp_submodule * +_ly_ctx_get_submodule2(const struct lys_module *module, const char *submodule, const char *revision, ly_bool latest) +{ + struct lysp_include *inc; + LY_ARRAY_COUNT_TYPE u; + + LY_CHECK_ARG_RET(NULL, module, module->parsed, submodule, NULL); + + LY_ARRAY_FOR(module->parsed->includes, u) { + if (module->parsed->includes[u].submodule && !strcmp(submodule, module->parsed->includes[u].submodule->name)) { + inc = &module->parsed->includes[u]; + + if (latest && inc->submodule->latest_revision) { + /* latest revision */ + return inc->submodule; + } else if (!revision && !inc->submodule->revs) { + /* no revision */ + return inc->submodule; + } else if (revision && inc->submodule->revs && !strcmp(revision, inc->submodule->revs[0].date)) { + /* specific revision */ + return inc->submodule; + } + } + } + + return NULL; +} + +/** + * @brief Try to find a submodule in the context. + * + * @param[in] ctx Context where to search in. + * @param[in] submodule Name of the submodule to find. + * @param[in] revision Revision of the submodule to find. NULL for submodule with no revision. + * @param[in] latest Ignore @p revision and look for the latest revision. + * @return Pointer to the specified submodule if it is present in the context. + */ +static const struct lysp_submodule * +_ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *submodule, const char *revision, ly_bool latest) +{ + const struct lys_module *mod; + const struct lysp_submodule *submod = NULL; + uint32_t v; + + LY_CHECK_ARG_RET(ctx, ctx, submodule, NULL); + + for (v = 0; v < ctx->list.count; ++v) { + mod = ctx->list.objs[v]; + if (!mod->parsed) { + continue; + } + + submod = _ly_ctx_get_submodule2(mod, submodule, revision, latest); + if (submod) { + break; + } + } + + return submod; +} + +LIBYANG_API_DEF const struct lysp_submodule * +ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *submodule, const char *revision) +{ + return _ly_ctx_get_submodule(ctx, submodule, revision, 0); +} + +LIBYANG_API_DEF const struct lysp_submodule * +ly_ctx_get_submodule_latest(const struct ly_ctx *ctx, const char *submodule) +{ + return _ly_ctx_get_submodule(ctx, submodule, NULL, 1); +} + +LIBYANG_API_DEF const struct lysp_submodule * +ly_ctx_get_submodule2(const struct lys_module *module, const char *submodule, const char *revision) +{ + return _ly_ctx_get_submodule2(module, submodule, revision, 0); +} + +LIBYANG_API_DEF const struct lysp_submodule * +ly_ctx_get_submodule2_latest(const struct lys_module *module, const char *submodule) +{ + return _ly_ctx_get_submodule2(module, submodule, NULL, 1); +} + +LIBYANG_API_DEF void +ly_ctx_reset_latests(struct ly_ctx *ctx) +{ + struct lys_module *mod; + + for (uint32_t v = 0; v < ctx->list.count; ++v) { + mod = ctx->list.objs[v]; + mod->latest_revision &= ~(LYS_MOD_LATEST_SEARCHDIRS | LYS_MOD_LATEST_IMPCLB); + if (mod->parsed && mod->parsed->includes) { + for (LY_ARRAY_COUNT_TYPE u = 0; u < LY_ARRAY_COUNT(mod->parsed->includes); ++u) { + mod->parsed->includes[u].submodule->latest_revision &= ~(LYS_MOD_LATEST_SEARCHDIRS | LYS_MOD_LATEST_IMPCLB); + } + } + } +} + +LIBYANG_API_DEF uint32_t +ly_ctx_internal_modules_count(const struct ly_ctx *ctx) +{ + if (!ctx) { + return 0; + } + + if (ctx->flags & LY_CTX_NO_YANGLIBRARY) { + return LY_INTERNAL_MODS_COUNT - 2; + } else { + return LY_INTERNAL_MODS_COUNT; + } +} + +static LY_ERR +ylib_feature(struct lyd_node *parent, const struct lysp_module *pmod) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_feature *f; + + if (!pmod->mod->implemented) { + /* no features can be enabled */ + return LY_SUCCESS; + } + + LY_ARRAY_FOR(pmod->features, struct lysp_feature, f) { + if (!(f->flags & LYS_FENABLED)) { + continue; + } + + LY_CHECK_RET(lyd_new_term(parent, NULL, "feature", f->name, 0, NULL)); + } + + LY_ARRAY_FOR(pmod->includes, u) { + LY_ARRAY_FOR(pmod->includes[u].submodule->features, struct lysp_feature, f) { + if (!(f->flags & LYS_FENABLED)) { + continue; + } + + LY_CHECK_RET(lyd_new_term(parent, NULL, "feature", f->name, 0, NULL)); + } + } + + return LY_SUCCESS; +} + +static LY_ERR +ylib_deviation(struct lyd_node *parent, const struct lys_module *cur_mod, ly_bool bis) +{ + LY_ARRAY_COUNT_TYPE i; + struct lys_module *mod; + + if (!cur_mod->implemented) { + /* no deviations of the module for certain */ + return LY_SUCCESS; + } + + LY_ARRAY_FOR(cur_mod->deviated_by, i) { + mod = cur_mod->deviated_by[i]; + + if (bis) { + LY_CHECK_RET(lyd_new_term(parent, NULL, "deviation", mod->name, 0, NULL)); + } else { + LY_CHECK_RET(lyd_new_list(parent, NULL, "deviation", 0, NULL, mod->name, + (mod->parsed->revs ? mod->parsed->revs[0].date : ""))); + } + } + + return LY_SUCCESS; +} + +static LY_ERR +ylib_submodules(struct lyd_node *parent, const struct lysp_module *pmod, ly_bool bis) +{ + LY_ERR ret; + LY_ARRAY_COUNT_TYPE i; + struct lyd_node *cont; + struct lysp_submodule *submod; + int r; + char *str; + + LY_ARRAY_FOR(pmod->includes, i) { + submod = pmod->includes[i].submodule; + + if (bis) { + LY_CHECK_RET(lyd_new_list(parent, NULL, "submodule", 0, &cont, submod->name)); + + if (submod->revs) { + LY_CHECK_RET(lyd_new_term(cont, NULL, "revision", submod->revs[0].date, 0, NULL)); + } + } else { + LY_CHECK_RET(lyd_new_list(parent, NULL, "submodule", 0, &cont, submod->name, + (submod->revs ? submod->revs[0].date : ""))); + } + + if (submod->filepath) { + r = asprintf(&str, "file://%s", submod->filepath); + LY_CHECK_ERR_RET(r == -1, LOGMEM(pmod->mod->ctx), LY_EMEM); + + ret = lyd_new_term(cont, NULL, bis ? "location" : "schema", str, 0, NULL); + free(str); + LY_CHECK_RET(ret); + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_ctx_get_yanglib_data(const struct ly_ctx *ctx, struct lyd_node **root_p, const char *content_id_format, ...) +{ + LY_ERR ret; + uint32_t i; + ly_bool bis = 0; + int r; + char *str; + const struct lys_module *mod; + struct lyd_node *root = NULL, *root_bis = NULL, *cont, *set_bis = NULL; + va_list ap; + + LY_CHECK_ARG_RET(ctx, ctx, root_p, LY_EINVAL); + + mod = ly_ctx_get_module_implemented(ctx, "ietf-yang-library"); + LY_CHECK_ERR_RET(!mod, LOGERR(ctx, LY_EINVAL, "Module \"ietf-yang-library\" is not implemented."), LY_EINVAL); + + if (mod->parsed->revs && !strcmp(mod->parsed->revs[0].date, "2016-06-21")) { + bis = 0; + } else if (mod->parsed->revs && !strcmp(mod->parsed->revs[0].date, IETF_YANG_LIB_REV)) { + bis = 1; + } else { + LOGERR(ctx, LY_EINVAL, "Incompatible ietf-yang-library version in context."); + return LY_EINVAL; + } + + LY_CHECK_GOTO(ret = lyd_new_inner(NULL, mod, "modules-state", 0, &root), error); + + if (bis) { + LY_CHECK_GOTO(ret = lyd_new_inner(NULL, mod, "yang-library", 0, &root_bis), error); + LY_CHECK_GOTO(ret = lyd_new_list(root_bis, NULL, "module-set", 0, &set_bis, "complete"), error); + } + + for (i = 0; i < ctx->list.count; ++i) { + mod = ctx->list.objs[i]; + if (!mod->parsed) { + LOGERR(ctx, LY_ENOTFOUND, "Parsed module \"%s\" missing in the context.", mod->name); + goto error; + } + + /* + * deprecated legacy + */ + LY_CHECK_GOTO(ret = lyd_new_list(root, NULL, "module", 0, &cont, mod->name, + (mod->parsed->revs ? mod->parsed->revs[0].date : "")), error); + + /* schema */ + if (mod->filepath) { + r = asprintf(&str, "file://%s", mod->filepath); + LY_CHECK_ERR_GOTO(r == -1, LOGMEM(ctx); ret = LY_EMEM, error); + + ret = lyd_new_term(cont, NULL, "schema", str, 0, NULL); + free(str); + LY_CHECK_GOTO(ret, error); + } + + /* namespace */ + LY_CHECK_GOTO(ret = lyd_new_term(cont, NULL, "namespace", mod->ns, 0, NULL), error); + + /* feature leaf-list */ + LY_CHECK_GOTO(ret = ylib_feature(cont, mod->parsed), error); + + /* deviation list */ + LY_CHECK_GOTO(ret = ylib_deviation(cont, mod, 0), error); + + /* conformance-type */ + LY_CHECK_GOTO(ret = lyd_new_term(cont, NULL, "conformance-type", mod->implemented ? "implement" : "import", 0, + NULL), error); + + /* submodule list */ + LY_CHECK_GOTO(ret = ylib_submodules(cont, mod->parsed, 0), error); + + /* + * current revision + */ + if (bis) { + /* name and revision */ + if (mod->implemented) { + LY_CHECK_GOTO(ret = lyd_new_list(set_bis, NULL, "module", 0, &cont, mod->name), error); + + if (mod->parsed->revs) { + LY_CHECK_GOTO(ret = lyd_new_term(cont, NULL, "revision", mod->parsed->revs[0].date, 0, NULL), error); + } + } else { + LY_CHECK_GOTO(ret = lyd_new_list(set_bis, NULL, "import-only-module", 0, &cont, mod->name, + (mod->parsed->revs ? mod->parsed->revs[0].date : "")), error); + } + + /* namespace */ + LY_CHECK_GOTO(ret = lyd_new_term(cont, NULL, "namespace", mod->ns, 0, NULL), error); + + /* location */ + if (mod->filepath) { + r = asprintf(&str, "file://%s", mod->filepath); + LY_CHECK_ERR_GOTO(r == -1, LOGMEM(ctx); ret = LY_EMEM, error); + + ret = lyd_new_term(cont, NULL, "location", str, 0, NULL); + free(str); + LY_CHECK_GOTO(ret, error); + } + + /* submodule list */ + LY_CHECK_GOTO(ret = ylib_submodules(cont, mod->parsed, 1), error); + + /* feature list */ + LY_CHECK_GOTO(ret = ylib_feature(cont, mod->parsed), error); + + /* deviation */ + LY_CHECK_GOTO(ret = ylib_deviation(cont, mod, 1), error); + } + } + + /* IDs */ + va_start(ap, content_id_format); + r = vasprintf(&str, content_id_format, ap); + va_end(ap); + LY_CHECK_ERR_GOTO(r == -1, LOGMEM(ctx); ret = LY_EMEM, error); + ret = lyd_new_term(root, NULL, "module-set-id", str, 0, NULL); + LY_CHECK_ERR_GOTO(ret, free(str), error); + + if (bis) { + /* create one complete schema */ + LY_CHECK_ERR_GOTO(ret = lyd_new_list(root_bis, NULL, "schema", 0, &cont, "complete"), free(str), error); + + LY_CHECK_ERR_GOTO(ret = lyd_new_term(cont, NULL, "module-set", "complete", 0, NULL), free(str), error); + + /* content-id */ + LY_CHECK_ERR_GOTO(ret = lyd_new_term(root_bis, NULL, "content-id", str, 0, NULL), free(str), error); + } + free(str); + + if (root_bis) { + if (lyd_insert_sibling(root, root_bis, &root)) { + goto error; + } + root_bis = NULL; + } + + LY_CHECK_GOTO(ret = lyd_validate_all(&root, NULL, LYD_VALIDATE_PRESENT, NULL), error); + + *root_p = root; + return LY_SUCCESS; + +error: + lyd_free_all(root); + lyd_free_all(root_bis); + return ret; +} + +LIBYANG_API_DEF void +ly_ctx_destroy(struct ly_ctx *ctx) +{ + struct lysf_ctx fctx = {.ctx = ctx}; + + if (!ctx) { + return; + } + + /* models list */ + for ( ; ctx->list.count; ctx->list.count--) { + fctx.mod = ctx->list.objs[ctx->list.count - 1]; + + /* remove the module */ + if (fctx.mod->implemented) { + fctx.mod->implemented = 0; + lysc_module_free(&fctx, fctx.mod->compiled); + fctx.mod->compiled = NULL; + } + lys_module_free(&fctx, fctx.mod, 0); + } + free(ctx->list.objs); + + /* free extensions */ + lysf_ctx_erase(&fctx); + + /* search paths list */ + ly_set_erase(&ctx->search_paths, free); + + /* leftover unres */ + lys_unres_glob_erase(&ctx->unres); + + /* clean the error list */ + ly_err_clean(ctx, 0); + pthread_key_delete(ctx->errlist_key); + + /* dictionary */ + lydict_clean(&ctx->dict); + + /* LYB hash lock */ + pthread_mutex_destroy(&ctx->lyb_hash_lock); + + /* plugins - will be removed only if this is the last context */ + lyplg_clean(); + + free(ctx); +} diff --git a/src/context.h b/src/context.h new file mode 100644 index 0000000..331a89f --- /dev/null +++ b/src/context.h @@ -0,0 +1,668 @@ +/** + * @file context.h + * @author Radek Krejci + * @brief internal context structures and functions + * + * Copyright (c) 2015 - 2020 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_CONTEXT_H_ +#define LY_CONTEXT_H_ + +#include + +#include "log.h" +#include "parser_schema.h" +#include "tree_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct lys_module; + +/** + * @page howtoContext Context + * + * The context concept allows callers to work in environments with different sets of YANG modules. + * + * The first step with libyang is to create a new context using ::ly_ctx_new(). It returns a handler used in the following work. + * Note that the context is supposed to provide a stable environment for work with the data. Therefore the caller should prepare + * a complete context and after starting working with the data, the context and its content should not change. If it does, + * in most cases it leads to the context being recompiled and any parsed data invalid. Despite the API not enforcing this + * approach, it may change in future versions in the form of a locking mechanism which would allow further + * optimization of data manipulation. Also note that modules cannot be removed from their context. If you need to change the set + * of the schema modules in the context, the recommended way is to create a new context. To remove the context, there is ::ly_ctx_destroy() function. + * + * The context has [several options](@ref contextoptions) changing behavior when processing YANG modules being inserted. The + * specific behavior is mentioned below. All the options can be set as a parameter when the context is being created or later + * with ::ly_ctx_set_options(). + * + * When creating a new context, another optional parameter is search_dir It provide directory where libyang + * will automatically search for YANG modules being imported or included. There is actually a set of search paths which can be later + * modified using ::ly_ctx_set_searchdir(), ::ly_ctx_unset_searchdir() and ::ly_ctx_unset_searchdir_last() functions. Before the values + * in the set are used, also the current working directory is (non-recursively) searched. For the case of the explicitly set + * search directories, they are searched recursively - all their subdirectories (and symlinks) are taken into account. Searching + * in the current working directory can be avoided with the context's ::LY_CTX_DISABLE_SEARCHDIR_CWD option. + * Searching in all the context's search dirs (without removing them) can be avoided with the context's + * ::LY_CTX_DISABLE_SEARCHDIRS option (or via ::ly_ctx_set_options()). This automatic searching can be preceded + * by a custom module searching callback (::ly_module_imp_clb) set via ::ly_ctx_set_module_imp_clb(). The algorithm of + * searching in search dirs is also available via API as ::lys_search_localfile() function. + * + * YANG modules are added into the context using [parser functions](@ref howtoSchemaParsers) - \b lys_parse*(). + * Alternatively, also ::ly_ctx_load_module() can be used - in that case the ::ly_module_imp_clb or automatic + * search in search directories and in the current working directory is used, as described above. YANG submodules cannot be loaded + * or even validated directly, they are loaded always only as includes of YANG modules. Explicitly parsed/loaded modules are + * handled as implemented - libyang is able to instantiate data representing such a module. The modules loaded implicitly, are + * not implemented and serve only as a source of grouping or typedef definitions. Context can hold multiple revisions of the same + * YANG module, but only one of them can be implemented. Details about the difference between implemented and imported modules + * can be found on @ref howtoSchema page. This behavior can be changed with the context's ::LY_CTX_ALL_IMPLEMENTED option, which + * causes that all the parsed modules, whether loaded explicitly or implicitly, are set to be implemented. Note, that as + * a consequence of this option, only a single revision of any module can be present in the context in this case. Also, a less + * crude option ::LY_CTX_REF_IMPLEMENTED can be used to implement only referenced modules that should also be implemented. + * + * When loading/importing a module without revision, the latest revision of the required module is supposed to load. + * For a context, the first time the latest revision of a module is requested, it is properly searched for and loaded. + * However, when this module is requested (without revision) the second time, the one found previously is returned. + * This has the advantage of not searching for the module repeatedly but there is a drawback in case the content of search + * directories is updated and a later revision become available. + * + * Context holds all the schema modules internally. To get a specific module, use ::ly_ctx_get_module() (or some of its + * variants). If you need to do something with all the modules in the context, it is advised to iterate over them using + * ::ly_ctx_get_module_iter(). Alternatively, the ::ly_ctx_get_yanglib_data() function can be used to get complex information about the schemas in the context + * in the form of data tree defined by ietf-yang-library module. + * + * YANG data can be parsed by \b lyd_parse_*() functions. Note, that functions for schema have \b lys_ + * prefix (or \b lysp_ for the parsed and \b lysc_ for the compiled schema - for details see @ref howtoSchema page) while + * functions for instance data have \b lyd_ prefix. Details about data formats or handling data without the appropriate + * YANG module in context can be found on @ref howtoData page. + * + * Besides the YANG modules, context holds also [error information](@ref howtoErrors) and + * [database of strings](@ref howtoContextDict), both connected with the processed YANG modules and data. + * + * - @subpage howtoErrors + * - @subpage howtoContextDict + * + * \note API for this group of functions is available in the [context module](@ref context). + * + * Functions List + * -------------- + * + * - ::ly_ctx_new() + * - ::ly_ctx_destroy() + * + * - ::ly_ctx_set_searchdir() + * - ::ly_ctx_get_searchdirs() + * - ::ly_ctx_unset_searchdir() + * - ::ly_ctx_unset_searchdir_last() + * + * - ::ly_ctx_set_options() + * - ::ly_ctx_get_options() + * - ::ly_ctx_unset_options() + * + * - ::ly_ctx_set_module_imp_clb() + * - ::ly_ctx_get_module_imp_clb() + * + * - ::ly_ctx_load_module() + * - ::ly_ctx_get_module_iter() + * - ::ly_ctx_get_module() + * - ::ly_ctx_get_module_ns() + * - ::ly_ctx_get_module_implemented() + * - ::ly_ctx_get_module_implemented_ns() + * - ::ly_ctx_get_module_latest() + * - ::ly_ctx_get_module_latest_ns() + * - ::ly_ctx_get_submodule() + * - ::ly_ctx_get_submodule_latest() + * - ::ly_ctx_get_submodule2() + * - ::ly_ctx_get_submodule2_latest() + * - ::ly_ctx_reset_latests() + * + * - ::ly_ctx_get_yanglib_data() + * + * - ::ly_ctx_get_change_count() + * - ::ly_ctx_internal_modules_count() + * + * - ::lys_search_localfile() + * - ::lys_set_implemented() + * + */ + +/** + * @defgroup context Context + * @{ + * + * Structures and functions to manipulate with the libyang context containers. + * + * The \em context concept allows callers to work in environments with different sets of YANG schemas. + * More detailed information can be found at @ref howtoContext page. + */ + +/** + * @struct ly_ctx + * @brief libyang context handler. + */ +struct ly_ctx; + +/** + * @ingroup context + * @defgroup contextoptions Context options + * + * Options to change context behavior. + * + * @{ + */ + +#define LY_CTX_ALL_IMPLEMENTED 0x01 /**< All the imported modules of the schema being parsed are implemented. */ +#define LY_CTX_REF_IMPLEMENTED 0x02 /**< Implement all imported modules "referenced" from an implemented module. + Normally, leafrefs, augment and deviation targets are implemented as + specified by YANG 1.1. In addition to this, implement any modules of + nodes referenced by when and must conditions and by any default values. + Generally, only if all these modules are implemented, the explicitly + implemented modules can be properly used and instantiated in data. */ +#define LY_CTX_NO_YANGLIBRARY 0x04 /**< Do not internally implement ietf-yang-library module. The option + causes that function ::ly_ctx_get_yanglib_data() does not work (returns ::LY_EINVAL) until + the ietf-yang-library module is loaded manually. While any revision + of this schema can be loaded with this option, note that the only + revisions implemented by ::ly_ctx_get_yanglib_data() are 2016-06-21 and 2019-01-04. + This option cannot be changed on existing context. */ +#define LY_CTX_DISABLE_SEARCHDIRS 0x08 /**< Do not search for schemas in context's searchdirs neither in current + working directory. It is entirely skipped and the only way to get + schema data for imports or for ::ly_ctx_load_module() is to use the + callbacks provided by caller via ::ly_ctx_set_module_imp_clb() */ +#define LY_CTX_DISABLE_SEARCHDIR_CWD 0x10 /**< Do not automatically search for schemas in current working + directory, which is by default searched automatically (despite not + recursively). */ +#define LY_CTX_PREFER_SEARCHDIRS 0x20 /**< When searching for schema, prefer searchdirs instead of user callback. */ +#define LY_CTX_SET_PRIV_PARSED 0x40 /**< For all compiled nodes, their private objects (::lysc_node.priv) are used + by libyang as a reference to the corresponding parsed node (::lysp_node). + The exception are \"case\" statements, which are omitted (shorthand), + in that case the private objects are set to NULL. + So if this option is set, the user must not change private objects. + Setting this option by ::ly_ctx_set_options() may result in context recompilation. + Resetting this option by ::ly_ctx_unset_options() cause that private + objects will be set to NULL. */ +#define LY_CTX_EXPLICIT_COMPILE 0x80 /**< If this flag is set, the compiled modules and their schema nodes are + not automatically updated (compiled) on any context changes. In other words, they do + not immediately take effect. To do that, call ::ly_ctx_compile(). Changes + requiring compilation include adding new modules, changing their features, + and implementing parsed-only modules. This option allows efficient compiled + context creation without redundant recompilations. */ +#define LY_CTX_ENABLE_IMP_FEATURES 0x0100 /**< By default, all features of newly implemented imported modules of + a module that is being loaded are disabled. With this flag they all become + enabled. */ + +/** @} contextoptions */ + +/** + * @brief Create libyang context. + * + * Context is used to hold all information about schemas. Usually, the application is supposed + * to work with a single context in which libyang is holding all schemas (and other internal + * information) according to which the data trees will be processed and validated. So, the schema + * trees are tightly connected with the specific context and they are held by the context internally + * - caller does not need to keep pointers to the schemas returned by ::lys_parse(), context knows + * about them. The data trees created with \b lyd_parse_*() are still connected with the specific context, + * but they are not internally held by the context. The data tree just points and lean on some data + * held by the context (schema tree, string dictionary, etc.). Therefore, in case of data trees, caller + * is supposed to keep pointers returned by the \b lyd_parse_*() functions and manage the data tree on its own. This + * also affects the number of instances of both tree types. While you can have only one instance of + * specific schema connected with a single context, number of data tree instances is not connected. + * + * @param[in] search_dir Directory (or directories) where libyang will search for the imported or included modules + * and submodules. If no such directory is available, NULL is accepted. Several directories can be specified, + * delimited by colon ":" (on Windows, use semicolon ";" instead). + * @param[in] options Context options, see @ref contextoptions. + * @param[out] new_ctx Pointer to the created libyang context if LY_SUCCESS returned. + * @return LY_ERR return value. + */ +LIBYANG_API_DECL LY_ERR ly_ctx_new(const char *search_dir, uint16_t options, struct ly_ctx **new_ctx); + +/** + * @brief Create libyang context according to the provided yang-library data in a file. + * + * This function loads the yang-library data from the given path. If you need to pass the data as + * string, use ::::ly_ctx_new_ylmem(). Both functions extend functionality of ::ly_ctx_new() by loading + * modules specified in the ietf-yang-library form into the context being created. + * The preferred tree model revision is 2019-01-04. However, only the first module-set is processed and loaded + * into the context. If there are no matching nodes from this tree, the legacy tree (originally from model revision 2016-04-09) + * is processed. Note, that the modules are loaded the same way as in case of ::ly_ctx_load_module(), so the schema paths in the + * yang-library data are ignored and the modules are loaded from the context's search locations. On the other hand, YANG features + * of the modules are set as specified in the yang-library data. + * To get yang library data from a libyang context, use ::ly_ctx_get_yanglib_data(). + * + * @param[in] search_dir Directory where libyang will search for the imported or included modules and submodules. + * If no such directory is available, NULL is accepted. + * @param[in] path Path to the file containing yang-library-data in the specified format + * @param[in] format Format of the data in the provided file. + * @param[in] options Context options, see @ref contextoptions. + * @param[in,out] ctx If *ctx is not NULL, the existing libyang context is modified. Otherwise, a pointer to a + * newly created context is returned here if LY_SUCCESS. + * @return LY_ERR return value + */ +LIBYANG_API_DECL LY_ERR ly_ctx_new_ylpath(const char *search_dir, const char *path, LYD_FORMAT format, int options, + struct ly_ctx **ctx); + +/** + * @brief Create libyang context according to the provided yang-library data in a string. + * + * Details in ::ly_ctx_new_ylpath(). + * + * @param[in] search_dir Directory where libyang will search for the imported or included modules and submodules. + * If no such directory is available, NULL is accepted. + * @param[in] data String containing yang-library data in the specified format. + * @param[in] format Format of the data in the provided file. + * @param[in] options Context options, see @ref contextoptions. + * @param[in,out] ctx If *ctx is not NULL, the existing libyang context is modified. Otherwise, a pointer to a + * newly created context is returned here if LY_SUCCESS. + * @return LY_ERR return value + */ +LIBYANG_API_DECL LY_ERR ly_ctx_new_ylmem(const char *search_dir, const char *data, LYD_FORMAT format, int options, + struct ly_ctx **ctx); + +/** + * @brief Create libyang context according to the provided yang-library data in a data tree. + * + * Details in ::ly_ctx_new_ylpath(). + * + * @param[in] search_dir Directory where libyang will search for the imported or included modules and submodules. + * If no such directory is available, NULL is accepted. + * @param[in] tree Data tree containing yang-library data. + * @param[in] options Context options, see @ref contextoptions. + * @param[in,out] ctx If *ctx is not NULL, the existing libyang context is modified. Otherwise, a pointer to a + * newly created context is returned here if LY_SUCCESS. + * @return LY_ERR return value + */ +LIBYANG_API_DECL LY_ERR ly_ctx_new_yldata(const char *search_dir, const struct lyd_node *tree, int options, + struct ly_ctx **ctx); + +/** + * @brief Compile (recompile) the context applying all the performed changes after the last context compilation. + * Should be used only if ::LY_CTX_EXPLICIT_COMPILE option is set, has no effect otherwise. + * + * @param[in] ctx Context to compile. + * @return LY_ERR return value. + */ +LIBYANG_API_DECL LY_ERR ly_ctx_compile(struct ly_ctx *ctx); + +/** + * @brief Add the search path into libyang context + * + * To reset search paths set in the context, use ::ly_ctx_unset_searchdir() and then + * set search paths again. + * + * @param[in] ctx Context to be modified. + * @param[in] search_dir New search path to add to the current paths previously set in ctx. + * @return LY_ERR return value. + */ +LIBYANG_API_DECL LY_ERR ly_ctx_set_searchdir(struct ly_ctx *ctx, const char *search_dir); + +/** + * @brief Clean the search path(s) from the libyang context + * + * To remove the recently added search path(s), use ::ly_ctx_unset_searchdir_last(). + * + * @param[in] ctx Context to be modified. + * @param[in] value Searchdir to be removed, use NULL to remove them all. + * @return LY_ERR return value + */ +LIBYANG_API_DECL LY_ERR ly_ctx_unset_searchdir(struct ly_ctx *ctx, const char *value); + +/** + * @brief Remove the least recently added search path(s) from the libyang context. + * + * To remove a specific search path by its value, use ::ly_ctx_unset_searchdir(). + * + * @param[in] ctx Context to be modified. + * @param[in] count Number of the searchdirs to be removed (starting by the least recently added). + * If the value is higher then the actual number of search paths, all paths are removed and no error is returned. + * Value 0 does not change the search path set. + * @return LY_ERR return value + */ +LIBYANG_API_DECL LY_ERR ly_ctx_unset_searchdir_last(struct ly_ctx *ctx, uint32_t count); + +/** + * @brief Get the NULL-terminated list of the search paths in libyang context. Do not modify the result! + * + * @param[in] ctx Context to query. + * @return NULL-terminated list (array) of the search paths, NULL if no searchpath was set. + * Do not modify the provided data in any way! + */ +LIBYANG_API_DECL const char * const *ly_ctx_get_searchdirs(const struct ly_ctx *ctx); + +/** + * @brief Get the currently set context's options. + * + * @param[in] ctx Context to query. + * @return Combination of all the currently set context's options, see @ref contextoptions. + */ +LIBYANG_API_DECL uint16_t ly_ctx_get_options(const struct ly_ctx *ctx); + +/** + * @brief Set some of the context's options, see @ref contextoptions. + * @param[in] ctx Context to be modified. + * @param[in] option Combination of the context's options to be set, see @ref contextoptions. + * If there is to be a change to ::LY_CTX_SET_PRIV_PARSED, the context will be recompiled + * and all ::lysc_node.priv in the modules will be overwritten, see ::LY_CTX_SET_PRIV_PARSED. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR ly_ctx_set_options(struct ly_ctx *ctx, uint16_t option); + +/** + * @brief Unset some of the context's options, see @ref contextoptions. + * @param[in] ctx Context to be modified. + * @param[in] option Combination of the context's options to be unset, see @ref contextoptions. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR ly_ctx_unset_options(struct ly_ctx *ctx, uint16_t option); + +/** + * @brief Get the change count of the context (module set) during its life-time. + * + * @param[in] ctx Context to be examined. + * @return Context change count. + */ +LIBYANG_API_DECL uint16_t ly_ctx_get_change_count(const struct ly_ctx *ctx); + +/** + * @brief Callback for freeing returned module data in #ly_module_imp_clb. + * + * @param[in] module_data Data to free. + * @param[in] user_data User-supplied callback data, same as for #ly_module_imp_clb. + */ +typedef void (*ly_module_imp_data_free_clb)(void *module_data, void *user_data); + +/** + * @brief Callback for retrieving missing included or imported models in a custom way. + * + * When @p submod_name is provided, the submodule is requested instead of the module (in this case only + * the module name without its revision is provided). + * + * If an @arg free_module_data callback is provided, it will be used later to free the allegedly const data + * which were returned by this callback. + * + * @param[in] mod_name Missing module name. + * @param[in] mod_rev Optional missing module revision. If NULL and submod_name is not provided, the latest revision is + * requested, the parsed module is then marked by the latest_revision flag. + * @param[in] submod_name Optional missing submodule name. + * @param[in] submod_rev Optional missing submodule revision. If NULL and submod_name is provided, the latest revision is + * requested, the parsed submodule is then marked by the latest_revision flag. + * @param[in] user_data User-supplied callback data. + * @param[out] format Format of the returned module data. + * @param[out] module_data Requested module data. + * @param[out] free_module_data Callback for freeing the returned module data. If not set, the data will be left untouched. + * @return LY_ERR value. If the returned value differs from LY_SUCCESS, libyang continue in trying to get the module data + * according to the settings of its mechanism to search for the imported/included schemas. + */ +typedef LY_ERR (*ly_module_imp_clb)(const char *mod_name, const char *mod_rev, const char *submod_name, const char *submod_rev, + void *user_data, LYS_INFORMAT *format, const char **module_data, ly_module_imp_data_free_clb *free_module_data); + +/** + * @brief Get the custom callback for missing import/include module retrieval. + * + * @param[in] ctx Context to read from. + * @param[in] user_data Optional pointer for getting the user-supplied callback data. + * @return Callback or NULL if not set. + */ +LIBYANG_API_DECL ly_module_imp_clb ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data); + +/** + * @brief Set missing include or import module callback. It is meant to be used when the models + * are not locally available (such as when downloading modules from a NETCONF server), it should + * not be required in other cases. + * + * @param[in] ctx Context that will use this callback. + * @param[in] clb Callback responsible for returning the missing model. + * @param[in] user_data Arbitrary data that will always be passed to the callback @p clb. + */ +LIBYANG_API_DECL void ly_ctx_set_module_imp_clb(struct ly_ctx *ctx, ly_module_imp_clb clb, void *user_data); + +/** + * @brief Callback for getting arbitrary run-time data required by an extension instance. + * + * @param[in] ext Compiled extension instance. + * @param[in] user_data User-supplied callback data. + * @param[out] ext_data Provided extension instance data. + * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not. + * @return LY_ERR value. + */ +typedef LY_ERR (*ly_ext_data_clb)(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, + ly_bool *ext_data_free); + +/** + * @brief Set callback providing run-time extension instance data. The expected data depend on the extension. + * Data expected by internal extensions: + * + * - *ietf-yang-schema-mount:mount-point* (struct lyd_node \*\*ext_data)\n + * Operational data tree with at least `ietf-yang-library` data describing the mounted schema and + * `ietf-yang-schema-mount` **validated** data describing the specific mount point + * ([ref](https://datatracker.ietf.org/doc/html/rfc8528#section-3.3)). + * + * @param[in] ctx Context that will use this callback. + * @param[in] clb Callback responsible for returning the extension instance data. + * @param[in] user_data Arbitrary data that will always be passed to the callback @p clb. + */ +LIBYANG_API_DECL ly_ext_data_clb ly_ctx_set_ext_data_clb(struct ly_ctx *ctx, ly_ext_data_clb clb, void *user_data); + +/** + * @brief Get YANG module of the given name and revision. + * + * @param[in] ctx Context to work in. + * @param[in] name Name of the YANG module to get. + * @param[in] revision Requested revision date of the YANG module to get. If not specified, + * the schema with no revision is returned, if it is present in the context. + * @return Pointer to the YANG module, NULL if no schema in the context follows the name and revision requirements. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module(const struct ly_ctx *ctx, const char *name, const char *revision); + +/** + * @brief Get the latest revision of the YANG module specified by its name. + * + * YANG modules with no revision are supposed to be the oldest one. + * + * @param[in] ctx Context where to search. + * @param[in] name Name of the YANG module to get. + * @return The latest revision of the specified YANG module in the given context, NULL if no YANG module of the + * given name is present in the context. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module_latest(const struct ly_ctx *ctx, const char *name); + +/** + * @brief Get the (only) implemented YANG module specified by its name. + * + * @param[in] ctx Context where to search. + * @param[in] name Name of the YANG module to get. + * @return The only implemented YANG module revision of the given name in the given context. NULL if there is no + * implemented module of the given name. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module_implemented(const struct ly_ctx *ctx, const char *name); + +/** + * @brief Iterate over all modules in the given context. + * + * @param[in] ctx Context with the modules. + * @param[in,out] index Index of the next module to get. Value of 0 starts from the beginning. + * The value is updated with each call, so to iterate over all modules the same variable is supposed + * to be used in all calls starting with value 0. + * @return Next context module, NULL if the last was already returned. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module_iter(const struct ly_ctx *ctx, uint32_t *index); + +/** + * @brief Get YANG module of the given namespace and revision. + * + * @param[in] ctx Context to work in. + * @param[in] ns Namespace of the YANG module to get. + * @param[in] revision Requested revision date of the YANG module to get. If not specified, + * the schema with no revision is returned, if it is present in the context. + * @return Pointer to the YANG module, NULL if no schema in the context follows the namespace and revision requirements. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module_ns(const struct ly_ctx *ctx, const char *ns, const char *revision); + +/** + * @brief Get the latest revision of the YANG module specified by its namespace. + * + * YANG modules with no revision are supposed to be the oldest one. + * + * @param[in] ctx Context where to search. + * @param[in] ns Namespace of the YANG module to get. + * @return The latest revision of the specified YANG module in the given context, NULL if no YANG module of the + * given namespace is present in the context. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module_latest_ns(const struct ly_ctx *ctx, const char *ns); + +/** + * @brief Get the (only) implemented YANG module specified by its namespace. + * + * @param[in] ctx Context where to search. + * @param[in] ns Namespace of the YANG module to get. + * @return The only implemented YANG module revision of the given namespace in the given context. NULL if there is no + * implemented module of the given namespace. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_get_module_implemented_ns(const struct ly_ctx *ctx, const char *ns); + +/** + * @brief Get a specific submodule from context. If its belongs-to module is known, use ::ly_ctx_get_submodule2(). + * + * @param[in] ctx libyang context to search in. + * @param[in] submodule Submodule name to find. + * @param[in] revision Revision of the submodule to find, NULL for a submodule without a revision. + * @return Found submodule, NULL if there is none. + */ +LIBYANG_API_DECL const struct lysp_submodule *ly_ctx_get_submodule(const struct ly_ctx *ctx, const char *submodule, + const char *revision); + +/** + * @brief Get the latests revision of a submodule from context. If its belongs-to module is known, + * use ::ly_ctx_get_submodule2_latest(). + * + * @param[in] ctx libyang context to search in. + * @param[in] submodule Submodule name to find. + * @return Found submodule, NULL if there is none. + */ +LIBYANG_API_DECL const struct lysp_submodule *ly_ctx_get_submodule_latest(const struct ly_ctx *ctx, const char *submodule); + +/** + * @brief Get a specific submodule from a module. If the belongs-to module is not known, use ::ly_ctx_get_submodule(). + * + * @param[in] module Belongs-to module to search in. + * @param[in] submodule Submodule name to find. + * @param[in] revision Revision of the submodule to find, NULL for a submodule without a revision. + * @return Found submodule, NULL if there is none. + */ +LIBYANG_API_DECL const struct lysp_submodule *ly_ctx_get_submodule2(const struct lys_module *module, const char *submodule, + const char *revision); + +/** + * @brief Get the latest revision of a submodule from a module. If the belongs-to module is not known, + * use ::ly_ctx_get_submodule_latest(). + * + * @param[in] module Belongs-to module to search in. + * @param[in] submodule Submodule name to find. + * @return Found submodule, NULL if there is none. + */ +LIBYANG_API_DECL const struct lysp_submodule *ly_ctx_get_submodule2_latest(const struct lys_module *module, + const char *submodule); + +/** + * @brief Reset cached latest revision information of the schemas in the context. + * + * This function is deprecated and should not be used. + * + * When a (sub)module is imported/included without revision, the latest revision is + * searched. libyang searches for the latest revision in searchdirs and/or via provided + * import callback ::ly_module_imp_clb() just once. Then it is expected that the content + * of searchdirs or data returned by the callback does not change. So when it changes, + * it is necessary to force searching for the latest revision in case of loading another + * module, which what this function does. + * + * The latest revision information is also reset when the searchdirs set changes via + * ::ly_ctx_set_searchdir(). + * + * @param[in] ctx libyang context where the latest revision information is going to be reset. + */ +LIBYANG_API_DECL void ly_ctx_reset_latests(struct ly_ctx *ctx); + +/** + * @brief Learn the number of internal modules of a context. Internal modules + * is considered one that was loaded during the context creation. + * + * @param[in] ctx libyang context to examine. + * @return Number of internal modules. + */ +LIBYANG_API_DECL uint32_t ly_ctx_internal_modules_count(const struct ly_ctx *ctx); + +/** + * @brief Try to find the model in the searchpaths of \p ctx and load it into it. If custom missing + * module callback is set, it is used instead. + * + * The context itself is searched for the requested module first. If \p revision is not specified + * (the module of the latest revision is requested) and there is implemented revision of the requested + * module in the context, this implemented revision is returned despite there might be a newer revision. + * This behavior is cause by the fact that it is not possible to have multiple implemented revisions of + * the same module in the context. + * + * @param[in] ctx Context to add to. + * @param[in] name Name of the module to load. + * @param[in] revision Optional revision date of the module. If not specified, the latest revision is loaded. + * @param[in] features Optional array of features ended with NULL to be enabled if the module is being implemented. + * The feature string '*' enables all and array of length 1 with only the terminating NULL explicitly disables all + * the features. In case the parameter is NULL, the features are untouched - left disabled in newly loaded module or + * with the current features settings in case the module is already present in the context. + * @return Pointer to the data model structure, NULL if not found or some error occurred. + */ +LIBYANG_API_DECL struct lys_module *ly_ctx_load_module(struct ly_ctx *ctx, const char *name, const char *revision, + const char **features); + +/** + * @brief Get data of the internal ietf-yang-library module with information about all the loaded modules. + * ietf-yang-library module must be loaded. + * + * Note that "/ietf-yang-library:yang-library/datastore" list instances are not created and should be + * appended by the caller. There is a single "/ietf-yang-library:yang-library/schema" instance created + * with the key value "complete". + * + * If the data identifier can be limited to the existence and changes of this context, the following + * last 2 parameters can be used: + * + * "%u" as @p content_id_format and ::ly_ctx_get_change_count() as its parameter. + * + * @param[in] ctx Context with the modules. + * @param[out] root Generated yang-library data. + * @param[in] content_id_format Format string (printf-like) for the yang-library data identifier, which is + * the "content_id" node in the 2019-01-04 revision of ietf-yang-library. + * @param[in] ... Parameters for @p content_id_format. + * @return LY_ERR value + */ +LIBYANG_API_DECL LY_ERR ly_ctx_get_yanglib_data(const struct ly_ctx *ctx, struct lyd_node **root, + const char *content_id_format, ...); + +/** + * @brief Free all internal structures of the specified context. + * + * The function should be used before terminating the application to destroy + * and free all structures internally used by libyang. If the caller uses + * multiple contexts, the function should be called for each used context. + * + * All instance data are supposed to be freed before destroying the context using ::lyd_free_all(), for example. + * Data models (schemas) are destroyed automatically as part of ::ly_ctx_destroy() call. + * + * Note that the data stored by user into the ::lysc_node.priv pointer are kept + * untouched and the caller is responsible for freeing this private data. + * + * @param[in] ctx libyang context to destroy + */ +LIBYANG_API_DECL void ly_ctx_destroy(struct ly_ctx *ctx); + +/** @} context */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_CONTEXT_H_ */ diff --git a/src/dict.h b/src/dict.h new file mode 100644 index 0000000..cf897e7 --- /dev/null +++ b/src/dict.h @@ -0,0 +1,122 @@ +/** + * @file dict.h + * @author Radek Krejci + * @brief libyang dictionary + * + * Copyright (c) 2015-2018 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_DICT_H_ +#define LY_DICT_H_ + +#include +#include +#include + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* dummy context structure */ +struct ly_ctx; + +/** + * @page howtoContextDict Context Dictionary + * + * Context includes dictionary to store strings more effectively. The most of strings repeats quite often in schema + * as well as data trees. Therefore, instead of allocating those strings each time they appear, libyang stores them + * as records in the dictionary. The basic API to the context dictionary is public, so even a caller application can + * use the dictionary. + * + * To insert a string into the dictionary, caller can use ::lydict_insert() (adding a constant string) or + * ::lydict_insert_zc() (for dynamically allocated strings that won't be used by the caller after its insertion into + * the dictionary). Both functions provide the pointer to the inserted string in the dictionary record. + * + * To remove (reference of the) string from the context dictionary, ::lydict_remove() is supposed to be used. + * + * \note Incorrect usage of the dictionary can break libyang functionality. + * + * \note API for this group of functions is described in the [Dictionary module](@ref dict). + * + * Functions List + * -------------- + * - ::lydict_insert() + * - ::lydict_insert_zc() + * - ::lydict_remove() + */ + +/** + * @defgroup dict Dictionary + * @{ + * + * Publicly visible functions and values of the libyang dictionary. They provide + * access to the strings stored in the libyang context. More detailed information can be found at + * @ref howtoContextDict page. + */ + +/** + * @brief Insert string into dictionary. If the string is already present, + * only a reference counter is incremented and no memory allocation is + * performed. + * + * @param[in] ctx libyang context handler + * @param[in] value String to be stored in the dictionary. If NULL, function does nothing. + * @param[in] len Number of bytes to store. The value is not required to be + * NULL terminated string, the len parameter says number of bytes stored in + * dictionary. The specified number of bytes is duplicated and terminating NULL + * byte is added automatically. If \p len is 0, it is count automatically using strlen(). + * @param[out] str_p Optional parameter to get pointer to the string corresponding to the @p value and stored in dictionary. + * @return LY_SUCCESS in case of successful insertion into dictionary, note that the function does not return LY_EEXIST. + * @return LY_EINVAL in case of invalid input parameters. + * @return LY_EMEM in case of memory allocation failure. + */ +LIBYANG_API_DECL LY_ERR lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p); + +/** + * @brief Insert string into dictionary - zerocopy version. If the string is + * already present, only a reference counter is incremented and no memory + * allocation is performed. This insert function variant avoids duplication of + * specified value - it is inserted into the dictionary directly. + * + * @param[in] ctx libyang context handler + * @param[in] value NULL-terminated string to be stored in the dictionary. If + * the string is not present in dictionary, the pointer is directly used by the + * dictionary. Otherwise, the reference counter is incremented and the value is + * freed. So, after calling the function, caller is supposed to not use the + * value address anymore. If NULL, function does nothing. + * @param[out] str_p Optional parameter to get pointer to the string corresponding to the @p value and stored in dictionary. + * @return LY_SUCCESS in case of successful insertion into dictionary, note that the function does not return LY_EEXIST. + * @return LY_EINVAL in case of invalid input parameters. + * @return LY_EMEM in case of memory allocation failure. + */ +LIBYANG_API_DECL LY_ERR lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p); + +/** + * @brief Remove specified string from the dictionary. It decrement reference + * counter for the string and if it is zero, the string itself is freed. + * + * @param[in] ctx libyang context handler + * @param[in] value String to be freed. Note, that not only the string itself + * must match the stored value, but also the address is being compared and the + * counter is decremented only if it matches. If NULL, function does nothing. + * @return LY_SUCCESS if the value was found and removed (or refcount decreased). + * @return LY_ENOTFOUND if the value was not found. + * @return LY_ERR on other errors. + */ +LIBYANG_API_DECL LY_ERR lydict_remove(const struct ly_ctx *ctx, const char *value); + +/** @} dict */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_DICT_H_ */ diff --git a/src/diff.c b/src/diff.c new file mode 100644 index 0000000..edfcb34 --- /dev/null +++ b/src/diff.c @@ -0,0 +1,2151 @@ +/** + * @file diff.c + * @author Michal Vasko + * @brief diff functions + * + * Copyright (c) 2020 - 2021 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 /* asprintf, strdup */ + +#include "diff.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "log.h" +#include "plugins_exts.h" +#include "plugins_exts/metadata.h" +#include "plugins_types.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" + +#define LOGERR_META(ctx, meta_name, node) \ + { \ + char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \ + LOGERR(ctx, LY_EINVAL, "Failed to find metadata \"%s\" for node \"%s\".", meta_name, __path); \ + free(__path); \ + } + +#define LOGERR_NOINST(ctx, node) \ + { \ + char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \ + LOGERR(ctx, LY_EINVAL, "Failed to find node \"%s\" instance in data.", __path); \ + free(__path); \ + } + +#define LOGERR_UNEXPVAL(ctx, node, data_source) \ + { \ + char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \ + LOGERR(ctx, LY_EINVAL, "Unexpected value of node \"%s\" in %s.", __path, data_source); \ + free(__path); \ + } + +#define LOGERR_MERGEOP(ctx, node, src_op, trg_op) \ + { \ + char *__path = lyd_path(node, LYD_PATH_STD, NULL, 0); \ + LOGERR(ctx, LY_EINVAL, "Unable to merge operation \"%s\" with \"%s\" for node \"%s\".", \ + lyd_diff_op2str(trg_op), lyd_diff_op2str(src_op), __path); \ + free(__path); \ + } + +static const char * +lyd_diff_op2str(enum lyd_diff_op op) +{ + switch (op) { + case LYD_DIFF_OP_CREATE: + return "create"; + case LYD_DIFF_OP_DELETE: + return "delete"; + case LYD_DIFF_OP_REPLACE: + return "replace"; + case LYD_DIFF_OP_NONE: + return "none"; + } + + LOGINT(NULL); + return NULL; +} + +static enum lyd_diff_op +lyd_diff_str2op(const char *str) +{ + switch (str[0]) { + case 'c': + assert(!strcmp(str, "create")); + return LYD_DIFF_OP_CREATE; + case 'd': + assert(!strcmp(str, "delete")); + return LYD_DIFF_OP_DELETE; + case 'r': + assert(!strcmp(str, "replace")); + return LYD_DIFF_OP_REPLACE; + case 'n': + assert(!strcmp(str, "none")); + return LYD_DIFF_OP_NONE; + } + + LOGINT(NULL); + return 0; +} + +/** + * @brief Create diff metadata for a nested user-ordered node with the effective operation "create". + * + * @param[in] node User-rodered node to update. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_add_create_nested_userord(struct lyd_node *node) +{ + LY_ERR rc = LY_SUCCESS; + const char *meta_name, *meta_val; + size_t buflen = 0, bufused = 0; + uint32_t pos; + char *dyn = NULL; + + assert(lysc_is_userordered(node->schema)); + + /* get correct metadata name and value */ + if (lysc_is_dup_inst_list(node->schema)) { + meta_name = "yang:position"; + + pos = lyd_list_pos(node); + if (asprintf(&dyn, "%" PRIu32, pos) == -1) { + LOGMEM(LYD_CTX(node)); + rc = LY_EMEM; + goto cleanup; + } + meta_val = dyn; + } else if (node->schema->nodetype == LYS_LIST) { + meta_name = "yang:key"; + + if (node->prev->next && (node->prev->schema == node->schema)) { + LY_CHECK_GOTO(rc = lyd_path_list_predicate(node->prev, &dyn, &buflen, &bufused, 0), cleanup); + meta_val = dyn; + } else { + meta_val = ""; + } + } else { + meta_name = "yang:value"; + + if (node->prev->next && (node->prev->schema == node->schema)) { + meta_val = lyd_get_value(node->prev); + } else { + meta_val = ""; + } + } + + /* create the metadata */ + LY_CHECK_GOTO(rc = lyd_new_meta(NULL, node, NULL, meta_name, meta_val, 0, NULL), cleanup); + +cleanup: + free(dyn); + return rc; +} + +LY_ERR +lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value, + const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position, + struct lyd_node **diff) +{ + struct lyd_node *dup, *siblings, *match = NULL, *diff_parent = NULL, *elem; + const struct lyd_node *parent = NULL; + + assert(diff); + + /* replace leaf always needs orig-default and orig-value */ + assert((node->schema->nodetype != LYS_LEAF) || (op != LYD_DIFF_OP_REPLACE) || (orig_default && orig_value)); + + /* create on userord needs key/value */ + assert((node->schema->nodetype != LYS_LIST) || !(node->schema->flags & LYS_ORDBY_USER) || (op != LYD_DIFF_OP_CREATE) || + (lysc_is_dup_inst_list(node->schema) && position) || key); + assert((node->schema->nodetype != LYS_LEAFLIST) || !(node->schema->flags & LYS_ORDBY_USER) || + (op != LYD_DIFF_OP_CREATE) || (lysc_is_dup_inst_list(node->schema) && position) || value); + + /* move on userord needs both key and orig-key/value and orig-value */ + assert((node->schema->nodetype != LYS_LIST) || !(node->schema->flags & LYS_ORDBY_USER) || (op != LYD_DIFF_OP_REPLACE) || + (lysc_is_dup_inst_list(node->schema) && position && orig_position) || (key && orig_key)); + assert((node->schema->nodetype != LYS_LEAFLIST) || !(node->schema->flags & LYS_ORDBY_USER) || + (op != LYD_DIFF_OP_REPLACE) || (lysc_is_dup_inst_list(node->schema) && position && orig_position) || + (value && orig_value)); + + /* find the first existing parent */ + siblings = *diff; + while (1) { + /* find next node parent */ + parent = node; + while (parent->parent && (!diff_parent || (parent->parent->schema != diff_parent->schema))) { + parent = lyd_parent(parent); + } + if (parent == node) { + /* no more parents to find */ + break; + } + + /* check whether it exists in the diff */ + if (lyd_find_sibling_first(siblings, parent, &match)) { + break; + } + + /* another parent found */ + diff_parent = match; + + /* move down in the diff */ + siblings = lyd_child_no_keys(match); + } + + /* duplicate the subtree (and connect to the diff if possible) */ + LY_CHECK_RET(lyd_dup_single(node, (struct lyd_node_inner *)diff_parent, + LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup)); + + /* find the first duplicated parent */ + if (!diff_parent) { + diff_parent = lyd_parent(dup); + while (diff_parent && diff_parent->parent) { + diff_parent = lyd_parent(diff_parent); + } + } else { + diff_parent = dup; + while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) { + diff_parent = lyd_parent(diff_parent); + } + } + + /* no parent existed, must be manually connected */ + if (!diff_parent) { + /* there actually was no parent to duplicate */ + lyd_insert_sibling(*diff, dup, diff); + } else if (!diff_parent->parent) { + lyd_insert_sibling(*diff, diff_parent, diff); + } + + /* add parent operation, if any */ + if (diff_parent && (diff_parent != dup)) { + LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL)); + } + + /* add subtree operation */ + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:operation", lyd_diff_op2str(op), 0, NULL)); + + if (op == LYD_DIFF_OP_CREATE) { + /* all nested user-ordered (leaf-)lists need special metadata for create op */ + LYD_TREE_DFS_BEGIN(dup, elem) { + if ((elem != dup) && lysc_is_userordered(elem->schema)) { + LY_CHECK_RET(lyd_diff_add_create_nested_userord(elem)); + } + LYD_TREE_DFS_END(dup, elem); + } + } + + /* orig-default */ + if (orig_default) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-default", orig_default, 0, NULL)); + } + + /* orig-value */ + if (orig_value) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-value", orig_value, 0, NULL)); + } + + /* key */ + if (key) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:key", key, 0, NULL)); + } + + /* value */ + if (value) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:value", value, 0, NULL)); + } + + /* position */ + if (position) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:position", position, 0, NULL)); + } + + /* orig-key */ + if (orig_key) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-key", orig_key, 0, NULL)); + } + + /* orig-position */ + if (orig_position) { + LY_CHECK_RET(lyd_new_meta(NULL, dup, NULL, "yang:orig-position", orig_position, 0, NULL)); + } + + return LY_SUCCESS; +} + +/** + * @brief Get a userord entry for a specific user-ordered list/leaf-list. Create if does not exist yet. + * + * @param[in] first Node from the first tree, can be NULL (on create). + * @param[in] schema Schema node of the list/leaf-list. + * @param[in,out] userord Sized array of userord items. + * @return Userord item for all the user-ordered list/leaf-list instances. + */ +static struct lyd_diff_userord * +lyd_diff_userord_get(const struct lyd_node *first, const struct lysc_node *schema, struct lyd_diff_userord **userord) +{ + struct lyd_diff_userord *item; + struct lyd_node *iter; + const struct lyd_node **node; + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(*userord, u) { + if ((*userord)[u].schema == schema) { + return &(*userord)[u]; + } + } + + /* it was not added yet, add it now */ + LY_ARRAY_NEW_RET(schema->module->ctx, *userord, item, NULL); + + item->schema = schema; + item->pos = 0; + item->inst = NULL; + + /* store all the instance pointers in the current order */ + if (first) { + LYD_LIST_FOR_INST(lyd_first_sibling(first), first->schema, iter) { + LY_ARRAY_NEW_RET(schema->module->ctx, item->inst, node, NULL); + *node = iter; + } + } + + return item; +} + +/** + * @brief Get all the metadata to be stored in a diff for the 2 nodes. Can be used only for user-ordered + * lists/leaf-lists. + * + * @param[in] first Node from the first tree, can be NULL (on create). + * @param[in] second Node from the second tree, can be NULL (on delete). + * @param[in] options Diff options. + * @param[in] userord_item Userord item of @p first and/or @p second node. + * @param[out] op Operation. + * @param[out] orig_default Original default metadata. + * @param[out] value Value metadata. + * @param[out] orig_value Original value metadata + * @param[out] key Key metadata. + * @param[out] orig_key Original key metadata. + * @param[out] position Position metadata. + * @param[out] orig_position Original position metadata. + * @return LY_SUCCESS on success, + * @return LY_ENOT if there is no change to be added into diff, + * @return LY_ERR value on other errors. + */ +static LY_ERR +lyd_diff_userord_attrs(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, + struct lyd_diff_userord *userord_item, enum lyd_diff_op *op, const char **orig_default, char **value, + char **orig_value, char **key, char **orig_key, char **position, char **orig_position) +{ + LY_ERR rc = LY_SUCCESS; + const struct lysc_node *schema; + size_t buflen, bufused; + uint32_t first_pos, second_pos; + + assert(first || second); + + *orig_default = NULL; + *value = NULL; + *orig_value = NULL; + *key = NULL; + *orig_key = NULL; + *position = NULL; + *orig_position = NULL; + + schema = first ? first->schema : second->schema; + assert(lysc_is_userordered(schema)); + + /* find user-ordered first position */ + if (first) { + for (first_pos = 0; first_pos < LY_ARRAY_COUNT(userord_item->inst); ++first_pos) { + if (userord_item->inst[first_pos] == first) { + break; + } + } + assert(first_pos < LY_ARRAY_COUNT(userord_item->inst)); + } else { + first_pos = 0; + } + + /* prepare position of the next instance */ + second_pos = userord_item->pos++; + + /* learn operation first */ + if (!second) { + *op = LYD_DIFF_OP_DELETE; + } else if (!first) { + *op = LYD_DIFF_OP_CREATE; + } else { + if (lyd_compare_single(second, userord_item->inst[second_pos], 0)) { + /* in first, there is a different instance on the second position, we are going to move 'first' node */ + *op = LYD_DIFF_OP_REPLACE; + } else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) { + /* default flag change */ + *op = LYD_DIFF_OP_NONE; + } else { + /* no changes */ + return LY_ENOT; + } + } + + /* + * set each attribute correctly based on the operation and node type + */ + + /* orig-default */ + if ((schema->nodetype == LYS_LEAFLIST) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_NONE))) { + if (first->flags & LYD_DEFAULT) { + *orig_default = "true"; + } else { + *orig_default = "false"; + } + } + + /* value */ + if ((schema->nodetype == LYS_LEAFLIST) && !lysc_is_dup_inst_list(schema) && + ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_CREATE))) { + if (second_pos) { + *value = strdup(lyd_get_value(userord_item->inst[second_pos - 1])); + LY_CHECK_ERR_GOTO(!*value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } else { + *value = strdup(""); + LY_CHECK_ERR_GOTO(!*value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } + } + + /* orig-value */ + if ((schema->nodetype == LYS_LEAFLIST) && !lysc_is_dup_inst_list(schema) && + ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_DELETE))) { + if (first_pos) { + *orig_value = strdup(lyd_get_value(userord_item->inst[first_pos - 1])); + LY_CHECK_ERR_GOTO(!*orig_value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } else { + *orig_value = strdup(""); + LY_CHECK_ERR_GOTO(!*orig_value, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } + } + + /* key */ + if ((schema->nodetype == LYS_LIST) && !lysc_is_dup_inst_list(schema) && + ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_CREATE))) { + if (second_pos) { + buflen = bufused = 0; + LY_CHECK_GOTO(rc = lyd_path_list_predicate(userord_item->inst[second_pos - 1], key, &buflen, &bufused, 0), cleanup); + } else { + *key = strdup(""); + LY_CHECK_ERR_GOTO(!*key, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } + } + + /* orig-key */ + if ((schema->nodetype == LYS_LIST) && !lysc_is_dup_inst_list(schema) && + ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_DELETE))) { + if (first_pos) { + buflen = bufused = 0; + LY_CHECK_GOTO(rc = lyd_path_list_predicate(userord_item->inst[first_pos - 1], orig_key, &buflen, &bufused, 0), cleanup); + } else { + *orig_key = strdup(""); + LY_CHECK_ERR_GOTO(!*orig_key, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } + } + + /* position */ + if (lysc_is_dup_inst_list(schema) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_CREATE))) { + if (second_pos) { + if (asprintf(position, "%" PRIu32, second_pos) == -1) { + LOGMEM(schema->module->ctx); + rc = LY_EMEM; + goto cleanup; + } + } else { + *position = strdup(""); + LY_CHECK_ERR_GOTO(!*position, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } + } + + /* orig-position */ + if (lysc_is_dup_inst_list(schema) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_DELETE))) { + if (first_pos) { + if (asprintf(orig_position, "%" PRIu32, first_pos) == -1) { + LOGMEM(schema->module->ctx); + rc = LY_EMEM; + goto cleanup; + } + } else { + *orig_position = strdup(""); + LY_CHECK_ERR_GOTO(!*orig_position, LOGMEM(schema->module->ctx); rc = LY_EMEM, cleanup); + } + } + + /* + * update our instances - apply the change + */ + if (*op == LYD_DIFF_OP_CREATE) { + /* insert the instance */ + LY_ARRAY_CREATE_GOTO(schema->module->ctx, userord_item->inst, 1, rc, cleanup); + if (second_pos < LY_ARRAY_COUNT(userord_item->inst)) { + memmove(userord_item->inst + second_pos + 1, userord_item->inst + second_pos, + (LY_ARRAY_COUNT(userord_item->inst) - second_pos) * sizeof *userord_item->inst); + } + LY_ARRAY_INCREMENT(userord_item->inst); + userord_item->inst[second_pos] = second; + + } else if (*op == LYD_DIFF_OP_DELETE) { + /* remove the instance */ + if (first_pos + 1 < LY_ARRAY_COUNT(userord_item->inst)) { + memmove(userord_item->inst + first_pos, userord_item->inst + first_pos + 1, + (LY_ARRAY_COUNT(userord_item->inst) - first_pos - 1) * sizeof *userord_item->inst); + } + LY_ARRAY_DECREMENT(userord_item->inst); + + } else if (*op == LYD_DIFF_OP_REPLACE) { + /* move the instances */ + memmove(userord_item->inst + second_pos + 1, userord_item->inst + second_pos, + (first_pos - second_pos) * sizeof *userord_item->inst); + userord_item->inst[second_pos] = first; + } + +cleanup: + if (rc) { + free(*value); + *value = NULL; + free(*orig_value); + *orig_value = NULL; + free(*key); + *key = NULL; + free(*orig_key); + *orig_key = NULL; + free(*position); + *position = NULL; + free(*orig_position); + *orig_position = NULL; + } + return rc; +} + +/** + * @brief Get all the metadata to be stored in a diff for the 2 nodes. Cannot be used for user-ordered + * lists/leaf-lists. + * + * @param[in] first Node from the first tree, can be NULL (on create). + * @param[in] second Node from the second tree, can be NULL (on delete). + * @param[in] options Diff options. + * @param[out] op Operation. + * @param[out] orig_default Original default metadata. + * @param[out] orig_value Original value metadata. + * @return LY_SUCCESS on success, + * @return LY_ENOT if there is no change to be added into diff, + * @return LY_ERR value on other errors. + */ +static LY_ERR +lyd_diff_attrs(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, enum lyd_diff_op *op, + const char **orig_default, char **orig_value) +{ + const struct lysc_node *schema; + const char *str_val; + + assert(first || second); + + *orig_default = NULL; + *orig_value = NULL; + + schema = first ? first->schema : second->schema; + assert(!lysc_is_userordered(schema)); + + /* learn operation first */ + if (!second) { + *op = LYD_DIFF_OP_DELETE; + } else if (!first) { + *op = LYD_DIFF_OP_CREATE; + } else { + switch (schema->nodetype) { + case LYS_CONTAINER: + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + /* no changes */ + return LY_ENOT; + case LYS_LIST: + case LYS_LEAFLIST: + if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) { + /* default flag change */ + *op = LYD_DIFF_OP_NONE; + } else { + /* no changes */ + return LY_ENOT; + } + break; + case LYS_LEAF: + case LYS_ANYXML: + case LYS_ANYDATA: + if (lyd_compare_single(first, second, 0)) { + /* different values */ + *op = LYD_DIFF_OP_REPLACE; + } else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) { + /* default flag change */ + *op = LYD_DIFF_OP_NONE; + } else { + /* no changes */ + return LY_ENOT; + } + break; + default: + LOGINT_RET(schema->module->ctx); + } + } + + /* + * set each attribute correctly based on the operation and node type + */ + + /* orig-default */ + if ((schema->nodetype & LYD_NODE_TERM) && ((*op == LYD_DIFF_OP_REPLACE) || (*op == LYD_DIFF_OP_NONE))) { + if (first->flags & LYD_DEFAULT) { + *orig_default = "true"; + } else { + *orig_default = "false"; + } + } + + /* orig-value */ + if ((schema->nodetype & (LYS_LEAF | LYS_ANYDATA)) && (*op == LYD_DIFF_OP_REPLACE)) { + if (schema->nodetype == LYS_LEAF) { + str_val = lyd_get_value(first); + *orig_value = strdup(str_val ? str_val : ""); + LY_CHECK_ERR_RET(!*orig_value, LOGMEM(schema->module->ctx), LY_EMEM); + } else { + LY_CHECK_RET(lyd_any_value_str(first, orig_value)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Find a matching instance of a node in a data tree. + * + * @param[in] siblings Siblings to search in. + * @param[in] target Target node to search for. + * @param[in] defaults Whether to consider (or ignore) default values. + * @param[in,out] dup_inst_cache Duplicate instance cache. + * @param[out] match Found match, NULL if no matching node found. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_find_match(const struct lyd_node *siblings, const struct lyd_node *target, ly_bool defaults, + struct lyd_dup_inst **dup_inst_cache, struct lyd_node **match) +{ + LY_ERR r; + + if (target->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { + /* try to find the exact instance */ + r = lyd_find_sibling_first(siblings, target, match); + } else { + /* try to simply find the node, there cannot be more instances */ + r = lyd_find_sibling_val(siblings, target->schema, NULL, 0, match); + } + if (r && (r != LY_ENOTFOUND)) { + return r; + } + + /* update match as needed */ + LY_CHECK_RET(lyd_dup_inst_next(match, siblings, dup_inst_cache)); + + if (*match && ((*match)->flags & LYD_DEFAULT) && !defaults) { + /* ignore default nodes */ + *match = NULL; + } + return LY_SUCCESS; +} + +/** + * @brief Perform diff for all siblings at certain depth, recursively. + * + * For user-ordered lists/leaf-lists a specific structure is used for storing + * the current order. The idea is to apply all the generated diff changes + * virtually on the first tree so that we can continue to generate correct + * changes after some were already generated. + * + * The algorithm then uses second tree position-based changes with a before + * (preceding) item anchor. + * + * Example: + * + * Virtual first tree leaf-list order: + * 1 2 [3] 4 5 + * + * Second tree leaf-list order: + * 1 2 [5] 3 4 + * + * We are at the 3rd node now. We look at whether the nodes on the 3rd position + * match - they do not - move nodes so that the 3rd position node is final -> + * -> move node 5 to the 3rd position -> move node 5 after node 2. + * + * Required properties: + * Stored operations (move) should not be affected by later operations - + * - would cause a redundantly long list of operations, possibly inifinite. + * + * Implemenation justification: + * First, all delete operations and only then move/create operations are stored. + * Also, preceding anchor is used and after each iteration another node is + * at its final position. That results in the invariant that all preceding + * nodes are final and will not be changed by the later operations, meaning + * they can safely be used as anchors for the later operations. + * + * @param[in] first First tree first sibling. + * @param[in] second Second tree first sibling. + * @param[in] options Diff options. + * @param[in] nosiblings Whether to skip following siblings. + * @param[in,out] diff Diff to append to. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_siblings_r(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, ly_bool nosiblings, + struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + const struct lyd_node *iter_first, *iter_second; + struct lyd_node *match_second, *match_first; + struct lyd_diff_userord *userord = NULL, *userord_item; + struct lyd_dup_inst *dup_inst_first = NULL, *dup_inst_second = NULL; + LY_ARRAY_COUNT_TYPE u; + enum lyd_diff_op op; + const char *orig_default; + char *orig_value, *key, *value, *position, *orig_key, *orig_position; + + /* compare first tree to the second tree - delete, replace, none */ + LY_LIST_FOR(first, iter_first) { + if (!iter_first->schema) { + continue; + } + + assert(!(iter_first->schema->flags & LYS_KEY)); + if ((iter_first->flags & LYD_DEFAULT) && !(options & LYD_DIFF_DEFAULTS)) { + /* skip default nodes */ + continue; + } + + /* find a match in the second tree */ + LY_CHECK_GOTO(ret = lyd_diff_find_match(second, iter_first, options & LYD_DIFF_DEFAULTS, &dup_inst_second, + &match_second), cleanup); + + if (lysc_is_userordered(iter_first->schema)) { + /* get (create) userord entry */ + userord_item = lyd_diff_userord_get(iter_first, iter_first->schema, &userord); + LY_CHECK_ERR_GOTO(!userord_item, LOGMEM(LYD_CTX(iter_first)); ret = LY_EMEM, cleanup); + + /* we are handling only user-ordered node delete now */ + if (!match_second) { + /* get all the attributes */ + LY_CHECK_GOTO(ret = lyd_diff_userord_attrs(iter_first, match_second, options, userord_item, &op, + &orig_default, &value, &orig_value, &key, &orig_key, &position, &orig_position), cleanup); + + /* there must be changes, it is deleted */ + assert(op == LYD_DIFF_OP_DELETE); + ret = lyd_diff_add(iter_first, op, orig_default, orig_value, key, value, position, orig_key, + orig_position, diff); + + free(orig_value); + free(key); + free(value); + free(position); + free(orig_key); + free(orig_position); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* get all the attributes */ + ret = lyd_diff_attrs(iter_first, match_second, options, &op, &orig_default, &orig_value); + + /* add into diff if there are any changes */ + if (!ret) { + if (op == LYD_DIFF_OP_DELETE) { + ret = lyd_diff_add(iter_first, op, orig_default, orig_value, NULL, NULL, NULL, NULL, NULL, diff); + } else { + assert(match_second); + ret = lyd_diff_add(match_second, op, orig_default, orig_value, NULL, NULL, NULL, NULL, NULL, diff); + } + + free(orig_value); + LY_CHECK_GOTO(ret, cleanup); + } else if (ret == LY_ENOT) { + ret = LY_SUCCESS; + } else { + goto cleanup; + } + } + + /* check descendants, if any, recursively */ + if (match_second) { + LY_CHECK_GOTO(ret = lyd_diff_siblings_r(lyd_child_no_keys(iter_first), lyd_child_no_keys(match_second), + options, 0, diff), cleanup); + } + + if (nosiblings) { + break; + } + } + + /* reset all cached positions */ + LY_ARRAY_FOR(userord, u) { + userord[u].pos = 0; + } + + /* compare second tree to the first tree - create, user-ordered move */ + LY_LIST_FOR(second, iter_second) { + if (!iter_second->schema) { + continue; + } + + assert(!(iter_second->schema->flags & LYS_KEY)); + if ((iter_second->flags & LYD_DEFAULT) && !(options & LYD_DIFF_DEFAULTS)) { + /* skip default nodes */ + continue; + } + + /* find a match in the first tree */ + LY_CHECK_GOTO(ret = lyd_diff_find_match(first, iter_second, options & LYD_DIFF_DEFAULTS, &dup_inst_first, + &match_first), cleanup); + + if (lysc_is_userordered(iter_second->schema)) { + /* get userord entry */ + userord_item = lyd_diff_userord_get(match_first, iter_second->schema, &userord); + LY_CHECK_ERR_GOTO(!userord_item, LOGMEM(LYD_CTX(iter_second)); ret = LY_EMEM, cleanup); + + /* get all the attributes */ + ret = lyd_diff_userord_attrs(match_first, iter_second, options, userord_item, &op, &orig_default, + &value, &orig_value, &key, &orig_key, &position, &orig_position); + + /* add into diff if there are any changes */ + if (!ret) { + ret = lyd_diff_add(iter_second, op, orig_default, orig_value, key, value, position, orig_key, + orig_position, diff); + + free(orig_value); + free(key); + free(value); + free(position); + free(orig_key); + free(orig_position); + LY_CHECK_GOTO(ret, cleanup); + } else if (ret == LY_ENOT) { + ret = LY_SUCCESS; + } else { + goto cleanup; + } + } else if (!match_first) { + /* get all the attributes */ + LY_CHECK_GOTO(ret = lyd_diff_attrs(match_first, iter_second, options, &op, &orig_default, &orig_value), cleanup); + + /* there must be changes, it is created */ + assert(op == LYD_DIFF_OP_CREATE); + ret = lyd_diff_add(iter_second, op, orig_default, orig_value, NULL, NULL, NULL, NULL, NULL, diff); + + free(orig_value); + LY_CHECK_GOTO(ret, cleanup); + } /* else was handled */ + + if (nosiblings) { + break; + } + } + +cleanup: + lyd_dup_inst_free(dup_inst_first); + lyd_dup_inst_free(dup_inst_second); + LY_ARRAY_FOR(userord, u) { + LY_ARRAY_FREE(userord[u].inst); + } + LY_ARRAY_FREE(userord); + return ret; +} + +static LY_ERR +lyd_diff(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, ly_bool nosiblings, + struct lyd_node **diff) +{ + const struct ly_ctx *ctx; + + LY_CHECK_ARG_RET(NULL, diff, LY_EINVAL); + + if (first) { + ctx = LYD_CTX(first); + } else if (second) { + ctx = LYD_CTX(second); + } else { + ctx = NULL; + } + + if (first && second && (lysc_data_parent(first->schema) != lysc_data_parent(second->schema))) { + LOGERR(ctx, LY_EINVAL, "Invalid arguments - cannot create diff for unrelated data (%s()).", __func__); + return LY_EINVAL; + } + + *diff = NULL; + + return lyd_diff_siblings_r(first, second, options, nosiblings, diff); +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_tree(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, struct lyd_node **diff) +{ + return lyd_diff(first, second, options, 1, diff); +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_siblings(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, struct lyd_node **diff) +{ + return lyd_diff(first, second, options, 0, diff); +} + +/** + * @brief Learn operation of a diff node. + * + * @param[in] diff_node Diff node. + * @param[out] op Operation. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op) +{ + struct lyd_meta *meta = NULL; + const struct lyd_node *diff_parent; + const char *str; + char *path; + + for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) { + LY_LIST_FOR(diff_parent->meta, meta) { + if (!strcmp(meta->name, "operation") && !strcmp(meta->annotation->module->name, "yang")) { + str = lyd_get_meta_value(meta); + if ((str[0] == 'r') && (diff_parent != diff_node)) { + /* we do not care about this operation if it's in our parent */ + continue; + } + *op = lyd_diff_str2op(str); + break; + } + } + if (meta) { + break; + } + } + + if (!meta) { + path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0); + LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path); + free(path); + return LY_EINT; + } + + return LY_SUCCESS; +} + +/** + * @brief Insert a diff node into a data tree. + * + * @param[in,out] first_node First sibling of the data tree. + * @param[in] parent_node Data tree sibling parent node. + * @param[in] new_node Node to insert. + * @param[in] userord_anchor Optional anchor (key, value, or position) of relative (leaf-)list instance. If not set, + * the user-ordered instance will be inserted at the first position. + * @return err_info, NULL on success. + */ +static LY_ERR +lyd_diff_insert(struct lyd_node **first_node, struct lyd_node *parent_node, struct lyd_node *new_node, + const char *userord_anchor) +{ + LY_ERR ret; + struct lyd_node *anchor; + uint32_t pos, anchor_pos; + int found; + + assert(new_node); + + if (!*first_node) { + if (!parent_node) { + /* no parent or siblings */ + *first_node = new_node; + return LY_SUCCESS; + } + + /* simply insert into parent, no other children */ + if (userord_anchor) { + LOGERR(LYD_CTX(new_node), LY_EINVAL, "Node \"%s\" instance to insert next to not found.", + new_node->schema->name); + return LY_EINVAL; + } + return lyd_insert_child(parent_node, new_node); + } + + assert(!(*first_node)->parent || (lyd_parent(*first_node) == parent_node)); + + if (!lysc_is_userordered(new_node->schema)) { + /* simple insert */ + return lyd_insert_sibling(*first_node, new_node, first_node); + } + + if (userord_anchor) { + /* find the anchor sibling */ + if (lysc_is_dup_inst_list(new_node->schema)) { + anchor_pos = atoi(userord_anchor); + if (!anchor_pos) { + LOGERR(LYD_CTX(new_node), LY_EINVAL, "Invalid user-ordered anchor value \"%s\".", userord_anchor); + return LY_EINVAL; + } + + found = 0; + pos = 1; + LYD_LIST_FOR_INST(*first_node, new_node->schema, anchor) { + if (pos == anchor_pos) { + found = 1; + break; + } + ++pos; + } + if (!found) { + LOGERR(LYD_CTX(new_node), LY_EINVAL, "Node \"%s\" instance to insert next to not found.", + new_node->schema->name); + return LY_EINVAL; + } + } else { + ret = lyd_find_sibling_val(*first_node, new_node->schema, userord_anchor, 0, &anchor); + if (ret == LY_ENOTFOUND) { + LOGERR(LYD_CTX(new_node), LY_EINVAL, "Node \"%s\" instance to insert next to not found.", + new_node->schema->name); + return LY_EINVAL; + } else if (ret) { + return ret; + } + } + + /* insert after */ + LY_CHECK_RET(lyd_insert_after(anchor, new_node)); + assert(new_node->prev == anchor); + if (*first_node == new_node) { + *first_node = anchor; + } + } else { + /* find the first instance */ + ret = lyd_find_sibling_val(*first_node, new_node->schema, NULL, 0, &anchor); + LY_CHECK_RET(ret && (ret != LY_ENOTFOUND), ret); + + if (anchor) { + /* insert before the first instance */ + LY_CHECK_RET(lyd_insert_before(anchor, new_node)); + if ((*first_node)->prev->next) { + assert(!new_node->prev->next); + *first_node = new_node; + } + } else { + /* insert anywhere */ + LY_CHECK_RET(lyd_insert_sibling(*first_node, new_node, first_node)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Apply diff subtree on data tree nodes, recursively. + * + * @param[in,out] first_node First sibling of the data tree. + * @param[in] parent_node Parent of the first sibling. + * @param[in] diff_node Current diff node. + * @param[in] diff_cb Optional diff callback. + * @param[in] cb_data User data for @p diff_cb. + * @param[in,out] dup_inst Duplicate instance cache for all @p diff_node siblings. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_apply_r(struct lyd_node **first_node, struct lyd_node *parent_node, const struct lyd_node *diff_node, + lyd_diff_cb diff_cb, void *cb_data, struct lyd_dup_inst **dup_inst) +{ + LY_ERR ret; + struct lyd_node *match, *diff_child; + const char *str_val, *meta_str; + enum lyd_diff_op op; + struct lyd_meta *meta; + struct lyd_dup_inst *child_dup_inst = NULL; + const struct ly_ctx *ctx = LYD_CTX(diff_node); + + /* read all the valid attributes */ + LY_CHECK_RET(lyd_diff_get_op(diff_node, &op)); + + /* handle specific user-ordered (leaf-)lists operations separately */ + if (lysc_is_userordered(diff_node->schema) && ((op == LYD_DIFF_OP_CREATE) || (op == LYD_DIFF_OP_REPLACE))) { + if (op == LYD_DIFF_OP_REPLACE) { + /* find the node (we must have some siblings because the node was only moved) */ + LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match)); + LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL); + } else { + /* duplicate the node */ + LY_CHECK_RET(lyd_dup_single(diff_node, NULL, LYD_DUP_NO_META, &match)); + } + + /* get "key", "value", or "position" metadata string value */ + if (lysc_is_dup_inst_list(diff_node->schema)) { + meta_str = "yang:position"; + } else if (diff_node->schema->nodetype == LYS_LIST) { + meta_str = "yang:key"; + } else { + meta_str = "yang:value"; + } + meta = lyd_find_meta(diff_node->meta, NULL, meta_str); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_str, diff_node), LY_EINVAL); + str_val = lyd_get_meta_value(meta); + + /* insert/move the node */ + if (str_val[0]) { + ret = lyd_diff_insert(first_node, parent_node, match, str_val); + } else { + ret = lyd_diff_insert(first_node, parent_node, match, NULL); + } + if (ret) { + if (op == LYD_DIFF_OP_CREATE) { + lyd_free_tree(match); + } + return ret; + } + + goto next_iter_r; + } + + /* apply operation */ + switch (op) { + case LYD_DIFF_OP_NONE: + /* find the node */ + LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match)); + LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL); + + if (match->schema->nodetype & LYD_NODE_TERM) { + /* special case of only dflt flag change */ + if (diff_node->flags & LYD_DEFAULT) { + match->flags |= LYD_DEFAULT; + } else { + match->flags &= ~LYD_DEFAULT; + } + } else { + /* none operation on nodes without children is redundant and hence forbidden */ + if (!lyd_child_no_keys(diff_node)) { + LOGERR(ctx, LY_EINVAL, "Operation \"none\" is invalid for node \"%s\" without children.", + LYD_NAME(diff_node)); + return LY_EINVAL; + } + } + break; + case LYD_DIFF_OP_CREATE: + /* duplicate the node */ + LY_CHECK_RET(lyd_dup_single(diff_node, NULL, LYD_DUP_NO_META, &match)); + + /* insert it at the end */ + ret = 0; + if (parent_node) { + if (match->flags & LYD_EXT) { + ret = lyplg_ext_insert(parent_node, match); + } else { + ret = lyd_insert_child(parent_node, match); + } + } else { + ret = lyd_insert_sibling(*first_node, match, first_node); + } + if (ret) { + lyd_free_tree(match); + return ret; + } + + break; + case LYD_DIFF_OP_DELETE: + /* find the node */ + LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match)); + LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL); + + /* remove it */ + if ((match == *first_node) && !match->parent) { + assert(!parent_node); + /* we have removed the top-level node */ + *first_node = (*first_node)->next; + } + lyd_free_tree(match); + + /* we are not going recursively in this case, the whole subtree was already deleted */ + return LY_SUCCESS; + case LYD_DIFF_OP_REPLACE: + if (!(diff_node->schema->nodetype & (LYS_LEAF | LYS_ANYDATA))) { + LOGERR(ctx, LY_EINVAL, "Operation \"replace\" is invalid for %s node \"%s\".", + lys_nodetype2str(diff_node->schema->nodetype), LYD_NAME(diff_node)); + return LY_EINVAL; + } + + /* find the node */ + LY_CHECK_RET(lyd_diff_find_match(*first_node, diff_node, 1, dup_inst, &match)); + LY_CHECK_ERR_RET(!match, LOGERR_NOINST(ctx, diff_node), LY_EINVAL); + + /* update the value */ + if (diff_node->schema->nodetype == LYS_LEAF) { + ret = lyd_change_term(match, lyd_get_value(diff_node)); + LY_CHECK_ERR_RET(ret && (ret != LY_EEXIST), LOGERR_UNEXPVAL(ctx, match, "data"), LY_EINVAL); + } else { + struct lyd_node_any *any = (struct lyd_node_any *)diff_node; + + LY_CHECK_RET(lyd_any_copy_value(match, &any->value, any->value_type)); + } + + /* with flags */ + match->flags = diff_node->flags; + break; + default: + LOGINT_RET(ctx); + } + +next_iter_r: + if (diff_cb) { + /* call callback */ + LY_CHECK_RET(diff_cb(diff_node, match, cb_data)); + } + + /* apply diff recursively */ + ret = LY_SUCCESS; + LY_LIST_FOR(lyd_child_no_keys(diff_node), diff_child) { + ret = lyd_diff_apply_r(lyd_node_child_p(match), match, diff_child, diff_cb, cb_data, &child_dup_inst); + if (ret) { + break; + } + } + + lyd_dup_inst_free(child_dup_inst); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_apply_module(struct lyd_node **data, const struct lyd_node *diff, const struct lys_module *mod, + lyd_diff_cb diff_cb, void *cb_data) +{ + const struct lyd_node *root; + struct lyd_dup_inst *dup_inst = NULL; + LY_ERR ret = LY_SUCCESS; + + LY_LIST_FOR(diff, root) { + if (mod && (lyd_owner_module(root) != mod)) { + /* skip data nodes from different modules */ + continue; + } + + /* apply relevant nodes from the diff datatree */ + ret = lyd_diff_apply_r(data, NULL, root, diff_cb, cb_data, &dup_inst); + if (ret) { + break; + } + } + + lyd_dup_inst_free(dup_inst); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_apply_all(struct lyd_node **data, const struct lyd_node *diff) +{ + return lyd_diff_apply_module(data, diff, NULL, NULL, NULL); +} + +/** + * @brief Update operations on a diff node when the new operation is NONE. + * + * @param[in] diff_match Node from the diff. + * @param[in] cur_op Current operation of @p diff_match. + * @param[in] src_diff Current source diff node. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_merge_none(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff) +{ + switch (cur_op) { + case LYD_DIFF_OP_NONE: + case LYD_DIFF_OP_CREATE: + case LYD_DIFF_OP_REPLACE: + if (src_diff->schema->nodetype & LYD_NODE_TERM) { + /* NONE on a term means only its dflt flag was changed */ + diff_match->flags &= ~LYD_DEFAULT; + diff_match->flags |= src_diff->flags & LYD_DEFAULT; + } + break; + default: + /* delete operation is not valid */ + LOGERR_MERGEOP(LYD_CTX(diff_match), diff_match, cur_op, LYD_DIFF_OP_NONE); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Remove an attribute from a node. + * + * @param[in] node Node with the metadata. + * @param[in] name Metadata name. + */ +static void +lyd_diff_del_meta(struct lyd_node *node, const char *name) +{ + struct lyd_meta *meta; + + LY_LIST_FOR(node->meta, meta) { + if (!strcmp(meta->name, name) && !strcmp(meta->annotation->module->name, "yang")) { + lyd_free_meta_single(meta); + return; + } + } + + assert(0); +} + +/** + * @brief Set a specific operation of a node. Delete the previous operation, if any. + * Does not change the default flag. + * + * @param[in] node Node to change. + * @param[in] op Operation to set. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_change_op(struct lyd_node *node, enum lyd_diff_op op) +{ + struct lyd_meta *meta; + + LY_LIST_FOR(node->meta, meta) { + if (!strcmp(meta->name, "operation") && !strcmp(meta->annotation->module->name, "yang")) { + lyd_free_meta_single(meta); + break; + } + } + + return lyd_new_meta(LYD_CTX(node), node, NULL, "yang:operation", lyd_diff_op2str(op), 0, NULL); +} + +/** + * @brief Update operations on a diff node when the new operation is REPLACE. + * + * @param[in] diff_match Node from the diff. + * @param[in] cur_op Current operation of @p diff_match. + * @param[in] src_diff Current source diff node. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff) +{ + LY_ERR ret; + const char *str_val, *meta_name, *orig_meta_name; + struct lyd_meta *meta; + const struct lys_module *mod; + const struct lyd_node_any *any; + const struct ly_ctx *ctx = LYD_CTX(diff_match); + + /* get "yang" module for the metadata */ + mod = ly_ctx_get_module_latest(LYD_CTX(diff_match), "yang"); + assert(mod); + + switch (cur_op) { + case LYD_DIFF_OP_REPLACE: + case LYD_DIFF_OP_CREATE: + switch (diff_match->schema->nodetype) { + case LYS_LIST: + case LYS_LEAFLIST: + /* it was created/moved somewhere, but now it will be created/moved somewhere else, + * keep orig_key/orig_value (only replace oper) and replace key/value */ + assert(lysc_is_userordered(diff_match->schema)); + if (lysc_is_dup_inst_list(diff_match->schema)) { + meta_name = "position"; + } else if (diff_match->schema->nodetype == LYS_LIST) { + meta_name = "key"; + } else { + meta_name = "value"; + } + + lyd_diff_del_meta(diff_match, meta_name); + meta = lyd_find_meta(src_diff->meta, mod, meta_name); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL); + LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + break; + case LYS_LEAF: + /* replaced with the exact same value, impossible */ + if (!lyd_compare_single(diff_match, src_diff, 0)) { + LOGERR_UNEXPVAL(ctx, diff_match, "target diff"); + return LY_EINVAL; + } + + /* modify the node value */ + if (lyd_change_term(diff_match, lyd_get_value(src_diff))) { + LOGINT_RET(LYD_CTX(src_diff)); + } + + if (cur_op == LYD_DIFF_OP_REPLACE) { + /* compare values whether there is any change at all */ + meta = lyd_find_meta(diff_match->meta, mod, "orig-value"); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, "orig-value", diff_match), LY_EINVAL); + str_val = lyd_get_meta_value(meta); + ret = lyd_value_compare((struct lyd_node_term *)diff_match, str_val, strlen(str_val)); + if (!ret) { + /* values are the same, remove orig-value meta and set oper to NONE */ + lyd_free_meta_single(meta); + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + } + } + + /* modify the default flag */ + diff_match->flags &= ~LYD_DEFAULT; + diff_match->flags |= src_diff->flags & LYD_DEFAULT; + break; + case LYS_ANYXML: + case LYS_ANYDATA: + if (!lyd_compare_single(diff_match, src_diff, 0)) { + LOGERR_UNEXPVAL(ctx, diff_match, "target diff"); + return LY_EINVAL; + } + + /* modify the node value */ + any = (struct lyd_node_any *)src_diff; + LY_CHECK_RET(lyd_any_copy_value(diff_match, &any->value, any->value_type)); + break; + default: + LOGINT_RET(LYD_CTX(src_diff)); + } + break; + case LYD_DIFF_OP_NONE: + /* it is moved now */ + assert(lysc_is_userordered(diff_match->schema) && (diff_match->schema->nodetype == LYS_LIST)); + + /* change the operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); + + /* set orig-meta and meta */ + if (lysc_is_dup_inst_list(diff_match->schema)) { + meta_name = "position"; + orig_meta_name = "orig-position"; + } else { + meta_name = "key"; + orig_meta_name = "orig-key"; + } + + meta = lyd_find_meta(src_diff->meta, mod, orig_meta_name); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, orig_meta_name, src_diff), LY_EINVAL); + LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + + meta = lyd_find_meta(src_diff->meta, mod, meta_name); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL); + LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + break; + default: + /* delete operation is not valid */ + LOGERR_MERGEOP(ctx, diff_match, cur_op, LYD_DIFF_OP_REPLACE); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Update operations in a diff node when the new operation is CREATE. + * + * @param[in] diff_match Node from the diff. + * @param[in] cur_op Current operation of @p diff_match. + * @param[in] src_diff Current source diff node. + * @param[in] options Diff merge options. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_merge_create(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff, uint16_t options) +{ + struct lyd_node *child; + const struct lysc_node_leaf *sleaf = NULL; + uint32_t trg_flags; + const char *meta_name, *orig_meta_name; + struct lyd_meta *meta, *orig_meta; + const struct ly_ctx *ctx = LYD_CTX(diff_match); + + switch (cur_op) { + case LYD_DIFF_OP_DELETE: + /* remember current flags */ + trg_flags = diff_match->flags; + + if (lysc_is_userordered(diff_match->schema)) { + /* get anchor metadata */ + if (lysc_is_dup_inst_list(diff_match->schema)) { + meta_name = "yang:position"; + orig_meta_name = "yang:orig-position"; + } else if (diff_match->schema->nodetype == LYS_LIST) { + meta_name = "yang:key"; + orig_meta_name = "yang:orig-key"; + } else { + meta_name = "yang:value"; + orig_meta_name = "yang:orig-value"; + } + meta = lyd_find_meta(src_diff->meta, NULL, meta_name); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL); + orig_meta = lyd_find_meta(diff_match->meta, NULL, orig_meta_name); + LY_CHECK_ERR_RET(!orig_meta, LOGERR_META(ctx, orig_meta_name, diff_match), LY_EINVAL); + + /* the (incorrect) assumption made here is that there are no previous diff nodes that would affect + * the anchors stored in the metadata */ + if (strcmp(lyd_get_meta_value(meta), lyd_get_meta_value(orig_meta))) { + /* deleted + created at another position -> operation REPLACE */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); + + /* add anchor metadata */ + LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + } else { + /* deleted + created at the same position -> operation NONE */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + + /* delete anchor metadata */ + lyd_free_meta_single(orig_meta); + } + } else if (diff_match->schema->nodetype == LYS_LEAF) { + if (options & LYD_DIFF_MERGE_DEFAULTS) { + /* we are dealing with a leaf and are handling default values specially (as explicit nodes) */ + sleaf = (struct lysc_node_leaf *)diff_match->schema; + } + + if (sleaf && sleaf->dflt && !sleaf->dflt->realtype->plugin->compare(sleaf->dflt, + &((struct lyd_node_term *)src_diff)->value)) { + /* we deleted it, so a default value was in-use, and it matches the created value -> operation NONE */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + } else if (!lyd_compare_single(diff_match, src_diff, 0)) { + /* deleted + created -> operation NONE */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + } else { + /* we deleted it, but it was created with a different value -> operation REPLACE */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); + + /* current value is the previous one (meta) */ + LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-value", + lyd_get_value(diff_match), 0, NULL)); + + /* update the value itself */ + LY_CHECK_RET(lyd_change_term(diff_match, lyd_get_value(src_diff))); + } + } else { + /* deleted + created -> operation NONE */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + } + + if (diff_match->schema->nodetype & LYD_NODE_TERM) { + /* add orig-dflt metadata */ + LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-default", + trg_flags & LYD_DEFAULT ? "true" : "false", 0, NULL)); + + /* update dflt flag itself */ + diff_match->flags &= ~LYD_DEFAULT; + diff_match->flags |= src_diff->flags & LYD_DEFAULT; + } + + /* but the operation of its children should remain DELETE */ + LY_LIST_FOR(lyd_child_no_keys(diff_match), child) { + LY_CHECK_RET(lyd_diff_change_op(child, LYD_DIFF_OP_DELETE)); + } + break; + default: + /* create and replace operations are not valid */ + LOGERR_MERGEOP(LYD_CTX(src_diff), diff_match, cur_op, LYD_DIFF_OP_CREATE); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Update operations on a diff node when the new operation is DELETE. + * + * @param[in] diff_match Node from the diff. + * @param[in] cur_op Current operation of @p diff_match. + * @param[in] src_diff Current source diff node. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_merge_delete(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff) +{ + struct lyd_node *child; + struct lyd_meta *meta; + const char *meta_name; + const struct ly_ctx *ctx = LYD_CTX(diff_match); + + /* we can delete only exact existing nodes */ + LY_CHECK_ERR_RET(lyd_compare_single(diff_match, src_diff, 0), LOGINT(LYD_CTX(src_diff)), LY_EINT); + + switch (cur_op) { + case LYD_DIFF_OP_CREATE: + /* it was created, but then deleted -> set NONE operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + + if (diff_match->schema->nodetype & LYD_NODE_TERM) { + /* add orig-default meta because it is expected */ + LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-default", + diff_match->flags & LYD_DEFAULT ? "true" : "false", 0, NULL)); + } else if (!lysc_is_dup_inst_list(diff_match->schema)) { + /* keep operation for all descendants (for now) */ + LY_LIST_FOR(lyd_child_no_keys(diff_match), child) { + LY_CHECK_RET(lyd_diff_change_op(child, cur_op)); + } + } /* else key-less list, for which all the descendants act as keys */ + break; + case LYD_DIFF_OP_REPLACE: + /* remove the redundant metadata */ + if (lysc_is_userordered(diff_match->schema)) { + if (lysc_is_dup_inst_list(diff_match->schema)) { + meta_name = "position"; + } else if (diff_match->schema->nodetype == LYS_LIST) { + meta_name = "key"; + } else { + meta_name = "value"; + } + } else { + assert(diff_match->schema->nodetype == LYS_LEAF); + + /* switch value for the original one */ + meta = lyd_find_meta(diff_match->meta, NULL, "yang:orig-value"); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, "yang:orig-value", diff_match), LY_EINVAL); + if (lyd_change_term(diff_match, lyd_get_meta_value(meta))) { + LOGERR_UNEXPVAL(ctx, diff_match, "target diff"); + return LY_EINVAL; + } + + /* switch default for the original one, then remove the meta */ + meta = lyd_find_meta(diff_match->meta, NULL, "yang:orig-default"); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, "yang:orig-default", diff_match), LY_EINVAL); + diff_match->flags &= ~LYD_DEFAULT; + if (meta->value.boolean) { + diff_match->flags |= LYD_DEFAULT; + } + lyd_free_meta_single(meta); + + meta_name = "orig-value"; + } + lyd_diff_del_meta(diff_match, meta_name); + + /* it was being changed, but should be deleted instead -> set DELETE operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_DELETE)); + break; + case LYD_DIFF_OP_NONE: + /* it was not modified, but should be deleted -> set DELETE operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_DELETE)); + break; + default: + /* delete operation is not valid */ + LOGERR_MERGEOP(LYD_CTX(diff_match), diff_match, cur_op, LYD_DIFF_OP_DELETE); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Check whether this diff node is redundant (does not change data). + * + * @param[in] diff Diff node. + * @return 0 if not, non-zero if it is. + */ +static int +lyd_diff_is_redundant(struct lyd_node *diff) +{ + enum lyd_diff_op op; + struct lyd_meta *meta, *orig_val_meta = NULL, *val_meta = NULL; + struct lyd_node *child; + const struct lys_module *mod; + const char *str, *orig_meta_name, *meta_name; + + assert(diff); + + if (lysc_is_dup_inst_list(diff->schema)) { + /* all descendants are keys */ + child = NULL; + } else { + child = lyd_child_no_keys(diff); + } + mod = ly_ctx_get_module_latest(LYD_CTX(diff), "yang"); + assert(mod); + + /* get node operation */ + LY_CHECK_RET(lyd_diff_get_op(diff, &op), 0); + + if ((op == LYD_DIFF_OP_REPLACE) && lysc_is_userordered(diff->schema)) { + /* get metadata names */ + if (lysc_is_dup_inst_list(diff->schema)) { + meta_name = "position"; + orig_meta_name = "orig-position"; + } else if (diff->schema->nodetype == LYS_LIST) { + meta_name = "key"; + orig_meta_name = "orig-key"; + } else { + meta_name = "value"; + orig_meta_name = "orig-value"; + } + + /* check for redundant move */ + orig_val_meta = lyd_find_meta(diff->meta, mod, orig_meta_name); + val_meta = lyd_find_meta(diff->meta, mod, meta_name); + assert(orig_val_meta && val_meta); + + if (!lyd_compare_meta(orig_val_meta, val_meta)) { + /* there is actually no move */ + lyd_free_meta_single(orig_val_meta); + lyd_free_meta_single(val_meta); + if (child) { + /* change operation to NONE, we have siblings */ + lyd_diff_change_op(diff, LYD_DIFF_OP_NONE); + return 0; + } + + /* redundant node, BUT !! + * In diff the move operation is always converted to be INSERT_AFTER, which is fine + * because the data that this is applied on should not change for the diff lifetime. + * However, when we are merging 2 diffs, this conversion is actually lossy because + * if the data change, the move operation can also change its meaning. In this specific + * case the move operation will be lost. But it can be considered a feature, it is not supported. + */ + return 1; + } + } else if ((op == LYD_DIFF_OP_NONE) && (diff->schema->nodetype & LYD_NODE_TERM)) { + /* check whether at least the default flags are different */ + meta = lyd_find_meta(diff->meta, mod, "orig-default"); + assert(meta); + str = lyd_get_meta_value(meta); + + /* if previous and current dflt flags are the same, this node is redundant */ + if ((!strcmp(str, "true") && (diff->flags & LYD_DEFAULT)) || (!strcmp(str, "false") && !(diff->flags & LYD_DEFAULT))) { + return 1; + } + return 0; + } + + if (!child && (op == LYD_DIFF_OP_NONE)) { + return 1; + } + + return 0; +} + +/** + * @brief Merge sysrepo diff subtree with another diff, recursively. + * + * @param[in] src_diff Source diff node. + * @param[in] diff_parent Current sysrepo diff parent. + * @param[in] diff_cb Optional diff callback. + * @param[in] cb_data User data for @p diff_cb. + * @param[in,out] dup_inst Duplicate instance cache for all @p src_diff siblings. + * @param[in] options Diff merge options. + * @param[in,out] diff Diff root node. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_merge_r(const struct lyd_node *src_diff, struct lyd_node *diff_parent, lyd_diff_cb diff_cb, void *cb_data, + struct lyd_dup_inst **dup_inst, uint16_t options, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *child, *diff_node = NULL; + enum lyd_diff_op src_op, cur_op; + struct lyd_dup_inst *child_dup_inst = NULL; + + /* get source node operation */ + LY_CHECK_RET(lyd_diff_get_op(src_diff, &src_op)); + + /* find an equal node in the current diff */ + LY_CHECK_RET(lyd_diff_find_match(diff_parent ? lyd_child_no_keys(diff_parent) : *diff, src_diff, 1, dup_inst, &diff_node)); + + if (diff_node) { + /* get target (current) operation */ + LY_CHECK_RET(lyd_diff_get_op(diff_node, &cur_op)); + + /* merge operations */ + switch (src_op) { + case LYD_DIFF_OP_REPLACE: + ret = lyd_diff_merge_replace(diff_node, cur_op, src_diff); + break; + case LYD_DIFF_OP_CREATE: + if ((cur_op == LYD_DIFF_OP_CREATE) && lysc_is_dup_inst_list(diff_node->schema)) { + /* special case of creating duplicate (leaf-)list instances */ + goto add_diff; + } + + ret = lyd_diff_merge_create(diff_node, cur_op, src_diff, options); + break; + case LYD_DIFF_OP_DELETE: + ret = lyd_diff_merge_delete(diff_node, cur_op, src_diff); + break; + case LYD_DIFF_OP_NONE: + /* key-less list can never have "none" operation since all its descendants are acting as "keys" */ + assert((src_diff->schema->nodetype != LYS_LIST) || !lysc_is_dup_inst_list(src_diff->schema)); + ret = lyd_diff_merge_none(diff_node, cur_op, src_diff); + break; + default: + LOGINT_RET(LYD_CTX(src_diff)); + } + if (ret) { + LOGERR(LYD_CTX(src_diff), LY_EOTHER, "Merging operation \"%s\" failed.", lyd_diff_op2str(src_op)); + return ret; + } + + if (diff_cb) { + /* call callback */ + LY_CHECK_RET(diff_cb(src_diff, diff_node, cb_data)); + } + + /* update diff parent */ + diff_parent = diff_node; + + /* for diff purposes, all key-less list descendants actually act as keys (identifying the same instances), + * so there is nothing to merge for these "keys" */ + if (!lysc_is_dup_inst_list(src_diff->schema)) { + /* merge src_diff recursively */ + LY_LIST_FOR(lyd_child_no_keys(src_diff), child) { + ret = lyd_diff_merge_r(child, diff_parent, diff_cb, cb_data, &child_dup_inst, options, diff); + if (ret) { + break; + } + } + lyd_dup_inst_free(child_dup_inst); + LY_CHECK_RET(ret); + } + } else { +add_diff: + /* add new diff node with all descendants */ + if ((src_diff->flags & LYD_EXT) && diff_parent) { + LY_CHECK_RET(lyd_dup_single_to_ctx(src_diff, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent, + LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS, &diff_node)); + } else { + LY_CHECK_RET(lyd_dup_single(src_diff, (struct lyd_node_inner *)diff_parent, + LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS, &diff_node)); + } + + /* insert node into diff if not already */ + if (!diff_parent) { + lyd_insert_sibling(*diff, diff_node, diff); + } + + /* update operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_node, src_op)); + + if (diff_cb) { + /* call callback with no source diff node since it was duplicated and just added */ + LY_CHECK_RET(diff_cb(NULL, diff_node, cb_data)); + } + + /* update diff parent */ + diff_parent = diff_node; + } + + /* remove any redundant nodes */ + if (lyd_diff_is_redundant(diff_parent)) { + if (diff_parent == *diff) { + *diff = (*diff)->next; + } + lyd_free_tree(diff_parent); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_merge_module(struct lyd_node **diff, const struct lyd_node *src_diff, const struct lys_module *mod, + lyd_diff_cb diff_cb, void *cb_data, uint16_t options) +{ + const struct lyd_node *src_root; + struct lyd_dup_inst *dup_inst = NULL; + LY_ERR ret = LY_SUCCESS; + + LY_LIST_FOR(src_diff, src_root) { + if (mod && (lyd_owner_module(src_root) != mod)) { + /* skip data nodes from different modules */ + continue; + } + + /* apply relevant nodes from the diff datatree */ + LY_CHECK_GOTO(ret = lyd_diff_merge_r(src_root, NULL, diff_cb, cb_data, &dup_inst, options, diff), cleanup); + } + +cleanup: + lyd_dup_inst_free(dup_inst); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_merge_tree(struct lyd_node **diff_first, struct lyd_node *diff_parent, const struct lyd_node *src_sibling, + lyd_diff_cb diff_cb, void *cb_data, uint16_t options) +{ + LY_ERR ret; + struct lyd_dup_inst *dup_inst = NULL; + + if (!src_sibling) { + return LY_SUCCESS; + } + + ret = lyd_diff_merge_r(src_sibling, diff_parent, diff_cb, cb_data, &dup_inst, options, diff_first); + lyd_dup_inst_free(dup_inst); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_merge_all(struct lyd_node **diff, const struct lyd_node *src_diff, uint16_t options) +{ + return lyd_diff_merge_module(diff, src_diff, NULL, NULL, NULL, options); +} + +static LY_ERR +lyd_diff_reverse_value(struct lyd_node *node, const struct lys_module *mod) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_meta *meta; + const char *val1 = NULL; + char *val2; + uint32_t flags; + + assert(node->schema->nodetype & (LYS_LEAF | LYS_ANYDATA)); + + meta = lyd_find_meta(node->meta, mod, "orig-value"); + LY_CHECK_ERR_RET(!meta, LOGERR_META(LYD_CTX(node), "orig-value", node), LY_EINVAL); + + /* orig-value */ + val1 = lyd_get_meta_value(meta); + + /* current value */ + if (node->schema->nodetype == LYS_LEAF) { + val2 = strdup(lyd_get_value(node)); + } else { + LY_CHECK_RET(lyd_any_value_str(node, &val2)); + } + + /* switch values, keep default flag */ + flags = node->flags; + if (node->schema->nodetype == LYS_LEAF) { + LY_CHECK_GOTO(ret = lyd_change_term(node, val1), cleanup); + } else { + union lyd_any_value anyval = {.str = val1}; + + LY_CHECK_GOTO(ret = lyd_any_copy_value(node, &anyval, LYD_ANYDATA_STRING), cleanup); + } + node->flags = flags; + LY_CHECK_GOTO(ret = lyd_change_meta(meta, val2), cleanup); + +cleanup: + free(val2); + return ret; +} + +static LY_ERR +lyd_diff_reverse_default(struct lyd_node *node, const struct lys_module *mod) +{ + struct lyd_meta *meta; + uint32_t flag1, flag2; + + meta = lyd_find_meta(node->meta, mod, "orig-default"); + LY_CHECK_ERR_RET(!meta, LOGINT(mod->ctx), LY_EINT); + + /* orig-default */ + if (meta->value.boolean) { + flag1 = LYD_DEFAULT; + } else { + flag1 = 0; + } + + /* current default */ + flag2 = node->flags & LYD_DEFAULT; + + if (flag1 == flag2) { + /* no default state change so nothing to reverse */ + return LY_SUCCESS; + } + + /* switch defaults */ + node->flags &= ~LYD_DEFAULT; + node->flags |= flag1; + LY_CHECK_RET(lyd_change_meta(meta, flag2 ? "true" : "false")); + + return LY_SUCCESS; +} + +static LY_ERR +lyd_diff_reverse_meta(struct lyd_node *node, const struct lys_module *mod, const char *name1, const char *name2) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_meta *meta1, *meta2; + const char *val1 = NULL; + char *val2 = NULL; + + meta1 = lyd_find_meta(node->meta, mod, name1); + LY_CHECK_ERR_RET(!meta1, LOGERR_META(LYD_CTX(node), name1, node), LY_EINVAL); + + meta2 = lyd_find_meta(node->meta, mod, name2); + LY_CHECK_ERR_RET(!meta2, LOGERR_META(LYD_CTX(node), name2, node), LY_EINVAL); + + /* value1 */ + val1 = lyd_get_meta_value(meta1); + + /* value2 */ + val2 = strdup(lyd_get_meta_value(meta2)); + + /* switch values */ + LY_CHECK_GOTO(ret = lyd_change_meta(meta1, val2), cleanup); + LY_CHECK_GOTO(ret = lyd_change_meta(meta2, val1), cleanup); + +cleanup: + free(val2); + return ret; +} + +/** + * @brief Remove specific operation from all the nodes in a subtree. + * + * @param[in] diff Diff subtree to process. + * @param[in] op Only expected operation. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_reverse_remove_op_r(struct lyd_node *diff, enum lyd_diff_op op) +{ + struct lyd_node *elem; + struct lyd_meta *meta; + + LYD_TREE_DFS_BEGIN(diff, elem) { + meta = lyd_find_meta(elem->meta, NULL, "yang:operation"); + if (meta) { + LY_CHECK_ERR_RET(lyd_diff_str2op(lyd_get_meta_value(meta)) != op, LOGINT(LYD_CTX(diff)), LY_EINT); + lyd_free_meta_single(meta); + } + + LYD_TREE_DFS_END(diff, elem); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_diff_reverse_all(const struct lyd_node *src_diff, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + const struct lys_module *mod; + struct lyd_node *root, *elem, *iter; + enum lyd_diff_op op; + + LY_CHECK_ARG_RET(NULL, diff, LY_EINVAL); + + if (!src_diff) { + *diff = NULL; + return LY_SUCCESS; + } + + /* duplicate diff */ + LY_CHECK_RET(lyd_dup_siblings(src_diff, NULL, LYD_DUP_RECURSIVE, diff)); + + /* find module with metadata needed for later */ + mod = ly_ctx_get_module_latest(LYD_CTX(src_diff), "yang"); + LY_CHECK_ERR_GOTO(!mod, LOGINT(LYD_CTX(src_diff)); ret = LY_EINT, cleanup); + + LY_LIST_FOR(*diff, root) { + LYD_TREE_DFS_BEGIN(root, elem) { + /* skip all keys */ + if (!lysc_is_key(elem->schema)) { + /* find operation attribute, if any */ + LY_CHECK_GOTO(ret = lyd_diff_get_op(elem, &op), cleanup); + + switch (op) { + case LYD_DIFF_OP_CREATE: + /* reverse create to delete */ + LY_CHECK_GOTO(ret = lyd_diff_change_op(elem, LYD_DIFF_OP_DELETE), cleanup); + + /* check all the children for the same operation, nothing else is expected */ + LY_LIST_FOR(lyd_child(elem), iter) { + lyd_diff_reverse_remove_op_r(iter, LYD_DIFF_OP_CREATE); + } + + LYD_TREE_DFS_continue = 1; + break; + case LYD_DIFF_OP_DELETE: + /* reverse delete to create */ + LY_CHECK_GOTO(ret = lyd_diff_change_op(elem, LYD_DIFF_OP_CREATE), cleanup); + + /* check all the children for the same operation, nothing else is expected */ + LY_LIST_FOR(lyd_child(elem), iter) { + lyd_diff_reverse_remove_op_r(iter, LYD_DIFF_OP_DELETE); + } + + LYD_TREE_DFS_continue = 1; + break; + case LYD_DIFF_OP_REPLACE: + switch (elem->schema->nodetype) { + case LYS_LEAF: + /* leaf value change */ + LY_CHECK_GOTO(ret = lyd_diff_reverse_value(elem, mod), cleanup); + LY_CHECK_GOTO(ret = lyd_diff_reverse_default(elem, mod), cleanup); + break; + case LYS_ANYXML: + case LYS_ANYDATA: + /* any value change */ + LY_CHECK_GOTO(ret = lyd_diff_reverse_value(elem, mod), cleanup); + break; + case LYS_LEAFLIST: + /* leaf-list move */ + LY_CHECK_GOTO(ret = lyd_diff_reverse_default(elem, mod), cleanup); + if (lysc_is_dup_inst_list(elem->schema)) { + LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-position", "position"), cleanup); + } else { + LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-value", "value"), cleanup); + } + break; + case LYS_LIST: + /* list move */ + if (lysc_is_dup_inst_list(elem->schema)) { + LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-position", "position"), cleanup); + } else { + LY_CHECK_GOTO(ret = lyd_diff_reverse_meta(elem, mod, "orig-key", "key"), cleanup); + } + break; + default: + LOGINT(LYD_CTX(src_diff)); + ret = LY_EINT; + goto cleanup; + } + break; + case LYD_DIFF_OP_NONE: + switch (elem->schema->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + /* default flag change */ + LY_CHECK_GOTO(ret = lyd_diff_reverse_default(elem, mod), cleanup); + break; + default: + /* nothing to do */ + break; + } + break; + } + } + + LYD_TREE_DFS_END(root, elem); + } + } + +cleanup: + if (ret) { + lyd_free_siblings(*diff); + *diff = NULL; + } + return ret; +} diff --git a/src/diff.h b/src/diff.h new file mode 100644 index 0000000..dab1614 --- /dev/null +++ b/src/diff.h @@ -0,0 +1,62 @@ +/** + * @file diff.h + * @author Michal Vasko + * @brief internal diff header + * + * Copyright (c) 2020 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_DIFF_H_ +#define LY_DIFF_H_ + +#include + +#include "log.h" + +struct lyd_node; + +/** + * @brief Internal structure for storing current (virtual) user-ordered instances order. + */ +struct lyd_diff_userord { + const struct lysc_node *schema; /**< User-ordered list/leaf-list schema node. */ + uint64_t pos; /**< Current position in the second tree. */ + const struct lyd_node **inst; /**< Sized array of current instance order. */ +}; + +/** + * @brief Diff operations. + */ +enum lyd_diff_op { + LYD_DIFF_OP_CREATE, /**< Subtree created. */ + LYD_DIFF_OP_DELETE, /**< Subtree deleted. */ + LYD_DIFF_OP_REPLACE, /**< Node value changed or (leaf-)list instance moved. */ + LYD_DIFF_OP_NONE /**< No change of an existing inner node or default flag change of a term node. */ +}; + +/** + * @brief Add a new change into diff. + * + * @param[in] node Node (subtree) to add into diff. + * @param[in] op Operation to set. + * @param[in] orig_default Original default metadata to set. + * @param[in] orig_value Original value metadata to set. + * @param[in] key Key metadata to set. + * @param[in] value Value metadata to set. + * @param[in] position Position metadata to set. + * @param[in] orig_key Original key metadata to set. + * @param[in] orig_position Original position metadata to set. + * @param[in,out] diff Diff to append to. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value, + const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position, + struct lyd_node **diff); + +#endif /* LY_DIFF_H_ */ diff --git a/src/hash_table.c b/src/hash_table.c new file mode 100644 index 0000000..4f9dec3 --- /dev/null +++ b/src/hash_table.c @@ -0,0 +1,880 @@ +/** + * @file hash_table.c + * @author Radek Krejci + * @brief libyang dictionary for storing strings and generic hash table + * + * Copyright (c) 2015 - 2018 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "hash_table.h" + +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "dict.h" +#include "log.h" + +#define LYDICT_MIN_SIZE 1024 + +/** + * @brief Comparison callback for dictionary's hash table + * + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lydict_val_eq(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data) +{ + LY_CHECK_ARG_RET(NULL, val1_p, val2_p, cb_data, 0); + + const char *str1 = ((struct dict_rec *)val1_p)->value; + const char *str2 = ((struct dict_rec *)val2_p)->value; + + LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0); + LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0); + + if (strncmp(str1, str2, *(size_t *)cb_data) == 0) { + return 1; + } + + return 0; +} + +void +lydict_init(struct dict_table *dict) +{ + LY_CHECK_ARG_RET(NULL, dict, ); + + dict->hash_tab = lyht_new(LYDICT_MIN_SIZE, sizeof(struct dict_rec), lydict_val_eq, NULL, 1); + LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), ); + pthread_mutex_init(&dict->lock, NULL); +} + +void +lydict_clean(struct dict_table *dict) +{ + struct dict_rec *dict_rec = NULL; + struct ht_rec *rec = NULL; + + LY_CHECK_ARG_RET(NULL, dict, ); + + for (uint32_t i = 0; i < dict->hash_tab->size; i++) { + /* get ith record */ + rec = (struct ht_rec *)&dict->hash_tab->recs[i * dict->hash_tab->rec_size]; + if (rec->hits == 1) { + /* + * this should not happen, all records inserted into + * dictionary are supposed to be removed using lydict_remove() + * before calling lydict_clean() + */ + dict_rec = (struct dict_rec *)rec->val; + LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %d", dict_rec->value, dict_rec->refcount); + /* if record wasn't removed before free string allocated for that record */ +#ifdef NDEBUG + free(dict_rec->value); +#endif + } + } + + /* free table and destroy mutex */ + lyht_free(dict->hash_tab); + pthread_mutex_destroy(&dict->lock); +} + +/* + * Usage: + * - init hash to 0 + * - repeatedly call dict_hash_multi(), provide hash from the last call + * - call dict_hash_multi() with key_part = NULL to finish the hash + */ +uint32_t +dict_hash_multi(uint32_t hash, const char *key_part, size_t len) +{ + uint32_t i; + + if (key_part && len) { + for (i = 0; i < len; ++i) { + hash += key_part[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + } else { + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + } + + return hash; +} + +/* + * Bob Jenkin's one-at-a-time hash + * http://www.burtleburtle.net/bob/hash/doobs.html + * + * Spooky hash is faster, but it works only for little endian architectures. + */ +uint32_t +dict_hash(const char *key, size_t len) +{ + uint32_t hash; + + hash = dict_hash_multi(0, key, len); + return dict_hash_multi(hash, NULL, len); +} + +static ly_bool +lydict_resize_val_eq(void *val1_p, void *val2_p, ly_bool mod, void *cb_data) +{ + LY_CHECK_ARG_RET(NULL, val1_p, val2_p, 0); + + const char *str1 = ((struct dict_rec *)val1_p)->value; + const char *str2 = ((struct dict_rec *)val2_p)->value; + + LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0); + LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0); + + if (mod) { + /* used when inserting new values */ + if (strcmp(str1, str2) == 0) { + return 1; + } + } else { + /* used when finding the original value again in the resized table */ + return lydict_val_eq(val1_p, val2_p, mod, cb_data); + } + + return 0; +} + +LIBYANG_API_DEF LY_ERR +lydict_remove(const struct ly_ctx *ctx, const char *value) +{ + LY_ERR ret = LY_SUCCESS; + size_t len; + uint32_t hash; + struct dict_rec rec, *match = NULL; + char *val_p; + + if (!ctx || !value) { + return LY_SUCCESS; + } + + LOGDBG(LY_LDGDICT, "removing \"%s\"", value); + + len = strlen(value); + hash = dict_hash(value, len); + + /* create record for lyht_find call */ + rec.value = (char *)value; + rec.refcount = 0; + + pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); + /* set len as data for compare callback */ + lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len); + /* check if value is already inserted */ + ret = lyht_find(ctx->dict.hash_tab, &rec, hash, (void **)&match); + + if (ret == LY_SUCCESS) { + LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), finish); + + /* if value is already in dictionary, decrement reference counter */ + match->refcount--; + if (match->refcount == 0) { + /* + * remove record + * save pointer to stored string before lyht_remove to + * free it after it is removed from hash table + */ + val_p = match->value; + ret = lyht_remove_with_resize_cb(ctx->dict.hash_tab, &rec, hash, lydict_resize_val_eq); + free(val_p); + LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), finish); + } + } else if (ret == LY_ENOTFOUND) { + LOGERR(ctx, LY_ENOTFOUND, "Value \"%s\" was not found in the dictionary.", value); + } else { + LOGINT(ctx); + } + +finish: + pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); + return ret; +} + +LY_ERR +dict_insert(const struct ly_ctx *ctx, char *value, size_t len, ly_bool zerocopy, const char **str_p) +{ + LY_ERR ret = LY_SUCCESS; + struct dict_rec *match = NULL, rec; + uint32_t hash; + + LOGDBG(LY_LDGDICT, "inserting \"%.*s\"", (int)len, value); + + hash = dict_hash(value, len); + /* set len as data for compare callback */ + lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len); + /* create record for lyht_insert */ + rec.value = value; + rec.refcount = 1; + + ret = lyht_insert_with_resize_cb(ctx->dict.hash_tab, (void *)&rec, hash, lydict_resize_val_eq, (void **)&match); + if (ret == LY_EEXIST) { + match->refcount++; + if (zerocopy) { + free(value); + } + ret = LY_SUCCESS; + } else if (ret == LY_SUCCESS) { + if (!zerocopy) { + /* + * allocate string for new record + * record is already inserted in hash table + */ + match->value = malloc(sizeof *match->value * (len + 1)); + LY_CHECK_ERR_RET(!match->value, LOGMEM(ctx), LY_EMEM); + if (len) { + memcpy(match->value, value, len); + } + match->value[len] = '\0'; + } + } else { + /* lyht_insert returned error */ + if (zerocopy) { + free(value); + } + return ret; + } + + if (str_p) { + *str_p = match->value; + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p) +{ + LY_ERR result; + + LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL); + + if (!value) { + *str_p = NULL; + return LY_SUCCESS; + } + + if (!len) { + len = strlen(value); + } + + pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); + result = dict_insert(ctx, (char *)value, len, 0, str_p); + pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); + + return result; +} + +LIBYANG_API_DEF LY_ERR +lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p) +{ + LY_ERR result; + + LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL); + + if (!value) { + *str_p = NULL; + return LY_SUCCESS; + } + + pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); + result = dict_insert(ctx, value, strlen(value), 1, str_p); + pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); + + return result; +} + +struct ht_rec * +lyht_get_rec(unsigned char *recs, uint16_t rec_size, uint32_t idx) +{ + return (struct ht_rec *)&recs[idx * rec_size]; +} + +struct hash_table * +lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void *cb_data, uint16_t resize) +{ + struct hash_table *ht; + + /* check that 2^x == size (power of 2) */ + assert(size && !(size & (size - 1))); + assert(val_equal && val_size); + assert(resize == 0 || resize == 1); + + if (size < LYHT_MIN_SIZE) { + size = LYHT_MIN_SIZE; + } + + ht = malloc(sizeof *ht); + LY_CHECK_ERR_RET(!ht, LOGMEM(NULL), NULL); + + ht->used = 0; + ht->size = size; + ht->invalid = 0; + ht->val_equal = val_equal; + ht->cb_data = cb_data; + ht->resize = resize; + + ht->rec_size = (sizeof(struct ht_rec) - 1) + val_size; + /* allocate the records correctly */ + ht->recs = calloc(size, ht->rec_size); + LY_CHECK_ERR_RET(!ht->recs, free(ht); LOGMEM(NULL), NULL); + + return ht; +} + +lyht_value_equal_cb +lyht_set_cb(struct hash_table *ht, lyht_value_equal_cb new_val_equal) +{ + lyht_value_equal_cb prev; + + prev = ht->val_equal; + ht->val_equal = new_val_equal; + return prev; +} + +void * +lyht_set_cb_data(struct hash_table *ht, void *new_cb_data) +{ + void *prev; + + prev = ht->cb_data; + ht->cb_data = new_cb_data; + return prev; +} + +struct hash_table * +lyht_dup(const struct hash_table *orig) +{ + struct hash_table *ht; + + LY_CHECK_ARG_RET(NULL, orig, NULL); + + ht = lyht_new(orig->size, orig->rec_size - (sizeof(struct ht_rec) - 1), orig->val_equal, orig->cb_data, orig->resize ? 1 : 0); + if (!ht) { + return NULL; + } + + memcpy(ht->recs, orig->recs, (size_t)orig->used * (size_t)orig->rec_size); + ht->used = orig->used; + ht->invalid = orig->invalid; + return ht; +} + +void +lyht_free(struct hash_table *ht) +{ + if (ht) { + free(ht->recs); + free(ht); + } +} + +/** + * @brief Resize a hash table. + * + * @param[in] ht Hash table to resize. + * @param[in] operation Operation to perform. 1 to enlarge, -1 to shrink, 0 to only rehash all records. + * @return LY_ERR value. + */ +static LY_ERR +lyht_resize(struct hash_table *ht, int operation) +{ + struct ht_rec *rec; + unsigned char *old_recs; + uint32_t i, old_size; + + old_recs = ht->recs; + old_size = ht->size; + + if (operation > 0) { + /* double the size */ + ht->size <<= 1; + } else if (operation < 0) { + /* half the size */ + ht->size >>= 1; + } + + ht->recs = calloc(ht->size, ht->rec_size); + LY_CHECK_ERR_RET(!ht->recs, LOGMEM(NULL); ht->recs = old_recs; ht->size = old_size, LY_EMEM); + + /* reset used and invalid, it will increase again */ + ht->used = 0; + ht->invalid = 0; + + /* add all the old records into the new records array */ + for (i = 0; i < old_size; ++i) { + rec = lyht_get_rec(old_recs, ht->rec_size, i); + if (rec->hits > 0) { + LY_ERR ret = lyht_insert(ht, rec->val, rec->hash, NULL); + + assert(!ret); + (void)ret; + } + } + + /* final touches */ + free(old_recs); + return LY_SUCCESS; +} + +/** + * @brief Search for the first match. + * + * @param[in] ht Hash table to search in. + * @param[in] hash Hash to find. + * @param[out] rec_p Optional found record. + * @return LY_SUCCESS hash found, returned its record, + * @return LY_ENOTFOUND hash not found, returned the record where it would be inserted. + */ +static LY_ERR +lyht_find_first(struct hash_table *ht, uint32_t hash, struct ht_rec **rec_p) +{ + struct ht_rec *rec; + uint32_t i, idx; + + if (rec_p) { + *rec_p = NULL; + } + + idx = i = hash & (ht->size - 1); + rec = lyht_get_rec(ht->recs, ht->rec_size, idx); + + /* skip through overflow and deleted records */ + while ((rec->hits != 0) && ((rec->hits == -1) || ((rec->hash & (ht->size - 1)) != idx))) { + if ((rec->hits == -1) && rec_p && !(*rec_p)) { + /* remember this record for return */ + *rec_p = rec; + } + i = (i + 1) % ht->size; + if (i == idx) { + /* we went through all the records (very unlikely, but possible when many records are invalid), + * just return not found */ + assert(!rec_p || *rec_p); + return LY_ENOTFOUND; + } + rec = lyht_get_rec(ht->recs, ht->rec_size, i); + } + if (rec->hits == 0) { + /* we could not find the value */ + if (rec_p && !*rec_p) { + *rec_p = rec; + } + return LY_ENOTFOUND; + } + + /* we have found a record with equal (shortened) hash */ + if (rec_p) { + *rec_p = rec; + } + return LY_SUCCESS; +} + +/** + * @brief Search for the next collision. + * + * @param[in] ht Hash table to search in. + * @param[in,out] last Last returned collision record. + * @param[in] first First collision record (hits > 1). + * @return LY_SUCCESS when hash collision found, \p last points to this next collision, + * @return LY_ENOTFOUND when hash collision not found, \p last points to the record where it would be inserted. + */ +static LY_ERR +lyht_find_collision(struct hash_table *ht, struct ht_rec **last, struct ht_rec *first) +{ + struct ht_rec *empty = NULL; + uint32_t i, idx; + + assert(last && *last); + + idx = (*last)->hash & (ht->size - 1); + i = (((unsigned char *)*last) - ht->recs) / ht->rec_size; + + do { + i = (i + 1) % ht->size; + *last = lyht_get_rec(ht->recs, ht->rec_size, i); + if (*last == first) { + /* we went through all the records (very unlikely, but possible when many records are invalid), + * just return an invalid record */ + assert(empty); + *last = empty; + return LY_ENOTFOUND; + } + + if (((*last)->hits == -1) && !empty) { + empty = *last; + } + } while (((*last)->hits != 0) && (((*last)->hits == -1) || (((*last)->hash & (ht->size - 1)) != idx))); + + if ((*last)->hits > 0) { + /* we found a collision */ + assert((*last)->hits == 1); + return LY_SUCCESS; + } + + /* no next collision found, return the record where it would be inserted */ + if (empty) { + *last = empty; + } /* else (*last)->hits == 0, it is already correct */ + return LY_ENOTFOUND; +} + +/** + * @brief Search for a record with specific value and hash. + * + * @param[in] ht Hash table to search in. + * @param[in] val_p Pointer to the value to find. + * @param[in] hash Hash to find. + * @param[in] mod Whether the operation modifies the hash table (insert or remove) or not (find). + * @param[out] crec_p Optional found first record. + * @param[out] col Optional collision number of @p rec_p, 0 for no collision. + * @param[out] rec_p Found exact matching record, may be a collision of @p crec_p. + * @return LY_ENOTFOUND if no record found, + * @return LY_SUCCESS if record was found. + */ +static LY_ERR +lyht_find_rec(struct hash_table *ht, void *val_p, uint32_t hash, ly_bool mod, struct ht_rec **crec_p, uint32_t *col, + struct ht_rec **rec_p) +{ + struct ht_rec *rec, *crec; + uint32_t i, c; + LY_ERR r; + + if (crec_p) { + *crec_p = NULL; + } + if (col) { + *col = 0; + } + *rec_p = NULL; + + if (lyht_find_first(ht, hash, &rec)) { + /* not found */ + return LY_ENOTFOUND; + } + if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) { + /* even the value matches */ + if (crec_p) { + *crec_p = rec; + } + if (col) { + *col = 0; + } + *rec_p = rec; + return LY_SUCCESS; + } + + /* some collisions, we need to go through them, too */ + crec = rec; + c = crec->hits; + for (i = 1; i < c; ++i) { + r = lyht_find_collision(ht, &rec, crec); + assert(!r); + (void)r; + + /* compare values */ + if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) { + if (crec_p) { + *crec_p = crec; + } + if (col) { + *col = i; + } + *rec_p = rec; + return LY_SUCCESS; + } + } + + /* not found even in collisions */ + return LY_ENOTFOUND; +} + +LY_ERR +lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) +{ + struct ht_rec *rec; + + lyht_find_rec(ht, val_p, hash, 0, NULL, NULL, &rec); + + if (rec && match_p) { + *match_p = rec->val; + } + return rec ? LY_SUCCESS : LY_ENOTFOUND; +} + +LY_ERR +lyht_find_next_with_collision_cb(struct hash_table *ht, void *val_p, uint32_t hash, + lyht_value_equal_cb collision_val_equal, void **match_p) +{ + struct ht_rec *rec, *crec; + uint32_t i, c; + LY_ERR r; + + /* find the record of the previously found value */ + if (lyht_find_rec(ht, val_p, hash, 1, &crec, &i, &rec)) { + /* not found, cannot happen */ + LOGINT_RET(NULL); + } + + /* go through collisions and find the next one after the previous one */ + c = crec->hits; + for (++i; i < c; ++i) { + r = lyht_find_collision(ht, &rec, crec); + assert(!r); + (void)r; + + if (rec->hash != hash) { + continue; + } + + if (collision_val_equal) { + if (collision_val_equal(val_p, &rec->val, 0, ht->cb_data)) { + /* even the value matches */ + if (match_p) { + *match_p = rec->val; + } + return LY_SUCCESS; + } + } else if (ht->val_equal(val_p, &rec->val, 0, ht->cb_data)) { + /* even the value matches */ + if (match_p) { + *match_p = rec->val; + } + return LY_SUCCESS; + } + } + + /* the last equal value was already returned */ + return LY_ENOTFOUND; +} + +LY_ERR +lyht_find_next(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) +{ + return lyht_find_next_with_collision_cb(ht, val_p, hash, NULL, match_p); +} + +LY_ERR +lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal, + void **match_p) +{ + LY_ERR r, ret = LY_SUCCESS; + struct ht_rec *rec, *crec = NULL; + int32_t i; + lyht_value_equal_cb old_val_equal = NULL; + + if (!lyht_find_first(ht, hash, &rec)) { + /* we found matching shortened hash */ + if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { + /* even the value matches */ + if (match_p) { + *match_p = (void *)&rec->val; + } + return LY_EEXIST; + } + + /* some collisions, we need to go through them, too */ + crec = rec; + for (i = 1; i < crec->hits; ++i) { + r = lyht_find_collision(ht, &rec, crec); + assert(!r); + + /* compare values */ + if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { + if (match_p) { + *match_p = (void *)&rec->val; + } + return LY_EEXIST; + } + } + + /* value not found, get the record where it will be inserted */ + r = lyht_find_collision(ht, &rec, crec); + assert(r); + } + + /* insert it into the returned record */ + assert(rec->hits < 1); + if (rec->hits < 0) { + --ht->invalid; + } + rec->hash = hash; + rec->hits = 1; + memcpy(&rec->val, val_p, ht->rec_size - (sizeof(struct ht_rec) - 1)); + if (match_p) { + *match_p = (void *)&rec->val; + } + + if (crec) { + /* there was a collision, increase hits */ + if (crec->hits == INT32_MAX) { + LOGINT(NULL); + } + ++crec->hits; + } + + /* check size & enlarge if needed */ + ++ht->used; + if (ht->resize) { + r = (ht->used * LYHT_HUNDRED_PERCENTAGE) / ht->size; + if ((ht->resize == 1) && (r >= LYHT_FIRST_SHRINK_PERCENTAGE)) { + /* enable shrinking */ + ht->resize = 2; + } + if ((ht->resize == 2) && (r >= LYHT_ENLARGE_PERCENTAGE)) { + if (resize_val_equal) { + old_val_equal = lyht_set_cb(ht, resize_val_equal); + } + + /* enlarge */ + ret = lyht_resize(ht, 1); + /* if hash_table was resized, we need to find new matching value */ + if ((ret == LY_SUCCESS) && match_p) { + lyht_find(ht, val_p, hash, match_p); + } + + if (resize_val_equal) { + lyht_set_cb(ht, old_val_equal); + } + } + } + return ret; +} + +LY_ERR +lyht_insert(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) +{ + return lyht_insert_with_resize_cb(ht, val_p, hash, NULL, match_p); +} + +LY_ERR +lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal) +{ + struct ht_rec *rec, *crec; + int32_t i; + ly_bool first_matched = 0; + LY_ERR r, ret = LY_SUCCESS; + lyht_value_equal_cb old_val_equal; + + LY_CHECK_ERR_RET(lyht_find_first(ht, hash, &rec), LOGARG(NULL, hash), LY_ENOTFOUND); /* hash not found */ + + if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { + /* even the value matches */ + first_matched = 1; + } + + /* we always need to go through collisions */ + crec = rec; + for (i = 1; i < crec->hits; ++i) { + r = lyht_find_collision(ht, &rec, crec); + assert(!r); + + /* compare values */ + if (!first_matched && (rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { + break; + } + } + + if (i < crec->hits) { + /* one of collisions matched, reduce collision count, remove the record */ + assert(!first_matched); + --crec->hits; + rec->hits = -1; + } else if (first_matched) { + /* the first record matches */ + if (crec != rec) { + /* ... so put the last collision in its place */ + rec->hits = crec->hits - 1; + memcpy(crec, rec, ht->rec_size); + } + rec->hits = -1; + } else { + /* value not found even in collisions */ + return LY_ENOTFOUND; + } + + /* check size & shrink if needed */ + --ht->used; + ++ht->invalid; + if (ht->resize == 2) { + r = (ht->used * LYHT_HUNDRED_PERCENTAGE) / ht->size; + if ((r < LYHT_SHRINK_PERCENTAGE) && (ht->size > LYHT_MIN_SIZE)) { + if (resize_val_equal) { + old_val_equal = lyht_set_cb(ht, resize_val_equal); + } + + /* shrink */ + ret = lyht_resize(ht, -1); + + if (resize_val_equal) { + lyht_set_cb(ht, old_val_equal); + } + } + } + + /* rehash all records if needed */ + r = ((ht->size - ht->used - ht->invalid) * 100) / ht->size; + if (r < LYHT_REHASH_PERCENTAGE) { + if (resize_val_equal) { + old_val_equal = lyht_set_cb(ht, resize_val_equal); + } + + /* rehash */ + ret = lyht_resize(ht, 0); + + if (resize_val_equal) { + lyht_set_cb(ht, old_val_equal); + } + } + + return ret; +} + +LY_ERR +lyht_remove(struct hash_table *ht, void *val_p, uint32_t hash) +{ + return lyht_remove_with_resize_cb(ht, val_p, hash, NULL); +} + +uint32_t +lyht_get_fixed_size(uint32_t item_count) +{ + uint32_t i, size = 0; + + /* detect number of upper zero bits in the items' counter value ... */ + for (i = (sizeof item_count * CHAR_BIT) - 1; i > 0; i--) { + size = item_count << i; + size = size >> i; + if (size == item_count) { + break; + } + } + assert(i); + + /* ... and then we convert it to the position of the highest non-zero bit ... */ + i = (sizeof item_count * CHAR_BIT) - i; + + /* ... and by using it to shift 1 to the left we get the closest sufficient hash table size */ + size = 1 << i; + + return size; +} diff --git a/src/hash_table.h b/src/hash_table.h new file mode 100644 index 0000000..91ae63d --- /dev/null +++ b/src/hash_table.h @@ -0,0 +1,279 @@ +/** + * @file hash_table.h + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang hash table + * + * Copyright (c) 2015 - 2022 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_HASH_TABLE_H_ +#define LY_HASH_TABLE_H_ + +#include +#include +#include + +#include "compat.h" +#include "log.h" + +/** + * @brief Compute hash from (several) string(s). + * + * Usage: + * - init hash to 0 + * - repeatedly call ::dict_hash_multi(), provide hash from the last call + * - call ::dict_hash_multi() with key_part = NULL to finish the hash + */ +uint32_t dict_hash_multi(uint32_t hash, const char *key_part, size_t len); + +/** + * @brief Compute hash from a string. + */ +uint32_t dict_hash(const char *key, size_t len); + +/** + * @brief Callback for checking hash table values equivalence. + * + * @param[in] val1_p Pointer to the first value, the one being searched (inserted/removed). + * @param[in] val2_p Pointer to the second value, the one stored in the hash table. + * @param[in] mod Whether the operation modifies the hash table (insert or remove) or not (find). + * @param[in] cb_data User callback data. + * @return false (non-equal) or true (equal). + */ +typedef ly_bool (*lyht_value_equal_cb)(void *val1_p, void *val2_p, ly_bool mod, void *cb_data); + +/** reference value for 100% */ +#define LYHT_HUNDRED_PERCENTAGE 100 + +/** when the table is at least this much percent full, it is enlarged (double the size) */ +#define LYHT_ENLARGE_PERCENTAGE 75 + +/** only once the table is this much percent full, enable shrinking */ +#define LYHT_FIRST_SHRINK_PERCENTAGE 50 + +/** when the table is less than this much percent full, it is shrunk (half the size) */ +#define LYHT_SHRINK_PERCENTAGE 25 + +/** when the table has less than this much percent empty records, it is rehashed to get rid of all the invalid records */ +#define LYHT_REHASH_PERCENTAGE 2 + +/** never shrink beyond this size */ +#define LYHT_MIN_SIZE 8 + +/** + * @brief Generic hash table record. + */ +struct ht_rec { + uint32_t hash; /* hash of the value */ + int32_t hits; /* collision/overflow value count - 1 (a filled entry has 1 hit, + * special value -1 means a deleted record) */ + unsigned char val[1]; /* arbitrary-size value */ +} _PACKED; + +/** + * @brief (Very) generic hash table. + * + * Hash table with open addressing collision resolution and + * linear probing of interval 1 (next free record is used). + * Removal is lazy (removed records are only marked), but + * if possible, they are fully emptied. + */ +struct hash_table { + uint32_t used; /* number of values stored in the hash table (filled records) */ + uint32_t size; /* always holds 2^x == size (is power of 2), actually number of records allocated */ + uint32_t invalid; /* number of invalid records (deleted) */ + lyht_value_equal_cb val_equal; /* callback for testing value equivalence */ + void *cb_data; /* user data callback arbitrary value */ + uint16_t resize; /* 0 - resizing is disabled, * + * 1 - enlarging is enabled, * + * 2 - both shrinking and enlarging is enabled */ + uint16_t rec_size; /* real size (in bytes) of one record for accessing recs array */ + unsigned char *recs; /* pointer to the hash table itself (array of struct ht_rec) */ +}; + +struct dict_rec { + char *value; + uint32_t refcount; +}; + +/** + * dictionary to store repeating strings + */ +struct dict_table { + struct hash_table *hash_tab; + pthread_mutex_t lock; +}; + +/** + * @brief Initiate content (non-zero values) of the dictionary + * + * @param[in] dict Dictionary table to initiate + */ +void lydict_init(struct dict_table *dict); + +/** + * @brief Cleanup the dictionary content + * + * @param[in] dict Dictionary table to cleanup + */ +void lydict_clean(struct dict_table *dict); + +/** + * @brief Create new hash table. + * + * @param[in] size Starting size of the hash table (capacity of values), must be power of 2. + * @param[in] val_size Size in bytes of value (the stored hashed item). + * @param[in] val_equal Callback for checking value equivalence. + * @param[in] cb_data User data always passed to @p val_equal. + * @param[in] resize Whether to resize the table on too few/too many records taken. + * @return Empty hash table, NULL on error. + */ +struct hash_table *lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void *cb_data, uint16_t resize); + +/** + * @brief Set hash table value equal callback. + * + * @param[in] ht Hash table to modify. + * @param[in] new_val_equal New callback for checking value equivalence. + * @return Previous callback for checking value equivalence. + */ +lyht_value_equal_cb lyht_set_cb(struct hash_table *ht, lyht_value_equal_cb new_val_equal); + +/** + * @brief Set hash table value equal callback user data. + * + * @param[in] ht Hash table to modify. + * @param[in] new_cb_data New data for values callback. + * @return Previous data for values callback. + */ +void *lyht_set_cb_data(struct hash_table *ht, void *new_cb_data); + +/** + * @brief Make a duplicate of an existing hash table. + * + * @param[in] orig Original hash table to duplicate. + * @return Duplicated hash table @p orig, NULL on error. + */ +struct hash_table *lyht_dup(const struct hash_table *orig); + +/** + * @brief Free a hash table. + * + * @param[in] ht Hash table to be freed. + */ +void lyht_free(struct hash_table *ht); + +/** + * @brief Find a value in a hash table. + * + * @param[in] ht Hash table to search in. + * @param[in] val_p Pointer to the value to find. + * @param[in] hash Hash of the stored value. + * @param[out] match_p Pointer to the matching value, optional. + * @return LY_SUCCESS if value was found, + * @return LY_ENOTFOUND if not found. + */ +LY_ERR lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p); + +/** + * @brief Find another equal value in the hash table. + * + * @param[in] ht Hash table to search in. + * @param[in] val_p Pointer to the previously found value in @p ht. + * @param[in] hash Hash of the previously found value. + * @param[out] match_p Pointer to the matching value, optional. + * @return LY_SUCCESS if value was found, + * @return LY_ENOTFOUND if not found. + */ +LY_ERR lyht_find_next(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p); + +/** + * @brief Find another equal value in the hash table. Same functionality as ::lyht_find_next() + * but allows to specify a collision val equal callback to be used for checking for matching colliding values. + * + * @param[in] ht Hash table to search in. + * @param[in] val_p Pointer to the previously found value in @p ht. + * @param[in] hash Hash of the previously found value. + * @param[in] collision_val_equal Val equal callback to use for checking collisions. + * @param[out] match_p Pointer to the matching value, optional. + * @return LY_SUCCESS if value was found, + * @return LY_ENOTFOUND if not found. + */ +LY_ERR lyht_find_next_with_collision_cb(struct hash_table *ht, void *val_p, uint32_t hash, + lyht_value_equal_cb collision_val_equal, void **match_p); + +/** + * @brief Insert a value into a hash table. + * + * @param[in] ht Hash table to insert into. + * @param[in] val_p Pointer to the value to insert. Be careful, if the values stored in the hash table + * are pointers, @p val_p must be a pointer to a pointer. + * @param[in] hash Hash of the stored value. + * @param[out] match_p Pointer to the stored value, optional + * @return LY_SUCCESS on success, + * @return LY_EEXIST in case the value is already present. + * @return LY_EMEM in case of memory allocation failure. + */ +LY_ERR lyht_insert(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p); + +/** + * @brief Insert a value into hash table. Same functionality as ::lyht_insert() + * but allows to specify a temporary val equal callback to be used in case the hash table + * will be resized after successful insertion. + * + * @param[in] ht Hash table to insert into. + * @param[in] val_p Pointer to the value to insert. Be careful, if the values stored in the hash table + * are pointers, @p val_p must be a pointer to a pointer. + * @param[in] hash Hash of the stored value. + * @param[in] resize_val_equal Val equal callback to use for resizing. + * @param[out] match_p Pointer to the stored value, optional + * @return LY_SUCCESS on success, + * @return LY_EEXIST in case the value is already present. + * @return LY_EMEM in case of memory allocation failure. + */ +LY_ERR lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal, + void **match_p); + +/** + * @brief Remove a value from a hash table. + * + * @param[in] ht Hash table to remove from. + * @param[in] val_p Pointer to value to be removed. Be careful, if the values stored in the hash table + * are pointers, @p val_p must be a pointer to a pointer. + * @param[in] hash Hash of the stored value. + * @return LY_SUCCESS on success, + * @return LY_ENOTFOUND if value was not found. + */ +LY_ERR lyht_remove(struct hash_table *ht, void *val_p, uint32_t hash); + +/** + * @brief Remove a value from a hash table. Same functionality as ::lyht_remove() + * but allows to specify a temporary val equal callback to be used in case the hash table + * will be resized after successful removal. + * + * @param[in] ht Hash table to remove from. + * @param[in] val_p Pointer to value to be removed. Be careful, if the values stored in the hash table + * are pointers, @p val_p must be a pointer to a pointer. + * @param[in] hash Hash of the stored value. + * @param[in] resize_val_equal Val equal callback to use for resizing. + * @return LY_SUCCESS on success, + * @return LY_ENOTFOUND if value was not found. + */ +LY_ERR lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal); + +/** + * @brief Get suitable size of a hash table for a fixed number of items. + * + * @param[in] item_count Number of stored items. + * @return Hash table size. + */ +uint32_t lyht_get_fixed_size(uint32_t item_count); + +#endif /* LY_HASH_TABLE_H_ */ diff --git a/src/in.c b/src/in.c new file mode 100644 index 0000000..431c10a --- /dev/null +++ b/src/in.c @@ -0,0 +1,329 @@ +/** + * @file in.c + * @author Radek Krejci + * @brief libyang input functions. + * + * Copyright (c) 2015 - 2020 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 +#define _POSIX_C_SOURCE 200809L /* strdup, strndup */ + +#include "in.h" +#include "in_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "dict.h" +#include "log.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" + +LIBYANG_API_DEF LY_IN_TYPE +ly_in_type(const struct ly_in *in) +{ + LY_CHECK_ARG_RET(NULL, in, LY_IN_ERROR); + return in->type; +} + +LIBYANG_API_DEF LY_ERR +ly_in_reset(struct ly_in *in) +{ + LY_CHECK_ARG_RET(NULL, in, LY_EINVAL); + + in->current = in->func_start = in->start; + in->line = 1; + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_in_new_fd(int fd, struct ly_in **in) +{ + size_t length; + char *addr; + + LY_CHECK_ARG_RET(NULL, fd >= 0, in, LY_EINVAL); + + LY_CHECK_RET(ly_mmap(NULL, fd, &length, (void **)&addr)); + if (!addr) { + LOGERR(NULL, LY_EINVAL, "Empty input file."); + return LY_EINVAL; + } + + *in = calloc(1, sizeof **in); + LY_CHECK_ERR_RET(!*in, LOGMEM(NULL); ly_munmap(addr, length), LY_EMEM); + + (*in)->type = LY_IN_FD; + (*in)->method.fd = fd; + (*in)->current = (*in)->start = (*in)->func_start = addr; + (*in)->line = 1; + (*in)->length = length; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF int +ly_in_fd(struct ly_in *in, int fd) +{ + int prev_fd; + size_t length; + const char *addr; + + LY_CHECK_ARG_RET(NULL, in, in->type == LY_IN_FD, -1); + + prev_fd = in->method.fd; + + if (fd != -1) { + LY_CHECK_RET(ly_mmap(NULL, fd, &length, (void **)&addr), -1); + if (!addr) { + LOGERR(NULL, LY_EINVAL, "Empty input file."); + return -1; + } + + ly_munmap((char *)in->start, in->length); + + in->method.fd = fd; + in->current = in->start = addr; + in->line = 1; + in->length = length; + } + + return prev_fd; +} + +LIBYANG_API_DEF LY_ERR +ly_in_new_file(FILE *f, struct ly_in **in) +{ + LY_CHECK_ARG_RET(NULL, f, in, LY_EINVAL); + + LY_CHECK_RET(ly_in_new_fd(fileno(f), in)); + + /* convert the LY_IN_FD input handler into the LY_IN_FILE */ + (*in)->type = LY_IN_FILE; + (*in)->method.f = f; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF FILE * +ly_in_file(struct ly_in *in, FILE *f) +{ + FILE *prev_f; + + LY_CHECK_ARG_RET(NULL, in, in->type == LY_IN_FILE, NULL); + + prev_f = in->method.f; + + if (f) { + /* convert LY_IN_FILE handler into LY_IN_FD to be able to update it via ly_in_fd() */ + in->type = LY_IN_FD; + in->method.fd = fileno(prev_f); + if (ly_in_fd(in, fileno(f)) == -1) { + in->type = LY_IN_FILE; + in->method.f = prev_f; + return NULL; + } + + /* if success, convert the result back */ + in->type = LY_IN_FILE; + in->method.f = f; + } + + return prev_f; +} + +LIBYANG_API_DEF LY_ERR +ly_in_new_memory(const char *str, struct ly_in **in) +{ + LY_CHECK_ARG_RET(NULL, str, in, LY_EINVAL); + + *in = calloc(1, sizeof **in); + LY_CHECK_ERR_RET(!*in, LOGMEM(NULL), LY_EMEM); + + (*in)->type = LY_IN_MEMORY; + (*in)->start = (*in)->current = (*in)->func_start = str; + (*in)->line = 1; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF const char * +ly_in_memory(struct ly_in *in, const char *str) +{ + const char *data; + + LY_CHECK_ARG_RET(NULL, in, in->type == LY_IN_MEMORY, NULL); + + data = in->current; + + if (str) { + in->start = in->current = str; + in->line = 1; + } + + return data; +} + +LIBYANG_API_DEF LY_ERR +ly_in_new_filepath(const char *filepath, size_t len, struct ly_in **in) +{ + LY_ERR ret; + char *fp; + int fd; + + LY_CHECK_ARG_RET(NULL, filepath, in, LY_EINVAL); + + if (len) { + fp = strndup(filepath, len); + } else { + fp = strdup(filepath); + } + + fd = open(fp, O_RDONLY); + LY_CHECK_ERR_RET(fd == -1, LOGERR(NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", fp, strerror(errno)); free(fp), + LY_ESYS); + + LY_CHECK_ERR_RET(ret = ly_in_new_fd(fd, in), free(fp), ret); + + /* convert the LY_IN_FD input handler into the LY_IN_FILE */ + (*in)->type = LY_IN_FILEPATH; + (*in)->method.fpath.fd = fd; + (*in)->method.fpath.filepath = fp; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF const char * +ly_in_filepath(struct ly_in *in, const char *filepath, size_t len) +{ + int fd, prev_fd; + char *fp = NULL; + + LY_CHECK_ARG_RET(NULL, in, in->type == LY_IN_FILEPATH, filepath ? NULL : ((void *)-1)); + + if (!filepath) { + return in->method.fpath.filepath; + } + + if (len) { + fp = strndup(filepath, len); + } else { + fp = strdup(filepath); + } + + /* replace filepath */ + fd = open(fp, O_RDONLY); + LY_CHECK_ERR_RET(!fd, LOGERR(NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", fp, strerror(errno)); free(fp), NULL); + + /* convert LY_IN_FILEPATH handler into LY_IN_FD to be able to update it via ly_in_fd() */ + in->type = LY_IN_FD; + prev_fd = ly_in_fd(in, fd); + LY_CHECK_ERR_RET(prev_fd == -1, in->type = LY_IN_FILEPATH; free(fp), NULL); + + /* and convert the result back */ + in->type = LY_IN_FILEPATH; + close(prev_fd); + free(in->method.fpath.filepath); + in->method.fpath.fd = fd; + in->method.fpath.filepath = fp; + + return NULL; +} + +LIBYANG_API_DEF size_t +ly_in_parsed(const struct ly_in *in) +{ + LY_CHECK_ARG_RET(NULL, in, 0); + + return in->current - in->func_start; +} + +LIBYANG_API_DEF void +ly_in_free(struct ly_in *in, ly_bool destroy) +{ + if (!in) { + return; + } else if (in->type == LY_IN_ERROR) { + LOGINT(NULL); + return; + } + + if (destroy) { + if (in->type == LY_IN_MEMORY) { + free((char *)in->start); + } else { + ly_munmap((char *)in->start, in->length); + + if (in->type == LY_IN_FILE) { + fclose(in->method.f); + } else { + close(in->method.fd); + + if (in->type == LY_IN_FILEPATH) { + free(in->method.fpath.filepath); + } + } + } + } else if (in->type != LY_IN_MEMORY) { + ly_munmap((char *)in->start, in->length); + + if (in->type == LY_IN_FILEPATH) { + close(in->method.fpath.fd); + free(in->method.fpath.filepath); + } + } + + free(in); +} + +LIBYANG_API_DEF LY_ERR +ly_in_read(struct ly_in *in, void *buf, size_t count) +{ + LY_CHECK_ARG_RET(NULL, in, buf, LY_EINVAL); + + if (in->length && (in->length - (in->current - in->start) < count)) { + /* EOF */ + return LY_EDENIED; + } + + if (count) { + memcpy(buf, in->current, count); + } + in->current += count; + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_in_skip(struct ly_in *in, size_t count) +{ + LY_CHECK_ARG_RET(NULL, in, LY_EINVAL); + + if (in->length && (in->length - (in->current - in->start) < count)) { + /* EOF */ + return LY_EDENIED; + } + + in->current += count; + return LY_SUCCESS; +} diff --git a/src/in.h b/src/in.h new file mode 100644 index 0000000..62f5aae --- /dev/null +++ b/src/in.h @@ -0,0 +1,252 @@ +/** + * @file in.h + * @author Radek Krejci + * @brief libyang input structures and functions + * + * Copyright (c) 2020 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_IN_H_ +#define LY_IN_H_ + +#include + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page howtoInput Input Processing + * + * libyang provides a mechanism to generalize work with the inputs (and [outputs](@ref howtoOutput)) of + * the different types. The ::ly_in handler can be created providing necessary information connected with the specific + * input type and then used throughout the parser functions processing the input data. Using a generic input handler avoids + * need to have a set of functions for each parser functionality and results in simpler API. + * + * The API allows to alter the source of the data behind the handler by another source. Also resetting a seekable source + * input is possible with ::ly_in_reset() to re-read the input. + * + * @note + * Currently, libyang supports only reading data from standard (disk) file, not from sockets, pipes, etc. The reason is + * that the parsers expects all the data to be present in the file (input data are complete). In future, we would like + * to change the internal mechanism and support sequential processing of the input data. In XML wording - we have DOM + * parser, but in future we would like to move to something like a SAX parser. + * + * @note + * This mechanism was introduced in libyang 2.0. To simplify transition from libyang 1.0 to version 2.0 and also for + * some simple use case where using the input handler would be an overkill, there are some basic parsers functions + * that do not require input handler. But remember, that functionality of these function can be limited in particular cases + * in contrast to the functions using input handlers. + * + * Functions List + * -------------- + * - ::ly_in_new_fd() + * - ::ly_in_new_file() + * - ::ly_in_new_filepath() + * - ::ly_in_new_memory() + * + * - ::ly_in_fd() + * - ::ly_in_file() + * - ::ly_in_filepath() + * - ::ly_in_memory() + * + * - ::ly_in_type() + * - ::ly_in_parsed() + * + * - ::ly_in_reset() + * - ::ly_in_free() + * + * libyang Parsers List + * -------------------- + * - @subpage howtoSchemaParsers + * - @subpage howtoDataParsers + */ + +/** + * @struct ly_in + * @brief Parser input structure specifying where the data are read. + */ +struct ly_in; + +/** + * @brief Types of the parser's inputs + */ +typedef enum LY_IN_TYPE { + LY_IN_ERROR = -1, /**< error value to indicate failure of the functions returning LY_IN_TYPE */ + LY_IN_FD, /**< file descriptor printer */ + LY_IN_FILE, /**< FILE stream parser */ + LY_IN_FILEPATH, /**< filepath parser */ + LY_IN_MEMORY /**< memory parser */ +} LY_IN_TYPE; + +/** + * @brief Get input type of the input handler. + * + * @param[in] in Input handler. + * @return Type of the parser's input. + */ +LIBYANG_API_DECL LY_IN_TYPE ly_in_type(const struct ly_in *in); + +/** + * @brief Reset the input medium to read from its beginning, so the following parser function will read from the object's beginning. + * + * Note that in case the underlying output is not seekable (stream referring a pipe/FIFO/socket or the callback output type), + * nothing actually happens despite the function succeeds. Also note that the medium is not returned to the state it was when + * the handler was created. For example, file is seeked into the offset zero, not to the offset where it was opened when + * ::ly_in_new_file() was called. + * + * @param[in] in Input handler. + * @return LY_SUCCESS in case of success + * @return LY_ESYS in case of failure + */ +LIBYANG_API_DECL LY_ERR ly_in_reset(struct ly_in *in); + +/** + * @brief Create input handler using file descriptor. + * + * @param[in] fd File descriptor to use. + * @param[out] in Created input handler supposed to be passed to different ly*_parse() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_in_new_fd(int fd, struct ly_in **in); + +/** + * @brief Get or reset file descriptor input handler. + * + * @param[in] in Input handler. + * @param[in] fd Optional value of a new file descriptor for the handler. If -1, only the current file descriptor value is returned. + * @return Previous value of the file descriptor. Note that caller is responsible for closing the returned file descriptor in case of setting new descriptor @p fd. + * @return -1 in case of error when setting up the new file descriptor. + */ +LIBYANG_API_DECL int ly_in_fd(struct ly_in *in, int fd); + +/** + * @brief Create input handler using file stream. + * + * @param[in] f File stream to use. + * @param[out] in Created input handler supposed to be passed to different ly*_parse() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_in_new_file(FILE *f, struct ly_in **in); + +/** + * @brief Get or reset file stream input handler. + * + * @param[in] in Input handler. + * @param[in] f Optional new file stream for the handler. If NULL, only the current file stream is returned. + * @return NULL in case of invalid argument or an error when setting up the new input file, original input handler @p in is untouched in this case. + * @return Previous file stream of the handler. Note that caller is responsible for closing the returned stream in case of setting new stream @p f. + */ +LIBYANG_API_DECL FILE *ly_in_file(struct ly_in *in, FILE *f); + +/** + * @brief Create input handler using memory to read data. + * + * @param[in] str Pointer where to start reading data. The input data are expected to be NULL-terminated. + * Note that in case the destroy argument of ::ly_in_free() is used, the input string is passed to free(), + * so if it is really a static string, do not use the destroy argument! + * @param[out] in Created input handler supposed to be passed to different ly*_parse() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_in_new_memory(const char *str, struct ly_in **in); + +/** + * @brief Get or change memory where the data are read from. + * + * @param[in] in Input handler. + * @param[in] str String containing the data to read. The input data are expected to be NULL-terminated. + * Note that in case the destroy argument of ::ly_in_free() is used, the input string is passed to free(), + * so if it is really a static string, do not use the destroy argument! + * @return Previous starting address to read data from. Note that the caller is responsible to free + * the data in case of changing string pointer @p str. + */ +LIBYANG_API_DECL const char *ly_in_memory(struct ly_in *in, const char *str); + +/** + * @brief Create input handler file of the given filename. + * + * @param[in] filepath Path of the file where to read data. + * @param[in] len Optional number of bytes to use from @p filepath. If 0, the @p filepath is considered to be NULL-terminated and + * the whole string is taken into account. + * @param[out] in Created input handler supposed to be passed to different ly*_parse() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_in_new_filepath(const char *filepath, size_t len, struct ly_in **in); + +/** + * @brief Get or change the filepath of the file where the parser reads the data. + * + * Note that in case of changing the filepath, the current file is closed and a new one is + * created/opened instead of renaming the previous file. Also note that the previous filepath + * string is returned only in case of not changing it's value. + * + * @param[in] in Input handler. + * @param[in] filepath Optional new filepath for the handler. If and only if NULL, the current filepath string is returned. + * @param[in] len Optional number of bytes to use from @p filepath. If 0, the @p filepath is considered to be NULL-terminated and + * the whole string is taken into account. + * @return Previous filepath string in case the @p filepath argument is NULL. + * @return NULL if changing filepath succeedes and ((void *)-1) otherwise. + */ +LIBYANG_API_DECL const char *ly_in_filepath(struct ly_in *in, const char *filepath, size_t len); + +/** + * @brief Get the number of parsed bytes by the last function. + * + * @param[in] in In structure used. + * @return Number of parsed bytes. + */ +LIBYANG_API_DECL size_t ly_in_parsed(const struct ly_in *in); + +/** + * @brief Free the input handler. + * + * @param[in] in Input handler to free. + * @param[in] destroy Flag to free the input data buffer (for LY_IN_MEMORY) or to + * close stream/file descriptor (for LY_IN_FD and LY_IN_FILE) + */ +LIBYANG_API_DECL void ly_in_free(struct ly_in *in, ly_bool destroy); + +/** + * @brief Read bytes from an input. + * + * Does not count new lines, which is expected from the caller who has better idea about + * the content of the read data and can better optimize counting. + * + * @param[in] in Input structure. + * @param[in] buf Destination buffer. + * @param[in] count Number of bytes to read. + * @return LY_SUCCESS on success, + * @return LY_EDENIED on EOF. + */ +LIBYANG_API_DECL LY_ERR ly_in_read(struct ly_in *in, void *buf, size_t count); + +/** + * @brief Just skip bytes in an input. + * + * Does not count new lines, which is expected from the caller who has better idea about + * the content of the skipped data and can better optimize counting. + * + * @param[in] in Input structure. + * @param[in] count Number of bytes to skip. + * @return LY_SUCCESS on success, + * @return LY_EDENIED on EOF. + */ +LIBYANG_API_DECL LY_ERR ly_in_skip(struct ly_in *in, size_t count); + +#ifdef __cplusplus +} +#endif + +#endif /* LY_IN_H_ */ diff --git a/src/in_internal.h b/src/in_internal.h new file mode 100644 index 0000000..cbc56da --- /dev/null +++ b/src/in_internal.h @@ -0,0 +1,50 @@ +/** + * @file in_internal.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Internal structures and functions for libyang parsers + * + * 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_IN_INTERNAL_H_ +#define LY_IN_INTERNAL_H_ + +#include "in.h" + +/** + * @brief Parser input structure specifying where the data are read. + */ +struct ly_in { + LY_IN_TYPE type; /**< type of the output to select the output method */ + const char *current; /**< Current position in the input data */ + const char *func_start; /**< Input data position when the last parser function was executed */ + const char *start; /**< Input data start */ + size_t length; /**< mmap() length (if used) */ + + union { + int fd; /**< file descriptor for LY_IN_FD type */ + FILE *f; /**< file structure for LY_IN_FILE and LY_IN_FILEPATH types */ + + struct { + int fd; /**< file descriptor for LY_IN_FILEPATH */ + char *filepath; /**< stored original filepath */ + } fpath; /**< filepath structure for LY_IN_FILEPATH */ + } method; /**< type-specific information about the output */ + uint64_t line; /**< current line of the input */ +}; + +/** + * @brief Increment line counter. + * @param[in] IN The input handler. + */ +#define LY_IN_NEW_LINE(IN) \ + (IN)->line++ + +#endif /* LY_IN_INTERNAL_H_ */ diff --git a/src/json.c b/src/json.c new file mode 100644 index 0000000..5c45c8c --- /dev/null +++ b/src/json.c @@ -0,0 +1,1047 @@ +/** + * @file json.c + * @author Radek Krejci + * @brief Generic JSON format parser for libyang + * + * Copyright (c) 2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "in_internal.h" +#include "json.h" +#include "tree_schema_internal.h" + +const char * +lyjson_token2str(enum LYJSON_PARSER_STATUS status) +{ + switch (status) { + case LYJSON_ERROR: + return "error"; + case LYJSON_ROOT: + return "document root"; + case LYJSON_FALSE: + return "false"; + case LYJSON_TRUE: + return "true"; + case LYJSON_NULL: + return "null"; + case LYJSON_OBJECT: + return "object"; + case LYJSON_OBJECT_CLOSED: + return "object closed"; + case LYJSON_OBJECT_EMPTY: + return "empty object"; + case LYJSON_ARRAY: + return "array"; + case LYJSON_ARRAY_CLOSED: + return "array closed"; + case LYJSON_ARRAY_EMPTY: + return "empty array"; + case LYJSON_NUMBER: + return "number"; + case LYJSON_STRING: + return "string"; + case LYJSON_END: + return "end of input"; + } + + return ""; +} + +static LY_ERR +skip_ws(struct lyjson_ctx *jsonctx) +{ + /* skip leading whitespaces */ + while (*jsonctx->in->current != '\0' && is_jsonws(*jsonctx->in->current)) { + if (*jsonctx->in->current == '\n') { + LY_IN_NEW_LINE(jsonctx->in); + } + ly_in_skip(jsonctx->in, 1); + } + if (*jsonctx->in->current == '\0') { + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_END); + } + + return LY_SUCCESS; +} + +/* + * @brief Set value corresponding to the current context's status + */ +static void +lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value_len, ly_bool dynamic) +{ + assert(jsonctx); + + if (jsonctx->dynamic) { + free((char *)jsonctx->value); + } + jsonctx->value = value; + jsonctx->value_len = value_len; + jsonctx->dynamic = dynamic; +} + +static LY_ERR +lyjson_check_next(struct lyjson_ctx *jsonctx) +{ + if (jsonctx->status.count == 1) { + /* top level value (JSON-text), ws expected */ + if ((*jsonctx->in->current == '\0') || is_jsonws(*jsonctx->in->current)) { + return LY_SUCCESS; + } + } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_OBJECT) { + LY_CHECK_RET(skip_ws(jsonctx)); + if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == '}')) { + return LY_SUCCESS; + } + } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_ARRAY) { + LY_CHECK_RET(skip_ws(jsonctx)); + if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == ']')) { + return LY_SUCCESS; + } + } + + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Unexpected character \"%c\" after JSON %s.", + *jsonctx->in->current, lyjson_token2str(lyjson_ctx_status(jsonctx, 0))); + return LY_EVALID; +} + +/** + * Input is expected to start after the opening quotation-mark. + * When succeeds, input is moved after the closing quotation-mark. + */ +static LY_ERR +lyjson_string_(struct lyjson_ctx *jsonctx) +{ +#define BUFSIZE 24 +#define BUFSIZE_STEP 128 + + const char *in = jsonctx->in->current, *start; + char *buf = NULL; + size_t offset; /* read offset in input buffer */ + size_t len; /* length of the output string (write offset in output buffer) */ + size_t size = 0; /* size of the output buffer */ + size_t u; + uint64_t start_line; + + assert(jsonctx); + + /* init */ + start = in; + start_line = jsonctx->in->line; + offset = len = 0; + + /* parse */ + while (in[offset]) { + if (in[offset] == '\\') { + /* escape sequence */ + const char *slash = &in[offset]; + uint32_t value; + uint8_t i = 1; + + if (!buf) { + /* prepare output buffer */ + buf = malloc(BUFSIZE); + LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); + size = BUFSIZE; + } + + /* allocate enough for the offset and next character, + * we will need 4 bytes at most since we support only the predefined + * (one-char) entities and character references */ + if (len + offset + 4 >= size) { + size_t increment; + + for (increment = BUFSIZE_STEP; len + offset + 4 >= size + increment; increment += BUFSIZE_STEP) {} + buf = ly_realloc(buf, size + increment); + LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); + size += BUFSIZE_STEP; + } + + if (offset) { + /* store what we have so far */ + memcpy(&buf[len], in, offset); + len += offset; + in += offset; + offset = 0; + } + + switch (in[++offset]) { + case '"': + /* quotation mark */ + value = 0x22; + break; + case '\\': + /* reverse solidus */ + value = 0x5c; + break; + case '/': + /* solidus */ + value = 0x2f; + break; + case 'b': + /* backspace */ + value = 0x08; + break; + case 'f': + /* form feed */ + value = 0x0c; + break; + case 'n': + /* line feed */ + value = 0x0a; + break; + case 'r': + /* carriage return */ + value = 0x0d; + break; + case 't': + /* tab */ + value = 0x09; + break; + case 'u': + /* Basic Multilingual Plane character \uXXXX */ + offset++; + for (value = i = 0; i < 4; i++) { + if (!in[offset + i]) { + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid basic multilingual plane character \"%s\".", slash); + goto error; + } else if (isdigit(in[offset + i])) { + u = (in[offset + i] - '0'); + } else if (in[offset + i] > 'F') { + u = LY_BASE_DEC + (in[offset + i] - 'a'); + } else { + u = LY_BASE_DEC + (in[offset + i] - 'A'); + } + value = (LY_BASE_HEX * value) + u; + } + break; + default: + /* invalid escape sequence */ + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character escape sequence \\%c.", in[offset]); + goto error; + + } + + offset += i; /* add read escaped characters */ + LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], value, &u), + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\" (0x%08x).", + (int)(&in[offset] - slash), slash, value), + error); + len += u; /* update number of bytes in buffer */ + in += offset; /* move the input by the processed bytes stored in the buffer ... */ + offset = 0; /* ... and reset the offset index for future moving data into buffer */ + + } else if (in[offset] == '"') { + /* end of string */ + if (buf) { + /* realloc exact size string */ + buf = ly_realloc(buf, len + offset + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); + size = len + offset + 1; + if (offset) { + memcpy(&buf[len], in, offset); + } + + /* set terminating NULL byte */ + buf[len + offset] = '\0'; + } + len += offset; + ++offset; + in += offset; + goto success; + } else { + /* get it as UTF-8 character for check */ + const char *c = &in[offset]; + uint32_t code = 0; + size_t code_len = 0; + + LY_CHECK_ERR_GOTO(ly_getutf8(&c, &code, &code_len), + LOGVAL(jsonctx->ctx, LY_VCODE_INCHAR, in[offset]), error); + + LY_CHECK_ERR_GOTO(!is_jsonstrchar(code), + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON string \"%.*s\" (0x%08x).", + (int)(&in[offset] - start + code_len), start, code), + error); + + /* character is ok, continue */ + offset += code_len; + } + } + + /* EOF reached before endchar */ + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + LOGVAL_LINE(jsonctx->ctx, start_line, LYVE_SYNTAX, "Missing quotation-mark at the end of a JSON string."); + +error: + free(buf); + return LY_EVALID; + +success: + jsonctx->in->current = in; + if (buf) { + lyjson_ctx_set_value(jsonctx, buf, len, 1); + } else { + lyjson_ctx_set_value(jsonctx, start, len, 0); + } + + return LY_SUCCESS; + +#undef BUFSIZE +#undef BUFSIZE_STEP +} + +/* + * + * Wrapper around lyjson_string_() adding LYJSON_STRING status into context to allow using lyjson_string_() for parsing object's name. + */ +static LY_ERR +lyjson_string(struct lyjson_ctx *jsonctx) +{ + LY_CHECK_RET(lyjson_string_(jsonctx)); + + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_STRING); + LY_CHECK_RET(lyjson_check_next(jsonctx)); + + return LY_SUCCESS; +} + +/** + * @brief Calculate how many @p c characters there are in a row. + * + * @param[in] str Count from this position. + * @param[in] end Position after the last checked character. + * @param[in] c Checked character. + * @param[in] backwards Set to 1, if to proceed from end-1 to str. + * @return Number of characters in a row. + */ +static uint32_t +lyjson_count_in_row(const char *str, const char *end, char c, ly_bool backwards) +{ + uint32_t cnt; + + assert(str && end); + + if (str >= end) { + return 0; + } + + if (!backwards) { + for (cnt = 0; (str != end) && (*str == c); ++str, ++cnt) {} + } else { + --end; + --str; + for (cnt = 0; (str != end) && (*end == c); --end, ++cnt) {} + } + + return cnt; +} + +/** + * @brief Check if the number can be shortened to zero. + * + * @param[in] in Start of input string; + * @param[in] end End of input string; + * @return 1 if number is zero, otherwise 0. + */ +static ly_bool +lyjson_number_is_zero(const char *in, const char *end) +{ + assert(in < end); + + if ((in[0] == '-') || (in[0] == '+')) { + in++; + assert(in < end); + } + if ((in[0] == '0') && (in[1] == '.')) { + in += 2; + if (!(in < end)) { + return 1; + } + } + + return lyjson_count_in_row(in, end, '0', 0) == end - in; +} + +/** + * @brief Allocate buffer for number in string format. + * + * @param[in] jsonctx JSON context. + * @param[in] num_len Required space in bytes for a number. + * Terminating null byte is added by default. + * @param[out] buffer Output allocated buffer. + * @return LY_ERR value. + */ +static LY_ERR +lyjson_get_buffer_for_number(const struct ly_ctx *ctx, uint64_t num_len, char **buffer) +{ + *buffer = NULL; + + LY_CHECK_ERR_RET((num_len + 1) > LY_NUMBER_MAXLEN, LOGVAL(ctx, LYVE_SEMANTICS, + "Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit."), LY_EVALID); + + /* allocate buffer for the result (add NULL-byte) */ + *buffer = malloc(num_len + 1); + LY_CHECK_ERR_RET(!(*buffer), LOGMEM(ctx), LY_EMEM); + return LY_SUCCESS; +} + +/** + * @brief Copy the 'numeric part' (@p num) except its decimal point + * (@p dec_point) and insert the new decimal point (@p dp_position) + * only if it is to be placed in the 'numeric part' range (@p num). + * + * @param[in] num Begin of the 'numeric part'. + * @param[in] num_len Length of the 'numeric part'. + * @param[in] dec_point Pointer to the old decimal point. + * If it has a NULL value, it is ignored. + * @param[in] dp_position Position of the new decimal point. + * If it has a negative value, it is ignored. + * @param[out] dst Memory into which the copied result is written. + * @return Number of characters written to the @p dst. + */ +static uint32_t +lyjson_exp_number_copy_num_part(const char *num, uint32_t num_len, + char *dec_point, int32_t dp_position, char *dst) +{ + int32_t dec_point_idx; + int32_t n, d; + + assert(num && dst); + + dec_point_idx = dec_point ? dec_point - num : INT32_MAX; + assert((dec_point_idx >= 0) && (dec_point_idx != dp_position)); + + for (n = 0, d = 0; (uint32_t)n < num_len; n++) { + if (n == dec_point_idx) { + continue; + } else if (d == dp_position) { + dst[d++] = '.'; + dst[d++] = num[n]; + } else { + dst[d++] = num[n]; + } + } + + return d; +} + +/** + * @brief Convert JSON number with exponent into the representation + * used by YANG. + * + * The input numeric string must be syntactically valid. Also, before + * calling this function, checks should be performed using the + * ::lyjson_number_is_zero(). + * + * @param[in] ctx Context for the error message. + * @param[in] in Beginning of the string containing the number. + * @param[in] exponent Pointer to the letter E/e. + * @param[in] total_len Total size of the input number. + * @param[out] res Conversion result. + * @param[out] res_len Length of the result. + * @return LY_ERR value. + */ +static LY_ERR +lyjson_exp_number(const struct ly_ctx *ctx, const char *in, const char *exponent, + uint64_t total_len, char **res, size_t *res_len) +{ + +#define MAYBE_WRITE_MINUS(ARRAY, INDEX, FLAG) \ + if (FLAG) { \ + ARRAY[INDEX++] = '-'; \ + } + +/* Length of leading zero followed by the decimal point. */ +#define LEADING_ZERO 1 + +/* Flags for the ::lyjson_count_in_row() */ +#define FORWARD 0 +#define BACKWARD 1 + + /* Buffer where the result is stored. */ + char *buf; + /* Size without space for terminating NULL-byte. */ + uint64_t buf_len; + /* Index to buf. */ + uint32_t i = 0; + /* A 'numeric part' doesn't contain a minus sign or an leading zero. + * For example, in 0.45, there is the leading zero. + */ + const char *num; + /* Length of the 'numeric part' ends before E/e. */ + uint16_t num_len; + /* Position of decimal point in the num. */ + char *dec_point; + /* Final position of decimal point in the buf. */ + int32_t dp_position; + /* Exponent as integer. */ + long long e_val; + /* Byte for the decimal point. */ + int8_t dot; + /* Required additional byte for the minus sign. */ + uint8_t minus; + /* The number of zeros. */ + long zeros; + /* If the number starts with leading zero followed by the decimal point. */ + ly_bool leading_zero; + + assert(ctx && in && exponent && res && res_len && (total_len > 2)); + assert((in < exponent) && ((*exponent == 'e') || (*exponent == 'E'))); + + if ((exponent - in) > UINT16_MAX) { + LOGVAL(ctx, LYVE_SEMANTICS, "JSON number is too long."); + return LY_EVALID; + } + + /* Convert exponent. */ + errno = 0; + e_val = strtoll(exponent + 1, NULL, LY_BASE_DEC); + if (errno || (e_val > UINT16_MAX) || (e_val < -UINT16_MAX)) { + LOGVAL(ctx, LYVE_SEMANTICS, + "Exponent out-of-bounds in a JSON Number value (%.*s).", + (int)total_len, in); + return LY_EVALID; + } + + minus = in[0] == '-'; + if (in[minus] == '0') { + assert(in[minus + 1] == '.'); + leading_zero = 1; + /* The leading zero has been found, it will be skipped. */ + num = &in[minus + 1]; + } else { + leading_zero = 0; + /* Set to the first number. */ + num = &in[minus]; + } + num_len = exponent - num; + + /* Find the location of the decimal points. */ + dec_point = ly_strnchr(num, '.', num_len); + dp_position = dec_point ? + dec_point - num + e_val : + num_len + e_val; + + /* Remove zeros after the decimal point from the end of + * the 'numeric part' because these are useless. + * (For example, in 40.001000 these are the last 3). + */ + num_len -= dp_position > 0 ? + lyjson_count_in_row(num + dp_position - 1, exponent, '0', BACKWARD) : + lyjson_count_in_row(num, exponent, '0', BACKWARD); + + /* Decide what to do with the dot from the 'numeric part'. */ + if (dec_point && ((int32_t)(num_len - 1) == dp_position)) { + /* Decimal point in the last place is useless. */ + dot = -1; + } else if (dec_point) { + /* Decimal point is shifted. */ + dot = 0; + } else { + /* Additional byte for the decimal point is requred. */ + dot = 1; + } + + /* Final composition of the result. */ + if (dp_position <= 0) { + /* Adding decimal point before the integer with adding additional zero(s). */ + + zeros = labs(dp_position); + buf_len = minus + LEADING_ZERO + dot + zeros + num_len; + LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); + MAYBE_WRITE_MINUS(buf, i, minus); + buf[i++] = '0'; + buf[i++] = '.'; + memset(buf + i, '0', zeros); + i += zeros; + dp_position = -1; + lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i); + } else if (leading_zero && (dp_position < (ssize_t)num_len)) { + /* Insert decimal point between the integer's digits. */ + + /* Set a new range of 'numeric part'. Old decimal point is skipped. */ + num++; + num_len--; + dp_position--; + /* Get the number of useless zeros between the old + * and new decimal point. For example, in the number 0.005E1, + * there is one useless zero. + */ + zeros = lyjson_count_in_row(num, num + dp_position + 1, '0', FORWARD); + /* If the new decimal point will be in the place of the first non-zero subnumber. */ + if (zeros == (dp_position + 1)) { + /* keep one zero as leading zero */ + zeros--; + /* new decimal point will be behind the leading zero */ + dp_position = 1; + dot = 1; + } else { + dot = 0; + } + buf_len = minus + dot + (num_len - zeros); + LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); + MAYBE_WRITE_MINUS(buf, i, minus); + /* Skip useless zeros and copy. */ + lyjson_exp_number_copy_num_part(num + zeros, num_len - zeros, NULL, dp_position, buf + i); + } else if (dp_position < (ssize_t)num_len) { + /* Insert decimal point between the integer's digits. */ + + buf_len = minus + dot + num_len; + LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); + MAYBE_WRITE_MINUS(buf, i, minus); + lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i); + } else if (leading_zero) { + /* Adding decimal point after the decimal value make the integer result. */ + + /* Set a new range of 'numeric part'. Old decimal point is skipped. */ + num++; + num_len--; + /* Get the number of useless zeros. */ + zeros = lyjson_count_in_row(num, num + num_len, '0', FORWARD); + buf_len = minus + dp_position - zeros; + LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); + MAYBE_WRITE_MINUS(buf, i, minus); + /* Skip useless zeros and copy. */ + i += lyjson_exp_number_copy_num_part(num + zeros, num_len - zeros, NULL, dp_position, buf + i); + /* Add multiples of ten behind the 'numeric part'. */ + memset(buf + i, '0', buf_len - i); + } else { + /* Adding decimal point after the decimal value make the integer result. */ + + buf_len = minus + dp_position; + LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); + MAYBE_WRITE_MINUS(buf, i, minus); + i += lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i); + /* Add multiples of ten behind the 'numeric part'. */ + memset(buf + i, '0', buf_len - i); + } + + buf[buf_len] = '\0'; + *res = buf; + *res_len = buf_len; + +#undef MAYBE_WRITE_MINUS +#undef LEADING_ZERO +#undef FORWARD +#undef BACKWARD + + return LY_SUCCESS; +} + +static LY_ERR +lyjson_number(struct lyjson_ctx *jsonctx) +{ + size_t offset = 0, num_len; + const char *in = jsonctx->in->current, *exponent = NULL; + uint8_t minus = 0; + char *num; + + if (in[offset] == '-') { + ++offset; + minus = 1; + } + + if (in[offset] == '0') { + ++offset; + } else if (isdigit(in[offset])) { + ++offset; + while (isdigit(in[offset])) { + ++offset; + } + } else { +invalid_character: + if (in[offset]) { + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON Number value (\"%c\").", in[offset]); + } else { + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + } + return LY_EVALID; + } + + if (in[offset] == '.') { + ++offset; + if (!isdigit(in[offset])) { + goto invalid_character; + } + while (isdigit(in[offset])) { + ++offset; + } + } + + if ((in[offset] == 'e') || (in[offset] == 'E')) { + exponent = &in[offset]; + ++offset; + if ((in[offset] == '+') || (in[offset] == '-')) { + ++offset; + } + if (!isdigit(in[offset])) { + goto invalid_character; + } + while (isdigit(in[offset])) { + ++offset; + } + } + + if (lyjson_number_is_zero(in, exponent ? exponent : &in[offset])) { + lyjson_ctx_set_value(jsonctx, in, minus + 1, 0); + } else if (exponent && lyjson_number_is_zero(exponent + 1, &in[offset])) { + lyjson_ctx_set_value(jsonctx, in, exponent - in, 0); + } else if (exponent) { + LY_CHECK_RET(lyjson_exp_number(jsonctx->ctx, in, exponent, offset, &num, &num_len)); + lyjson_ctx_set_value(jsonctx, num, num_len, 1); + } else { + if (offset > LY_NUMBER_MAXLEN) { + LOGVAL(jsonctx->ctx, LYVE_SEMANTICS, + "Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit."); + return LY_EVALID; + } + lyjson_ctx_set_value(jsonctx, in, offset, 0); + } + ly_in_skip(jsonctx->in, offset); + + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NUMBER); + LY_CHECK_RET(lyjson_check_next(jsonctx)); + + return LY_SUCCESS; +} + +static LY_ERR +lyjson_object_name(struct lyjson_ctx *jsonctx) +{ + if (*jsonctx->in->current != '"') { + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), + jsonctx->in->current, "a JSON object's member"); + return LY_EVALID; + } + ly_in_skip(jsonctx->in, 1); + + LY_CHECK_RET(lyjson_string_(jsonctx)); + LY_CHECK_RET(skip_ws(jsonctx)); + if (*jsonctx->in->current != ':') { + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, + "a JSON object's name-separator ':'"); + return LY_EVALID; + } + ly_in_skip(jsonctx->in, 1); + LY_CHECK_RET(skip_ws(jsonctx)); + + return LY_SUCCESS; +} + +static LY_ERR +lyjson_object(struct lyjson_ctx *jsonctx) +{ + LY_CHECK_RET(skip_ws(jsonctx)); + + if (*jsonctx->in->current == '}') { + assert(jsonctx->depth); + jsonctx->depth--; + /* empty object */ + ly_in_skip(jsonctx->in, 1); + lyjson_ctx_set_value(jsonctx, NULL, 0, 0); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_EMPTY); + return LY_SUCCESS; + } + + LY_CHECK_RET(lyjson_object_name(jsonctx)); + + /* output data are set by lyjson_string_() */ + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT); + + return LY_SUCCESS; +} + +/** + * @brief Process JSON array envelope + * + * @param[in] jsonctx JSON parser context + * @return LY_SUCCESS or LY_EMEM + */ +static LY_ERR +lyjson_array(struct lyjson_ctx *jsonctx) +{ + LY_CHECK_RET(skip_ws(jsonctx)); + + if (*jsonctx->in->current == ']') { + /* empty array */ + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_EMPTY); + } else { + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY); + } + + /* erase previous values, array has no value on its own */ + lyjson_ctx_set_value(jsonctx, NULL, 0, 0); + + return LY_SUCCESS; +} + +static LY_ERR +lyjson_value(struct lyjson_ctx *jsonctx) +{ + if (jsonctx->status.count && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { + return LY_SUCCESS; + } + + if ((*jsonctx->in->current == 'f') && !strncmp(jsonctx->in->current, "false", ly_strlen_const("false"))) { + /* false */ + lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("false"), 0); + ly_in_skip(jsonctx->in, ly_strlen_const("false")); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_FALSE); + LY_CHECK_RET(lyjson_check_next(jsonctx)); + + } else if ((*jsonctx->in->current == 't') && !strncmp(jsonctx->in->current, "true", ly_strlen_const("true"))) { + /* true */ + lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("true"), 0); + ly_in_skip(jsonctx->in, ly_strlen_const("true")); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_TRUE); + LY_CHECK_RET(lyjson_check_next(jsonctx)); + + } else if ((*jsonctx->in->current == 'n') && !strncmp(jsonctx->in->current, "null", ly_strlen_const("null"))) { + /* none */ + lyjson_ctx_set_value(jsonctx, "", 0, 0); + ly_in_skip(jsonctx->in, ly_strlen_const("null")); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NULL); + LY_CHECK_RET(lyjson_check_next(jsonctx)); + + } else if (*jsonctx->in->current == '"') { + /* string */ + ly_in_skip(jsonctx->in, 1); + LY_CHECK_RET(lyjson_string(jsonctx)); + + } else if (*jsonctx->in->current == '[') { + /* array */ + ly_in_skip(jsonctx->in, 1); + LY_CHECK_RET(lyjson_array(jsonctx)); + + } else if (*jsonctx->in->current == '{') { + jsonctx->depth++; + if (jsonctx->depth > LY_MAX_BLOCK_DEPTH) { + LOGERR(jsonctx->ctx, LY_EINVAL, + "The maximum number of block nestings has been exceeded."); + return LY_EINVAL; + } + /* object */ + ly_in_skip(jsonctx->in, 1); + LY_CHECK_RET(lyjson_object(jsonctx)); + + } else if ((*jsonctx->in->current == '-') || ((*jsonctx->in->current >= '0') && (*jsonctx->in->current <= '9'))) { + /* number */ + LY_CHECK_RET(lyjson_number(jsonctx)); + + } else { + /* unexpected value */ + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), + jsonctx->in->current, "a JSON value"); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +LY_ERR +lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, ly_bool subtree, struct lyjson_ctx **jsonctx_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyjson_ctx *jsonctx; + + assert(ctx); + assert(in); + assert(jsonctx_p); + + /* new context */ + jsonctx = calloc(1, sizeof *jsonctx); + LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM); + jsonctx->ctx = ctx; + jsonctx->in = in; + + LOG_LOCSET(NULL, NULL, NULL, in); + + /* parse JSON value, if any */ + LY_CHECK_GOTO(ret = skip_ws(jsonctx), cleanup); + if (lyjson_ctx_status(jsonctx, 0) == LYJSON_END) { + /* empty data input */ + goto cleanup; + } + + if (subtree) { + ret = lyjson_object(jsonctx); + jsonctx->depth++; + } else { + ret = lyjson_value(jsonctx); + } + if ((jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + ret = LY_EVALID; + } + +cleanup: + if (ret) { + lyjson_ctx_free(jsonctx); + } else { + *jsonctx_p = jsonctx; + } + return ret; +} + +void +lyjson_ctx_backup(struct lyjson_ctx *jsonctx) +{ + if (jsonctx->backup.dynamic) { + free((char *)jsonctx->backup.value); + } + jsonctx->backup.status = lyjson_ctx_status(jsonctx, 0); + jsonctx->backup.status_count = jsonctx->status.count; + jsonctx->backup.value = jsonctx->value; + jsonctx->backup.value_len = jsonctx->value_len; + jsonctx->backup.input = jsonctx->in->current; + jsonctx->backup.dynamic = jsonctx->dynamic; + jsonctx->backup.depth = jsonctx->depth; + jsonctx->dynamic = 0; +} + +void +lyjson_ctx_restore(struct lyjson_ctx *jsonctx) +{ + if (jsonctx->dynamic) { + free((char *)jsonctx->value); + } + jsonctx->status.count = jsonctx->backup.status_count; + jsonctx->status.objs[jsonctx->backup.status_count - 1] = (void *)jsonctx->backup.status; + jsonctx->value = jsonctx->backup.value; + jsonctx->value_len = jsonctx->backup.value_len; + jsonctx->in->current = jsonctx->backup.input; + jsonctx->dynamic = jsonctx->backup.dynamic; + jsonctx->depth = jsonctx->backup.depth; + jsonctx->backup.dynamic = 0; +} + +LY_ERR +lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status) +{ + LY_ERR ret = LY_SUCCESS; + ly_bool toplevel = 0; + enum LYJSON_PARSER_STATUS prev; + + assert(jsonctx); + + prev = lyjson_ctx_status(jsonctx, 0); + + if ((prev == LYJSON_OBJECT) || (prev == LYJSON_ARRAY)) { + /* get value for the object's member OR the first value in the array */ + ret = lyjson_value(jsonctx); + goto result; + } else { + /* the previous token is closed and should be completely processed */ + LYJSON_STATUS_POP_RET(jsonctx); + prev = lyjson_ctx_status(jsonctx, 0); + } + + if (!jsonctx->status.count) { + /* we are done with the top level value */ + toplevel = 1; + } + LY_CHECK_RET(skip_ws(jsonctx)); + if (toplevel && !jsonctx->status.count) { + /* EOF expected, but there are some data after the top level token */ + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Expecting end-of-input, but some data follows the top level JSON value."); + return LY_EVALID; + } + + if (toplevel) { + /* we are done */ + goto result; + } + + /* continue with the next token */ + assert(prev == LYJSON_OBJECT || prev == LYJSON_ARRAY); + + if (*jsonctx->in->current == ',') { + /* sibling item in the ... */ + ly_in_skip(jsonctx->in, 1); + LY_CHECK_RET(skip_ws(jsonctx)); + + if (prev == LYJSON_OBJECT) { + /* ... object - get another object's member */ + ret = lyjson_object_name(jsonctx); + } else { /* LYJSON_ARRAY */ + /* ... array - get another complete value */ + ret = lyjson_value(jsonctx); + } + } else if (((prev == LYJSON_OBJECT) && (*jsonctx->in->current == '}')) || + ((prev == LYJSON_ARRAY) && (*jsonctx->in->current == ']'))) { + if (*jsonctx->in->current == '}') { + assert(jsonctx->depth); + jsonctx->depth--; + } + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_POP_RET(jsonctx); + LYJSON_STATUS_PUSH_RET(jsonctx, prev + 1); + } else { + /* unexpected value */ + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, + prev == LYJSON_ARRAY ? "another JSON value in array" : "another JSON object's member"); + return LY_EVALID; + } + +result: + if ((ret == LY_SUCCESS) && (jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + ret = LY_EVALID; + } + + if ((ret == LY_SUCCESS) && status) { + *status = lyjson_ctx_status(jsonctx, 0); + } + + return ret; +} + +enum LYJSON_PARSER_STATUS +lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index) +{ + assert(jsonctx); + + if (jsonctx->status.count < index) { + return LYJSON_ERROR; + } else if (jsonctx->status.count == index) { + return LYJSON_ROOT; + } else { + return (enum LYJSON_PARSER_STATUS)(uintptr_t)jsonctx->status.objs[jsonctx->status.count - (index + 1)]; + } +} + +void +lyjson_ctx_free(struct lyjson_ctx *jsonctx) +{ + if (!jsonctx) { + return; + } + + LOG_LOCBACK(0, 0, 0, 1); + + if (jsonctx->dynamic) { + free((char *)jsonctx->value); + } + if (jsonctx->backup.dynamic) { + free((char *)jsonctx->backup.value); + } + + ly_set_erase(&jsonctx->status, NULL); + + free(jsonctx); +} diff --git a/src/json.h b/src/json.h new file mode 100644 index 0000000..53efe2a --- /dev/null +++ b/src/json.h @@ -0,0 +1,139 @@ +/** + * @file json.h + * @author Radek Krejci + * @brief Generic JSON format parser routines. + * + * Copyright (c) 2020 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_JSON_H_ +#define LY_JSON_H_ + +#include +#include + +#include "log.h" +#include "set.h" + +struct ly_ctx; +struct ly_in; + +/* Macro to test if character is whitespace */ +#define is_jsonws(c) (c == 0x20 || c == 0x9 || c == 0xa || c == 0xd) + +/* Macro to test if character is valid string character */ +#define is_jsonstrchar(c) (c == 0x20 || c == 0x21 || (c >= 0x23 && c <= 0x5b) || (c >= 0x5d && c <= 0x10ffff)) + +/* Macro to push JSON parser status */ +#define LYJSON_STATUS_PUSH_RET(CTX, STATUS) \ + LY_CHECK_RET(ly_set_add(&CTX->status, (void *)(uintptr_t)(STATUS), 1, NULL)) + +/* Macro to pop JSON parser status */ +#define LYJSON_STATUS_POP_RET(CTX) \ + assert(CTX->status.count); CTX->status.count--; + +/** + * @brief Status of the parser providing information what is expected next (which function is supposed to be called). + */ +enum LYJSON_PARSER_STATUS { + LYJSON_ERROR, /* JSON parser error - value is used as an error return code */ + LYJSON_ROOT, /* JSON document root, used internally */ + LYJSON_OBJECT, /* JSON object */ + LYJSON_OBJECT_CLOSED, /* JSON object closed */ + LYJSON_OBJECT_EMPTY, /* empty JSON object { } */ + LYJSON_ARRAY, /* JSON array */ + LYJSON_ARRAY_CLOSED, /* JSON array closed */ + LYJSON_ARRAY_EMPTY, /* empty JSON array */ + LYJSON_NUMBER, /* JSON number value */ + LYJSON_STRING, /* JSON string value */ + LYJSON_FALSE, /* JSON false value */ + LYJSON_TRUE, /* JSON true value */ + LYJSON_NULL, /* JSON null value */ + LYJSON_END /* end of input data */ +}; + +struct lyjson_ctx { + const struct ly_ctx *ctx; + struct ly_in *in; /* input structure */ + + struct ly_set status; /* stack of ::LYJSON_PARSER_STATUS values corresponding to the JSON items being processed */ + + const char *value; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT */ + size_t value_len; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT */ + ly_bool dynamic; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT */ + uint32_t depth; /* current number of nested blocks, see ::LY_MAX_BLOCK_DEPTH */ + + struct { + enum LYJSON_PARSER_STATUS status; + uint32_t status_count; + const char *value; + size_t value_len; + ly_bool dynamic; + uint32_t depth; + const char *input; + } backup; +}; + +/** + * @brief Create a new JSON parser context and start parsing. + * + * @param[in] ctx libyang context. + * @param[in] in JSON string data to parse. + * @param[in] subtree Whether this is a special case of parsing a subtree (starting with object name). + * @param[out] jsonctx New JSON context with status referring the parsed value. + * @return LY_ERR value. + */ +LY_ERR lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, ly_bool subtree, struct lyjson_ctx **jsonctx); + +/** + * @brief Get status of the parser as the last/previous parsed token + * + * @param[in] jsonctx JSON context to check. + * @param[in] index Index of the token, starting by 0 for the last token + * @return ::LYJSON_ERROR in case of invalid index, other ::LYJSON_PARSER_STATUS corresponding to the token. + */ +enum LYJSON_PARSER_STATUS lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index); + +/** + * @brief Get string representation of the JSON context status (token). + * + * @param[in] status Context status (aka JSON token) + * @return String representation of the @p status. + */ +const char *lyjson_token2str(enum LYJSON_PARSER_STATUS status); + +/** + * @brief Move to the next JSON artifact and update parser status. + * + * @param[in] jsonctx XML context to move. + * @param[out] status Optional parameter to provide new parser status + * @return LY_ERR value. + */ +LY_ERR lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status); + +/** + * @brief Backup the JSON parser context's state To restore the backup, use ::lyjson_ctx_restore(). + * @param[in] jsonctx JSON parser context to backup. + */ +void lyjson_ctx_backup(struct lyjson_ctx *jsonctx); + +/** + * @brief REstore the JSON parser context's state from the backup created by ::lyjson_ctx_backup(). + * @param[in] jsonctx JSON parser context to restore. + */ +void lyjson_ctx_restore(struct lyjson_ctx *jsonctx); + +/** + * @brief Remove the allocated working memory of the context. + * + * @param[in] jsonctx JSON context to clear. + */ +void lyjson_ctx_free(struct lyjson_ctx *jsonctx); + +#endif /* LY_JSON_H_ */ diff --git a/src/libyang.h b/src/libyang.h new file mode 100644 index 0000000..2bfc6be --- /dev/null +++ b/src/libyang.h @@ -0,0 +1,167 @@ +/** + * @file libyang.h + * @author Radek Krejci + * @author Michal Vasko + * @brief The main libyang public header. + * + * Copyright (c) 2015 - 2022 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_LIBYANG_H_ +#define LY_LIBYANG_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "context.h" +#include "dict.h" +#include "in.h" +#include "log.h" +#include "metadata.h" +#include "out.h" +#include "parser_data.h" +#include "parser_schema.h" +#include "printer_data.h" +#include "printer_schema.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_schema.h" + +/* + * The following headers are supposed to be included explicitly: + * - metadata.h + * - plugins_types.h + * - plugins_exts.h + */ + +/** + * @mainpage About + * + * libyang is a library implementing processing of the YANG schemas and data modeled by the YANG language. The + * library is implemented in C for GNU/Linux and provides C API. + * + * @section about-features Main Features + * + * - [Parsing (and validating) schemas](@ref howtoSchema) in YANG format. + * - [Parsing (and validating) schemas](@ref howtoSchema) in YIN format. + * - [Parsing, validating and printing instance data](@ref howtoData) in XML format. + * - [Parsing, validating and printing instance data](@ref howtoData) in JSON format + * ([RFC 7951](https://tools.ietf.org/html/rfc7951)). + * - [Manipulation with the instance data](@ref howtoDataManipulation). + * - Support for [default values in the instance data](@ref howtoDataWD) ([RFC 6243](https://tools.ietf.org/html/rfc6243)). + * - Support for [YANG extensions and user types](@ref howtoPlugins). + * - Support for [YANG Metadata](@ref howtoDataMetadata) ([RFC 7952](https://tools.ietf.org/html/rfc6243)). + * - Support for [YANG Schema Mount](@ref howtoDataMountpoint) ([RFC 8528](https://tools.ietf.org/html/rfc8528)). + * + * The current implementation covers YANG 1.0 ([RFC 6020](https://tools.ietf.org/html/rfc6020)) as well as + * YANG 1.1 ([RFC 7950](https://tools.ietf.org/html/rfc7950)). + * + * @section about-license License + * + * Copyright (c) 2015-2022 CESNET, z.s.p.o. + * + * (The BSD 3-Clause License) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the Company nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + */ + +/** + * @page howto libyang API Overview + * + * @section howtoGeneral General notes + * + * libyang is primarily intended for handling data modeled by YANG modeling language, so the library is supposed to be optimized + * for this purpose. However, as a side effect, the library has to be able precisely process YANG modules. Thus, it is usable by + * YANG module authors to validate their modules and schemas in the development process. + * + * - @subpage howtoStructures + * - @subpage howtoErrors + * - @subpage howtoLogger + * - @subpage howtoThreads + * - @subpage howtoContext + * - @subpage howtoInput + * - @subpage howtoOutput + * - @subpage howtoSchema + * - @subpage howtoData + * - @subpage howtoXPath + * - @subpage howtoPlugins + */ + +/** + * @page howtoStructures Data Structures + * + * @section sizedarrays Sized Arrays + * + * The structure starts with 32bit number storing size of the array - the number of the items inside. The size is part of the + * array to have it allocated together with the array itself only when it is needed. However, the pointers to the array always + * points after the 32b number, so items can be accessed directly as for standard C arrays. Because of a known size (available + * via ::LY_ARRAY_COUNT macro), it is not terminated by any special byte (sequence), so there is also no limitation for specific + * content of the stored records (e.g. that first byte must not be NULL). + * + * The sized arrays must be carefully freed (which should be done anyway only internally), since pointers to the sized arrays used + * in libyang structures, does not point to the beginning of the allocated space. + * + * - ::LY_ARRAY_COUNT + * - ::LY_ARRAY_FOR + * + * @section struct_lists Lists + * + * The lists are structures connected via a `next` and `prev` pointers. Iterating over the siblings can be simply done by + * ::LY_LIST_FOR macro. Examples of such structures are ::lyd_node or ::lysc_node. + * + * The `prev` pointer is always filled. In case there is just a single item in the list, the `prev` pointer points to the + * item itself. Otherwise, the `prev` pointer of the first item points to the last item of the list. In contrast, the + * `next` pointer of the last item in the list is always NULL. + */ + +/** + * @page howtoThreads Threading Limitations + * + * @section context Context + * + * It is safe to read from ::ly_ctx structure concurrently and use its dictionary, which is protected by a lock. + * Thread-safe functions include any ones working with data trees (only context dictionary is accessed) and all + * the `ly_ctx_get_*()` functions. Generally, they are the functions with `const` context parameter. + * + * @section data Data Trees + * + * Data trees are not internally synchronized so the general safe practice of a single writer **or** several concurrent + * readers should be followed. Specifically, only the functions with non-const ::lyd_node parameters modify the node(s) + * and no concurrent execution of such functions should be allowed on a single data tree or subtrees of one. + */ + +/** + * @internal + * @page internals Developers' Notes + * @tableofcontents + * + * Following texts describes various internal subsystems and mechanism in libyang which are hidden from external users, but important + * for libyang developers. The texts should explain various decisions made and internal processes utilized in libyang. + */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_LIBYANG_H_ */ diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..2a1f862 --- /dev/null +++ b/src/log.c @@ -0,0 +1,773 @@ +/** + * @file log.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Logger routines implementations + * + * Copyright (c) 2015 - 2022 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 /* asprintf, strdup */ + +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "in_internal.h" +#include "plugins_exts.h" +#include "set.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" + +ATOMIC_T ly_ll = (uint_fast32_t)LY_LLWRN; +ATOMIC_T ly_log_opts = (uint_fast32_t)(LY_LOLOG | LY_LOSTORE_LAST); +THREAD_LOCAL uint32_t *temp_ly_log_opts; +static ly_log_clb log_clb; +static ATOMIC_T path_flag = 1; +#ifndef NDEBUG +ATOMIC_T ly_ldbg_groups = 0; +#endif + +THREAD_LOCAL struct ly_log_location_s log_location = {0}; + +/* how many bytes add when enlarging buffers */ +#define LY_BUF_STEP 128 + +LIBYANG_API_DEF LY_ERR +ly_errcode(const struct ly_ctx *ctx) +{ + struct ly_err_item *i; + + i = ly_err_last(ctx); + if (i) { + return i->no; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_VECODE +ly_vecode(const struct ly_ctx *ctx) +{ + struct ly_err_item *i; + + i = ly_err_last(ctx); + if (i) { + return i->vecode; + } + + return LYVE_SUCCESS; +} + +LIBYANG_API_DEF const char * +ly_errmsg(const struct ly_ctx *ctx) +{ + struct ly_err_item *i; + + LY_CHECK_ARG_RET(NULL, ctx, NULL); + + i = ly_err_last(ctx); + if (i) { + return i->msg; + } + + return NULL; +} + +LIBYANG_API_DEF const char * +ly_errpath(const struct ly_ctx *ctx) +{ + struct ly_err_item *i; + + LY_CHECK_ARG_RET(NULL, ctx, NULL); + + i = ly_err_last(ctx); + if (i) { + return i->path; + } + + return NULL; +} + +LIBYANG_API_DEF const char * +ly_errapptag(const struct ly_ctx *ctx) +{ + struct ly_err_item *i; + + LY_CHECK_ARG_RET(NULL, ctx, NULL); + + i = ly_err_last(ctx); + if (i) { + return i->apptag; + } + + return NULL; +} + +LIBYANG_API_DEF LY_ERR +ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, char *apptag, const char *err_format, ...) +{ + char *msg = NULL; + struct ly_err_item *e; + + if (!err || (ecode == LY_SUCCESS)) { + /* nothing to do */ + return ecode; + } + + e = malloc(sizeof *e); + LY_CHECK_ERR_RET(!e, LOGMEM(NULL), LY_EMEM); + e->prev = (*err) ? (*err)->prev : e; + e->next = NULL; + if (*err) { + (*err)->prev->next = e; + } + + /* fill in the information */ + e->level = LY_LLERR; + e->no = ecode; + e->vecode = vecode; + e->path = path; + e->apptag = apptag; + + if (err_format) { + va_list print_args; + + va_start(print_args, err_format); + + if (vasprintf(&msg, err_format, print_args) == -1) { + /* we don't have anything more to do, just set msg to NULL to avoid undefined content, + * still keep the information about the original error instead of LY_EMEM or other printf's error */ + msg = NULL; + } + + va_end(print_args); + } + e->msg = msg; + + if (!(*err)) { + *err = e; + } + + return e->no; +} + +LIBYANG_API_DEF struct ly_err_item * +ly_err_first(const struct ly_ctx *ctx) +{ + LY_CHECK_ARG_RET(NULL, ctx, NULL); + + return pthread_getspecific(ctx->errlist_key); +} + +LIBYANG_API_DEF struct ly_err_item * +ly_err_last(const struct ly_ctx *ctx) +{ + const struct ly_err_item *e; + + LY_CHECK_ARG_RET(NULL, ctx, NULL); + + e = pthread_getspecific(ctx->errlist_key); + return e ? e->prev : NULL; +} + +LIBYANG_API_DEF void +ly_err_free(void *ptr) +{ + struct ly_err_item *i, *next; + + /* clean the error list */ + for (i = (struct ly_err_item *)ptr; i; i = next) { + next = i->next; + free(i->msg); + free(i->path); + free(i->apptag); + free(i); + } +} + +LIBYANG_API_DEF void +ly_err_clean(struct ly_ctx *ctx, struct ly_err_item *eitem) +{ + struct ly_err_item *i, *first; + + first = ly_err_first(ctx); + if (first == eitem) { + eitem = NULL; + } + if (eitem) { + /* disconnect the error */ + for (i = first; i && (i->next != eitem); i = i->next) {} + assert(i); + i->next = NULL; + first->prev = i; + /* free this err and newer */ + ly_err_free(eitem); + } else { + /* free all err */ + ly_err_free(first); + pthread_setspecific(ctx->errlist_key, NULL); + } +} + +LIBYANG_API_DEF LY_LOG_LEVEL +ly_log_level(LY_LOG_LEVEL level) +{ + LY_LOG_LEVEL prev = ATOMIC_LOAD_RELAXED(ly_ll); + + ATOMIC_STORE_RELAXED(ly_ll, level); + return prev; +} + +LIBYANG_API_DEF uint32_t +ly_log_options(uint32_t opts) +{ + uint32_t prev = ATOMIC_LOAD_RELAXED(ly_log_opts); + + ATOMIC_STORE_RELAXED(ly_log_opts, opts); + return prev; +} + +LIBYANG_API_DEF void +ly_temp_log_options(uint32_t *opts) +{ + temp_ly_log_opts = opts; +} + +LIBYANG_API_DEF uint32_t +ly_log_dbg_groups(uint32_t dbg_groups) +{ +#ifndef NDEBUG + uint32_t prev = ATOMIC_LOAD_RELAXED(ly_ldbg_groups); + + ATOMIC_STORE_RELAXED(ly_ldbg_groups, dbg_groups); + return prev; +#else + (void)dbg_groups; + return 0; +#endif +} + +LIBYANG_API_DEF void +ly_set_log_clb(ly_log_clb clb, ly_bool path) +{ + log_clb = clb; + ATOMIC_STORE_RELAXED(path_flag, path); +} + +LIBYANG_API_DEF ly_log_clb +ly_get_log_clb(void) +{ + return log_clb; +} + +void +ly_log_location(const struct lysc_node *scnode, const struct lyd_node *dnode, const char *path, const struct ly_in *in, + uint64_t line) +{ + if (scnode) { + ly_set_add(&log_location.scnodes, (void *)scnode, 1, NULL); + } + if (dnode) { + ly_set_add(&log_location.dnodes, (void *)dnode, 1, NULL); + } + if (path) { + char *s = strdup(path); + + LY_CHECK_ERR_RET(!s, LOGMEM(NULL), ); + ly_set_add(&log_location.paths, s, 1, NULL); + } + if (in) { + ly_set_add(&log_location.inputs, (void *)in, 1, NULL); + } + if (line) { + log_location.line = line; + } +} + +void +ly_log_location_revert(uint32_t scnode_steps, uint32_t dnode_steps, uint32_t path_steps, uint32_t in_steps) +{ + for (uint32_t i = scnode_steps; i && log_location.scnodes.count; i--) { + log_location.scnodes.count--; + } + + for (uint32_t i = dnode_steps; i && log_location.dnodes.count; i--) { + log_location.dnodes.count--; + } + + for (uint32_t i = path_steps; i && log_location.paths.count; i--) { + ly_set_rm_index(&log_location.paths, log_location.paths.count - 1, free); + } + + for (uint32_t i = in_steps; i && log_location.inputs.count; i--) { + log_location.inputs.count--; + } + + /* deallocate the empty sets */ + if (scnode_steps && !log_location.scnodes.count) { + ly_set_erase(&log_location.scnodes, NULL); + } + if (dnode_steps && !log_location.dnodes.count) { + ly_set_erase(&log_location.dnodes, NULL); + } + if (path_steps && !log_location.paths.count) { + ly_set_erase(&log_location.paths, free); + } + if (in_steps && !log_location.inputs.count) { + ly_set_erase(&log_location.inputs, NULL); + } +} + +static LY_ERR +log_store(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *msg, char *path, char *apptag) +{ + struct ly_err_item *eitem, *last; + + assert(ctx && (level < LY_LLVRB)); + + eitem = pthread_getspecific(ctx->errlist_key); + if (!eitem) { + /* if we are only to fill in path, there must have been an error stored */ + assert(msg); + eitem = malloc(sizeof *eitem); + LY_CHECK_GOTO(!eitem, mem_fail); + eitem->prev = eitem; + eitem->next = NULL; + + pthread_setspecific(ctx->errlist_key, eitem); + } else if (!msg) { + /* only filling the path */ + assert(path); + + /* find last error */ + eitem = eitem->prev; + do { + if (eitem->level == LY_LLERR) { + /* fill the path */ + free(eitem->path); + eitem->path = path; + return LY_SUCCESS; + } + eitem = eitem->prev; + } while (eitem->prev->next); + /* last error was not found */ + assert(0); + } else if ((temp_ly_log_opts && ((*temp_ly_log_opts & LY_LOSTORE_LAST) == LY_LOSTORE_LAST)) || + (!temp_ly_log_opts && ((ATOMIC_LOAD_RELAXED(ly_log_opts) & LY_LOSTORE_LAST) == LY_LOSTORE_LAST))) { + /* overwrite last message */ + free(eitem->msg); + free(eitem->path); + free(eitem->apptag); + } else { + /* store new message */ + last = eitem->prev; + eitem->prev = malloc(sizeof *eitem); + LY_CHECK_GOTO(!eitem->prev, mem_fail); + eitem = eitem->prev; + eitem->prev = last; + eitem->next = NULL; + last->next = eitem; + } + + /* fill in the information */ + eitem->level = level; + eitem->no = no; + eitem->vecode = vecode; + eitem->msg = msg; + eitem->path = path; + eitem->apptag = apptag; + return LY_SUCCESS; + +mem_fail: + LOGMEM(NULL); + free(msg); + free(path); + free(apptag); + return LY_EMEM; +} + +static void +log_vprintf(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *path, const char *apptag, + const char *format, va_list args) +{ + char *msg = NULL; + ly_bool free_strs, lolog, lostore; + + /* learn effective logger options */ + if (temp_ly_log_opts) { + lolog = *temp_ly_log_opts & LY_LOLOG; + lostore = *temp_ly_log_opts & LY_LOSTORE; + } else { + lolog = ATOMIC_LOAD_RELAXED(ly_log_opts) & LY_LOLOG; + lostore = ATOMIC_LOAD_RELAXED(ly_log_opts) & LY_LOSTORE; + } + + if (level > ATOMIC_LOAD_RELAXED(ly_ll)) { + /* do not print or store the message */ + free(path); + return; + } + + if (no == LY_EMEM) { + /* just print it, anything else would most likely fail anyway */ + if (lolog) { + if (log_clb) { + log_clb(level, LY_EMEM_MSG, path); + } else { + fprintf(stderr, "libyang[%d]: ", level); + vfprintf(stderr, format, args); + if (path) { + fprintf(stderr, " (path: %s)\n", path); + } else { + fprintf(stderr, "\n"); + } + } + } + free(path); + return; + } + + /* store the error/warning (if we need to store errors internally, it does not matter what are the user log options) */ + if ((level < LY_LLVRB) && ctx && lostore) { + assert(format); + if (vasprintf(&msg, format, args) == -1) { + LOGMEM(ctx); + free(path); + return; + } + if (((no & ~LY_EPLUGIN) == LY_EVALID) && (vecode == LYVE_SUCCESS)) { + /* assume we are inheriting the error, so inherit vecode as well */ + vecode = ly_vecode(ctx); + } + if (log_store(ctx, level, no, vecode, msg, path, apptag ? strdup(apptag) : NULL)) { + return; + } + free_strs = 0; + } else { + if (vasprintf(&msg, format, args) == -1) { + LOGMEM(ctx); + free(path); + return; + } + free_strs = 1; + } + + /* if we are only storing errors internally, never print the message (yet) */ + if (lolog) { + if (log_clb) { + log_clb(level, msg, path); + } else { + fprintf(stderr, "libyang[%d]: %s%s", level, msg, path ? " " : "\n"); + if (path) { + fprintf(stderr, "(path: %s)\n", path); + } + } + } + + if (free_strs) { + free(path); + free(msg); + } +} + +#ifndef NDEBUG + +void +ly_log_dbg(uint32_t group, const char *format, ...) +{ + char *dbg_format; + const char *str_group; + va_list ap; + + if (!(ATOMIC_LOAD_RELAXED(ly_ldbg_groups) & group)) { + return; + } + + switch (group) { + case LY_LDGDICT: + str_group = "DICT"; + break; + case LY_LDGXPATH: + str_group = "XPATH"; + break; + case LY_LDGDEPSETS: + str_group = "DEPSETS"; + break; + default: + LOGINT(NULL); + return; + } + + if (asprintf(&dbg_format, "%s: %s", str_group, format) == -1) { + LOGMEM(NULL); + return; + } + + va_start(ap, format); + log_vprintf(NULL, LY_LLDBG, 0, 0, NULL, NULL, dbg_format, ap); + va_end(ap); +} + +#endif + +void +ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + log_vprintf(ctx, level, no, 0, NULL, NULL, format, ap); + va_end(ap); +} + +/** + * @brief Append a schema node name to a generated data path, only if it fits. + * + * @param[in,out] str Generated path to update. + * @param[in] snode Schema node to append. + * @param[in] parent Last printed data node. + * @return LY_ERR value. + */ +static LY_ERR +ly_vlog_build_path_append(char **str, const struct lysc_node *snode, const struct lyd_node *parent) +{ + const struct lys_module *mod, *prev_mod; + uint32_t len, new_len; + void *mem; + + if (snode->nodetype & (LYS_CHOICE | LYS_CASE)) { + /* schema-only node */ + return LY_SUCCESS; + } else if (lysc_data_parent(snode) != parent->schema) { + /* not a direct descendant node */ + return LY_SUCCESS; + } + + /* get module to print, if any */ + mod = snode->module; + prev_mod = (parent->schema) ? parent->schema->module : lyd_owner_module(parent); + if (prev_mod == mod) { + mod = NULL; + } + + /* realloc string */ + len = strlen(*str); + new_len = len + 1 + (mod ? strlen(mod->name) + 1 : 0) + strlen(snode->name); + mem = realloc(*str, new_len + 1); + LY_CHECK_ERR_RET(!mem, LOGMEM(LYD_CTX(parent)), LY_EMEM); + *str = mem; + + /* print the last schema node */ + sprintf(*str + len, "/%s%s%s", mod ? mod->name : "", mod ? ":" : "", snode->name); + return LY_SUCCESS; +} + +/** + * @brief Build log path from the stored log location information. + * + * @param[in] ctx Context to use. + * @param[out] path Generated log path. + * @return LY_ERR value. + */ +static LY_ERR +ly_vlog_build_path(const struct ly_ctx *ctx, char **path) +{ + int r; + char *str = NULL, *prev = NULL; + const struct lyd_node *dnode; + + *path = NULL; + + if (log_location.paths.count && ((const char *)(log_location.paths.objs[log_location.paths.count - 1]))[0]) { + /* simply get what is in the provided path string */ + *path = strdup((const char *)log_location.paths.objs[log_location.paths.count - 1]); + LY_CHECK_ERR_RET(!(*path), LOGMEM(ctx), LY_EMEM); + } else { + /* data/schema node */ + if (log_location.dnodes.count) { + dnode = log_location.dnodes.objs[log_location.dnodes.count - 1]; + if (dnode->parent || !lysc_data_parent(dnode->schema)) { + /* data node with all of its parents */ + str = lyd_path(log_location.dnodes.objs[log_location.dnodes.count - 1], LYD_PATH_STD, NULL, 0); + LY_CHECK_ERR_RET(!str, LOGMEM(ctx), LY_EMEM); + } else { + /* data parsers put all the parent nodes in the set, but they are not connected */ + str = lyd_path_set(&log_location.dnodes, LYD_PATH_STD); + LY_CHECK_ERR_RET(!str, LOGMEM(ctx), LY_EMEM); + } + + /* sometimes the last node is not created yet and we only have the schema node */ + if (log_location.scnodes.count) { + ly_vlog_build_path_append(&str, log_location.scnodes.objs[log_location.scnodes.count - 1], dnode); + } + + r = asprintf(path, "Data location \"%s\"", str); + free(str); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + } else if (log_location.scnodes.count) { + str = lysc_path(log_location.scnodes.objs[log_location.scnodes.count - 1], LYSC_PATH_LOG, NULL, 0); + LY_CHECK_ERR_RET(!str, LOGMEM(ctx), LY_EMEM); + + r = asprintf(path, "Schema location \"%s\"", str); + free(str); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + } + + /* line */ + prev = *path; + if (log_location.line) { + r = asprintf(path, "%s%sine number %" PRIu64, prev ? prev : "", prev ? ", l" : "L", log_location.line); + free(prev); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + + log_location.line = 0; + } else if (log_location.inputs.count) { + r = asprintf(path, "%s%sine number %" PRIu64, prev ? prev : "", prev ? ", l" : "L", + ((struct ly_in *)log_location.inputs.objs[log_location.inputs.count - 1])->line); + free(prev); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + } + + if (*path) { + prev = *path; + r = asprintf(path, "%s.", prev); + free(prev); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + } + } + + return LY_SUCCESS; +} + +void +ly_vlog(const struct ly_ctx *ctx, const char *apptag, LY_VECODE code, const char *format, ...) +{ + va_list ap; + char *path = NULL; + + if (ATOMIC_LOAD_RELAXED(path_flag) && ctx) { + ly_vlog_build_path(ctx, &path); + } + + va_start(ap, format); + log_vprintf(ctx, LY_LLERR, LY_EVALID, code, path, apptag, format, ap); + /* path is spent and should not be freed! */ + va_end(ap); +} + +/** + * @brief Print a log message from an extension plugin callback. + * + * @param[in] ctx libyang context to store the error record. If not provided, the error is just printed. + * @param[in] plugin_name Name of the plugin generating the message. + * @param[in] level Log message level (error, warning, etc.) + * @param[in] err_no Error type code. + * @param[in] path Optional path of the error. + * @param[in] format Format string to print. + * @param[in] ap Var arg list for @p format. + */ +static void +ly_ext_log(const struct ly_ctx *ctx, const char *plugin_name, LY_LOG_LEVEL level, LY_ERR err_no, const char *path, + const char *format, va_list ap) +{ + char *plugin_msg; + + if (ATOMIC_LOAD_RELAXED(ly_ll) < level) { + return; + } + if (asprintf(&plugin_msg, "Ext plugin \"%s\": %s", plugin_name, format) == -1) { + LOGMEM(ctx); + return; + } + + log_vprintf(ctx, level, (level == LY_LLERR ? LY_EPLUGIN : 0) | err_no, LYVE_OTHER, path ? strdup(path) : NULL, NULL, + plugin_msg, ap); + free(plugin_msg); +} + +LIBYANG_API_DEF void +lyplg_ext_parse_log(const struct lysp_ctx *pctx, const struct lysp_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, + const char *format, ...) +{ + va_list ap; + char *path = NULL; + + if (ATOMIC_LOAD_RELAXED(path_flag)) { + ly_vlog_build_path(PARSER_CTX(pctx), &path); + } + + va_start(ap, format); + ly_ext_log(PARSER_CTX(pctx), ext->record->plugin.id, level, err_no, path, format, ap); + va_end(ap); + + free(path); +} + +LIBYANG_API_DEF void +lyplg_ext_compile_log(const struct lysc_ctx *cctx, const struct lysc_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, + const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + ly_ext_log(ext->module->ctx, ext->def->plugin->id, level, err_no, cctx ? cctx->path : NULL, format, ap); + va_end(ap); +} + +LIBYANG_API_DEF void +lyplg_ext_compile_log_path(const char *path, const struct lysc_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, + const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + ly_ext_log(ext->module->ctx, ext->def->plugin->id, level, err_no, path, format, ap); + va_end(ap); +} + +/** + * @brief Exact same functionality as ::ly_err_print() but has variable arguments so log_vprintf() can be called. + */ +static void +_ly_err_print(const struct ly_ctx *ctx, struct ly_err_item *eitem, const char *format, ...) +{ + va_list ap; + char *path_dup = NULL; + + LY_CHECK_ARG_RET(ctx, eitem, ); + + if (eitem->path) { + /* duplicate path because it will be freed */ + path_dup = strdup(eitem->path); + LY_CHECK_ERR_RET(!path_dup, LOGMEM(ctx), ); + } + + va_start(ap, format); + log_vprintf(ctx, eitem->level, eitem->no, eitem->vecode, path_dup, eitem->apptag, format, ap); + va_end(ap); +} + +LIBYANG_API_DEF void +ly_err_print(const struct ly_ctx *ctx, struct ly_err_item *eitem) +{ + /* String ::ly_err_item.msg cannot be used directly because it may contain the % character */ + _ly_err_print(ctx, eitem, "%s", eitem->msg); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..2144668 --- /dev/null +++ b/src/log.h @@ -0,0 +1,404 @@ +/** + * @file log.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Logger manipulation routines and error definitions. + * + * Copyright (c) 2015 - 2022 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_LOG_H_ +#define LY_LOG_H_ + +#include + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* dummy context structure */ +struct ly_ctx; + +/** + * @brief Type to indicate boolean value. + * + * Do not test for actual value. Instead, handle it as true/false value in condition. + */ +typedef uint8_t ly_bool; + +/** + * @page howtoLogger Information Logging + * + * The libyang logger is supposed to process all the messages (and some other accompanied information) generated by the performed + * functions. According to the logger settings, the information can be printed, stored or further processed by a callback + * functions. + * + * The logger is tightly connected with [errors handling](@ref howtoErrors), because when an error appears, the logger (according + * to [logger options](@ref logopts)) generates error records available via libyang context. + * + * There are 4 verbosity levels defined as ::LY_LOG_LEVEL. The level can be changed by the ::ly_log_level() function. + * By default, the verbosity level is set to #LY_LLERR value, so only the errors are processed. + * + * By default, all libyang messages are printed to `stderr`. However, the callers are able to set their own logging callback + * function (::ly_log_clb). In that case, instead of printing messages, libyang passes error level, message and path (if any) to + * the caller's callback function set via ::ly_set_log_clb(). In case of error level, the error information is still + * automatically stored and available via the [error handling functions](@ref howtoErrors). + * + * With [logging options](@ref logopts) set via ::ly_log_options(), the caller can modify what is done with all the messages. + * Default flags are ::LY_LOLOG and ::LY_LOSTORE_LAST so that messages are logged and the last one is stored. If you set the flag + * ::LY_LOSTORE, all the messages will be stored. Be careful because unless you regularly clean them, the error list in context + * will grow indefinitely. + * + * As a separate group, there are @ref dbggroup to select group of debugging messages to print. The options can be set via + * ::ly_log_dbg_groups() function, but note that the options take effect only in case the libyang is compiled in + * [Debug build mode](@ref build). + * + * \note API for this group of functions is described in the [logger module](@ref log). + * + * Functions List + * -------------- + * - ::ly_log_level() + * - ::ly_log_dbg_groups() + * - ::ly_log_options() + * - ::ly_set_log_clb() + * - ::ly_get_log_clb() + * + */ + +/** + * @defgroup log Logger + * @{ + * + * Publicly visible functions and values of the libyang logger. For more + * information, see @ref howtoLogger. + */ + +/** + * @typedef LY_LOG_LEVEL + * @brief Verbosity levels of the libyang logger. + */ +typedef enum { + LY_LLERR = 0, /**< Print only error messages. */ + LY_LLWRN = 1, /**< Print error and warning messages, default value. */ + LY_LLVRB = 2, /**< Besides errors and warnings, print some other verbose messages. */ + LY_LLDBG = 3 /**< Print all messages including some development debug messages (be careful, + without subsequently calling ::ly_log_dbg_groups() no debug messages will be printed!). */ +} LY_LOG_LEVEL; + +/** + * @brief Set logger verbosity level. + * + * To get the current value, the function must be called twice resetting the level by the received value. + * + * @param[in] level Verbosity level. + * @return Previous verbosity level. + */ +LIBYANG_API_DECL LY_LOG_LEVEL ly_log_level(LY_LOG_LEVEL level); + +/** + * @ingroup logger + * @defgroup logopts Logging options + * + * Logging option bits of libyang. + * + * Can be set via ::ly_log_options(). + * + * @{ + */ +#define LY_LOLOG 0x01 /**< Log messages normally, using callback if set. If not set, messages will + not be printed by libyang. */ +#define LY_LOSTORE 0x02 /**< Store any generated errors or warnings, never verbose or debug messages. + Note that if #LY_LOLOG is not set then verbose and debug messages are always lost. */ +#define LY_LOSTORE_LAST 0x06 /**< Store any generated errors or warnings but only the last message, always overwrite + the previous one. */ + +/** + * @} + */ + +/** + * @brief Set logger options. Default is #LY_LOLOG | #LY_LOSTORE_LAST. + * + * To get the current value, the function must be called twice resetting the level by the received value. + * + * @param[in] opts Bitfield of @ref logopts. + * @return Previous logger options. + */ +LIBYANG_API_DECL uint32_t ly_log_options(uint32_t opts); + +/** + * @brief Set temporary thread-safe logger options overwriting those set by ::ly_log_options(). + * + * @param[in] opts Pointer to the temporary @ref logopts. If NULL, restores the effect of global logger options. + */ +LIBYANG_API_DECL void ly_temp_log_options(uint32_t *opts); + +#ifndef NDEBUG + +/** + * @ingroup log + * @defgroup dbggroup Debug messages groups + * + * Categories of the debug messages. + * + * Allows to show only the selected group(s) of the debug messages. + * + * @{ + */ + +#define LY_LDGDICT 0x01 /**< Dictionary additions and deletions. */ +#define LY_LDGXPATH 0x02 /**< XPath parsing end evaluation. */ +#define LY_LDGDEPSETS 0x04 /**< Dependency module sets for schema compilation. */ + +/** + * @} + */ + +/** + * @brief Enable specific debugging messages (independent of log level). + * + * To get the current value, the function must be called twice resetting the level by the received value. + * + * @param[in] dbg_groups Bitfield of enabled debug message groups (see @ref dbggroup). + * @return Previous options bitfield. + */ +uint32_t ly_log_dbg_groups(uint32_t dbg_groups); + +#endif + +/** + * @brief Logger callback. + * + * !IMPORTANT! If an error has a specific error-app-tag defined in the model, it will NOT be set + * at the time of calling this callback. It will be set right after, so to retrieve it + * it must be checked afterwards with ::ly_errapptag(). + * + * @param[in] level Log level of the message. + * @param[in] msg Message. + * @param[in] path Optional path of the concerned node. + */ +typedef void (*ly_log_clb)(LY_LOG_LEVEL level, const char *msg, const char *path); + +/** + * @brief Set logger callback. + * + * @param[in] clb Logging callback. + * @param[in] path flag to resolve and provide path as the third parameter of the callback function. In case of + * validation and some other errors, it can be useful to get the path to the problematic element. Note, + * that according to the tree type and the specific situation, the path can slightly differs (keys + * presence) or it can be NULL, so consider it as an optional parameter. If the flag is 0, libyang will + * not bother with resolving the path. + */ +LIBYANG_API_DECL void ly_set_log_clb(ly_log_clb clb, ly_bool path); + +/** + * @brief Get logger callback. + * @return Logger callback (can be NULL). + */ +LIBYANG_API_DECL ly_log_clb ly_get_log_clb(void); + +/** @} log */ + +/** + * @page howtoErrors Errors Handling + * + * The most of the API functions directly returns error code in the form of ::LY_ERR value. In addition, if the ::LY_EVALID error + * arises, additional [validation error code](@ref ::LY_VECODE) is provided to categorize validation failures into several groups. + * + * All the errors arisen in connection with manipulation with the [context](@ref howtoContext), [YANG modules](@ref howtoSchema) + * or [YANG data](@ref howtoData), are recorded into the context and can be examined for the more detailed information. These + * records are stored as ::ly_err_item structures and they are not only context-specific, but also thread-specific. + * + * Storing error information is tightly connected with + * [logging](@ref howtoLogger). So the @ref logopts control if and which errors are stored in the context. By default, only the + * last error is recorded, so a new error replaces the previous one. This can be changed with ::LY_LOSTORE option set via + * ::ly_log_options(). Then, the errors are stored as a list preserving the previous error records. The stored records can be + * accessed using ::ly_err_last() or ::ly_err_first() functions. The ::ly_err_clean() is used to remove error records from the + * context. + * + * To print a specific error information via libyang logger, there is ::ly_err_print(). + * + * To simplify access to the last error record in the context, there is a set of functions returning important error information. + * - ::ly_errapptag() + * - ::ly_errcode() + * - ::ly_vecode() + * - ::ly_errmsg() + * - ::ly_errpath() + * + * \note API for this group of functions is described in the [error information module](@ref errors). + */ + +/** + * @defgroup errors Error information + * + * Structures and functions to allow error information processing. + * + * @{ + */ + +/** + * @typedef LY_ERR + * @brief libyang's error codes returned by the libyang functions. + */ +typedef enum { + LY_SUCCESS = 0, /**< no error, not set by functions, included just to complete #LY_ERR enumeration */ + LY_EMEM, /**< Memory allocation failure */ + LY_ESYS, /**< System call failure */ + LY_EINVAL, /**< Invalid value */ + LY_EEXIST, /**< Item already exists */ + LY_ENOTFOUND, /**< Item does not exists */ + LY_EINT, /**< Internal error */ + LY_EVALID, /**< Validation failure */ + LY_EDENIED, /**< Operation is not allowed */ + LY_EINCOMPLETE, /**< The operation did not fail, but for some reason it was not possible to finish it completely. + According to the specific use case, the caller is usually supposed to perform the operation again. */ + LY_ERECOMPILE, /**< The operation did not fail, but requires context recompilation before it can be completed. + According to the specific use case, the caller should react appropriately. */ + LY_ENOT, /**< Negative result */ + LY_EOTHER, /**< Unknown error */ + + LY_EPLUGIN = 128/**< Error reported by a plugin - the highest bit in the first byte is set. + This value is used ORed with one of the other LY_ERR value and can be simply masked. */ +} LY_ERR; + +/** + * @ingroup logger + * @typedef LY_VECODE + * @brief libyang's codes of validation error. Whenever ly_errno is set to LY_EVALID, the ly_vecode is also set + * to the appropriate LY_VECODE value. + */ +typedef enum { + LYVE_SUCCESS = 0, /**< no error */ + LYVE_SYNTAX, /**< generic syntax error */ + LYVE_SYNTAX_YANG, /**< YANG-related syntax error */ + LYVE_SYNTAX_YIN, /**< YIN-related syntax error */ + LYVE_REFERENCE, /**< invalid referencing or using an item */ + LYVE_XPATH, /**< invalid XPath expression */ + LYVE_SEMANTICS, /**< generic semantic error */ + LYVE_SYNTAX_XML, /**< XML-related syntax error */ + LYVE_SYNTAX_JSON, /**< JSON-related syntax error */ + LYVE_DATA, /**< YANG data does not reflect some of the module restrictions */ + + LYVE_OTHER /**< Unknown error */ +} LY_VECODE; + +/** + * @brief Libyang full error structure. + */ +struct ly_err_item { + LY_LOG_LEVEL level; + LY_ERR no; + LY_VECODE vecode; + char *msg; + char *path; + char *apptag; + struct ly_err_item *next; + struct ly_err_item *prev; /* first item's prev points to the last item */ +}; + +/** + * @brief Get the last (thread, context-specific) validation error code. + * + * This value is set only if ly_errno is #LY_EVALID. + * + * @param[in] ctx Relative context. + * @return Validation error code. + */ +LIBYANG_API_DECL LY_VECODE ly_vecode(const struct ly_ctx *ctx); + +/** + * @brief Get the last (thread, context-specific) error code. + * + * @param[in] ctx Relative context. + * @return LY_ERR value of the last error code. + */ +LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx); + +/** + * @brief Get the last (thread, context-specific) error message. If the coresponding module defined + * a specific error message, it will be used instead the default one. + * + * Sometimes, the error message is extended with path of the element where the problem is. + * The path is available via ::ly_errpath(). + * + * @param[in] ctx Relative context. + * @return Text of the last error message, empty string if there is no error. + */ +LIBYANG_API_DECL const char *ly_errmsg(const struct ly_ctx *ctx); + +/** + * @brief Get the last (thread, context-specific) path of the element where was an error. + * + * The path always corresponds to the error message available via ::ly_errmsg(), so + * whenever a subsequent error message is printed, the path is erased or rewritten. + * The path reflects the type of the processed tree - data path for data tree functions + * and schema path in case of schema tree functions. In case of processing YIN schema + * or XML data, the path can be just XML path. In such a case, the corresponding + * ly_vecode (value 1-3) is set. + * + * @param[in] ctx Relative context. + * @return Path of the error element, empty string if error path does not apply to the last error. + */ +LIBYANG_API_DECL const char *ly_errpath(const struct ly_ctx *ctx); + +/** + * @brief Get the last (thread, context-specific) error-app-tag if there was a specific one defined + * in the module for the last error. + * + * The app-tag always corresponds to the error message available via ::ly_errmsg(), so + * whenever a subsequent error message is printed, the app-tag is erased or rewritten. + * + * @param[in] ctx Relative context. + * @return Error-app-tag of the last error, empty string if the error-app-tag does not apply to the last error. + */ +LIBYANG_API_DECL const char *ly_errapptag(const struct ly_ctx *ctx); + +/** + * @brief Get the first (thread, context-specific) generated error structure. + * + * @param[in] ctx Relative context. + * @return The first error structure (can be NULL), do not modify! + */ +LIBYANG_API_DECL struct ly_err_item *ly_err_first(const struct ly_ctx *ctx); + +/** + * @brief Get the latest (thread, context-specific) generated error structure. + * + * @param[in] ctx Relative context. + * @return The last error structure (can be NULL), do not modify! + */ +LIBYANG_API_DECL struct ly_err_item *ly_err_last(const struct ly_ctx *ctx); + +/** + * @brief Print the error structure as if just generated. + * + * @param[in] ctx Optional context to store the message in. + * @param[in] eitem Error item structure to print. + */ +LIBYANG_API_DECL void ly_err_print(const struct ly_ctx *ctx, struct ly_err_item *eitem); + +/** + * @brief Free error structures from a context. + * + * If \p eitem is not set, free all the error structures. + * + * @param[in] ctx Relative context. + * @param[in] eitem Oldest error structure to remove, optional. + */ +LIBYANG_API_DECL void ly_err_clean(struct ly_ctx *ctx, struct ly_err_item *eitem); + +/** @} errors */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_LOG_H_ */ diff --git a/src/lyb.c b/src/lyb.c new file mode 100644 index 0000000..87806c8 --- /dev/null +++ b/src/lyb.c @@ -0,0 +1,125 @@ +/** + * @file lyb.c + * @author Michal Vasko + * @brief LYB format common functionality. + * + * Copyright (c) 2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "lyb.h" + +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "tree_schema.h" + +/** + * @brief Generate single hash for a schema node to be used for LYB data. + * + * @param[in] node Node to hash. + * @param[in] collision_id Collision ID of the hash to generate. + * @return Generated hash. + */ +static LYB_HASH +lyb_generate_hash(const struct lysc_node *node, uint8_t collision_id) +{ + const struct lys_module *mod = node->module; + uint32_t full_hash; + LYB_HASH hash; + + /* generate full hash */ + full_hash = dict_hash_multi(0, mod->name, strlen(mod->name)); + full_hash = dict_hash_multi(full_hash, node->name, strlen(node->name)); + if (collision_id) { + size_t ext_len; + + if (collision_id > strlen(mod->name)) { + /* fine, we will not hash more bytes, just use more bits from the hash than previously */ + ext_len = strlen(mod->name); + } else { + /* use one more byte from the module name than before */ + ext_len = collision_id; + } + full_hash = dict_hash_multi(full_hash, mod->name, ext_len); + } + full_hash = dict_hash_multi(full_hash, NULL, 0); + + /* use the shortened hash */ + hash = full_hash & (LYB_HASH_MASK >> collision_id); + /* add collision identificator */ + hash |= LYB_HASH_COLLISION_ID >> collision_id; + + return hash; +} + +LYB_HASH +lyb_get_hash(const struct lysc_node *node, uint8_t collision_id) +{ + /* hashes must be cached */ + assert(node->hash[0]); + + if (collision_id < LYS_NODE_HASH_COUNT) { + /* read from cache */ + return node->hash[collision_id]; + } + + /* generate */ + return lyb_generate_hash(node, collision_id); +} + +/** + * @brief Module DFS callback filling all cached hashes of a schema node. + */ +static LY_ERR +lyb_cache_node_hash_cb(struct lysc_node *node, void *UNUSED(data), ly_bool *UNUSED(dfs_continue)) +{ + if (node->hash[0]) { + /* already cached, stop the DFS */ + return LY_EEXIST; + } + + for (uint8_t i = 0; i < LYS_NODE_HASH_COUNT; ++i) { + /* store the hash in the cache */ + node->hash[i] = lyb_generate_hash(node, i); + } + + return LY_SUCCESS; +} + +void +lyb_cache_module_hash(const struct lys_module *mod) +{ + /* LOCK */ + pthread_mutex_lock(&mod->ctx->lyb_hash_lock); + + /* store all cached hashes for all the nodes */ + lysc_module_dfs_full(mod, lyb_cache_node_hash_cb, NULL); + + /* UNLOCK */ + pthread_mutex_unlock(&mod->ctx->lyb_hash_lock); +} + +ly_bool +lyb_has_schema_model(const struct lysc_node *node, const struct lys_module **models) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(models, u) { + if (node->module == models[u]) { + return 1; + } + } + + return 0; +} diff --git a/src/lyb.h b/src/lyb.h new file mode 100644 index 0000000..b123ee5 --- /dev/null +++ b/src/lyb.h @@ -0,0 +1,199 @@ +/** + * @file lyb.h + * @author Michal Vasko + * @brief Header for LYB format printer & parser + * + * Copyright (c) 2020 - 2022 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_LYB_H_ +#define LY_LYB_H_ + +#include +#include + +#include "parser_internal.h" + +struct ly_ctx; +struct lysc_node; + +/* + * LYB format + * + * Unlike XML or JSON, it is binary format so most data are represented in similar way but in binary. + * Some notable differences: + * + * - schema nodes are identified based on their hash instead of their string name. In case of collisions + * an array of hashes is created with each next hash one bit shorter until a unique sequence of all these + * hashes is found and then all of them are stored. + * + * - tree structure is represented as individual strictly bounded "siblings". Each "siblings" begins + * with its metadata, which consist of 1) the whole "sibling" length in bytes and 2) number + * of included metadata chunks of nested "siblings". + * + * - since length of a "sibling" is not known before it is printed, holes are first written and + * after the "sibling" is printed, they are filled with actual valid metadata. As a consequence, + * LYB data cannot be directly printed into streams! + * + * - data are preceded with information about all the used modules. It is needed because of + * possible augments and deviations which must be known beforehand, otherwise schema hashes + * could be matched to the wrong nodes. + * + * This is a short summary of the format: + * @verbatim + + sb = siblings_start + se = siblings_end + siblings = zero-LYB_SIZE_BYTES | (sb instance+ se) + instance = node_type model hash node + model = 16bit_zero | (model_name_length model_name revision) + node = opaq | leaflist | list | any | inner | leaf + opaq = opaq_data siblings + leaflist = sb leaf+ se + list = sb (node_header siblings)+ se + any = node_header anydata_data + inner = node_header siblings + leaf = node_header term_value + node_header = metadata node_flags + + @endverbatim + */ + +/** + * @brief LYB data node type + */ +enum lylyb_node_type { + LYB_NODE_TOP, /**< top-level node */ + LYB_NODE_CHILD, /**< child node with a parent */ + LYB_NODE_OPAQ, /**< opaque node */ + LYB_NODE_EXT /**< nested extension data node */ +}; + +/** + * @brief LYB format parser context + */ +struct lylyb_ctx { + const struct ly_ctx *ctx; + uint64_t line; /* current line */ + struct ly_in *in; /* input structure */ + + const struct lys_module **models; + + struct lyd_lyb_sibling { + size_t written; + size_t position; + uint16_t inner_chunks; + } *siblings; + LY_ARRAY_COUNT_TYPE sibling_size; + + /* LYB printer only */ + struct lyd_lyb_sib_ht { + struct lysc_node *first_sibling; + struct hash_table *ht; + } *sib_hts; +}; + +/** + * @brief Destructor for the lylyb_ctx structure + */ +void lyd_lyb_ctx_free(struct lyd_ctx *lydctx); + +/* just a shortcut */ +#define LYB_LAST_SIBLING(lybctx) lybctx->siblings[LY_ARRAY_COUNT(lybctx->siblings) - 1] + +/* struct lyd_lyb_sibling allocation step */ +#define LYB_SIBLING_STEP 4 + +/* current LYB format version */ +#define LYB_VERSION_NUM 0x05 + +/* LYB format version mask of the header byte */ +#define LYB_VERSION_MASK 0x0F + +/** + * LYB schema hash constants + * + * Hash is divided to collision ID and hash itself. + * + * @anchor collisionid + * + * First bits are collision ID until 1 is found. The rest is truncated 32b hash. + * 1xxx xxxx - collision ID 0 (no collisions) + * 01xx xxxx - collision ID 1 (collision ID 0 hash collided) + * 001x xxxx - collision ID 2 ... + * + * When finding a match for a unique schema (siblings) hash (sequence of hashes with increasing collision ID), the highest + * collision ID can be read from the last hash (LYB parser). + * + * To learn what is the highest collision ID of a hash that must be included in a unique schema (siblings) hash, + * collisions with all the preceding sibling schema hashes must be checked (LYB printer). + */ + +/* Number of bits the whole hash will take (including hash collision ID) */ +#define LYB_HASH_BITS 8 + +/* Masking 32b hash (collision ID 0) */ +#define LYB_HASH_MASK 0x7f + +/* Type for storing the whole hash (used only internally, publicly defined directly) */ +#define LYB_HASH uint8_t + +/* Need to move this first >> collision number (from 0) to get collision ID hash part */ +#define LYB_HASH_COLLISION_ID 0x80 + +/* How many bytes are reserved for one data chunk SIZE (8B is maximum) */ +#define LYB_SIZE_BYTES 2 + +/* Maximum size that will be written into LYB_SIZE_BYTES (must be large enough) */ +#define LYB_SIZE_MAX UINT16_MAX + +/* How many bytes are reserved for one data chunk inner chunk count */ +#define LYB_INCHUNK_BYTES 2 + +/* Maximum size that will be written into LYB_INCHUNK_BYTES (must be large enough) */ +#define LYB_INCHUNK_MAX UINT16_MAX + +/* Just a helper macro */ +#define LYB_META_BYTES (LYB_INCHUNK_BYTES + LYB_SIZE_BYTES) + +/* model revision as XXXX XXXX XXXX XXXX (2B) (year is offset from 2000) + * YYYY YYYM MMMD DDDD */ +#define LYB_REV_YEAR_OFFSET 2000 +#define LYB_REV_YEAR_MASK 0xfe00U +#define LYB_REV_YEAR_SHIFT 9 +#define LYB_REV_MONTH_MASK 0x01E0U +#define LYB_REV_MONTH_SHIFT 5 +#define LYB_REV_DAY_MASK 0x001fU + +/** + * @brief Get single hash for a schema node to be used for LYB data. Read from cache, if possible. + * + * @param[in] node Node to hash. + * @param[in] collision_id Collision ID of the hash to generate, see @ref collisionid. + * @return Generated hash. + */ +LYB_HASH lyb_get_hash(const struct lysc_node *node, uint8_t collision_id); + +/** + * @brief Fill the hash cache of all the schema nodes of a module. + * + * @param[in] mod Module to process. + */ +void lyb_cache_module_hash(const struct lys_module *mod); + +/** + * @brief Check whether a node's module is in a module array. + * + * @param[in] node Node to check. + * @param[in] models Modules in a sized array. + * @return Boolean value whether @p node's module was found in the given @p models array. + */ +ly_bool lyb_has_schema_model(const struct lysc_node *node, const struct lys_module **models); + +#endif /* LY_LYB_H_ */ diff --git a/src/out.c b/src/out.c new file mode 100644 index 0000000..5567387 --- /dev/null +++ b/src/out.c @@ -0,0 +1,768 @@ +/** + * @file out.c + * @author Radek Krejci + * @brief libyang output functions. + * + * Copyright (c) 2015 - 2020 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 /* asprintf, strdup */ + +#include "out.h" +#include "out_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "log.h" +#include "printer_data.h" +#include "tree_data.h" +#include "tree_schema.h" + +/** + * @brief Align the desired size to 1 KB. + */ +#define REALLOC_CHUNK(NEW_SIZE) \ + NEW_SIZE + (1024 - (NEW_SIZE % 1024)) + +LIBYANG_API_DEF ly_bool +lyd_node_should_print(const struct lyd_node *node, uint32_t options) +{ + const struct lyd_node *elem; + + if (options & LYD_PRINT_WD_TRIM) { + /* do not print default nodes */ + if (node->flags & LYD_DEFAULT) { + /* implicit default node/NP container with only default nodes */ + return 0; + } else if (node->schema && (node->schema->nodetype & LYD_NODE_TERM)) { + if (lyd_is_default(node)) { + /* explicit default node */ + return 0; + } + } + } else if ((node->flags & LYD_DEFAULT) && (node->schema->nodetype == LYS_CONTAINER)) { + if (options & LYD_PRINT_KEEPEMPTYCONT) { + /* explicit request to print */ + return 1; + } + + /* avoid empty default containers */ + LYD_TREE_DFS_BEGIN(node, elem) { + if ((elem != node) && lyd_node_should_print(elem, options)) { + return 1; + } + assert(elem->flags & LYD_DEFAULT); + LYD_TREE_DFS_END(node, elem) + } + return 0; + } else if ((node->flags & LYD_DEFAULT) && !(options & LYD_PRINT_WD_MASK) && !(node->schema->flags & LYS_CONFIG_R)) { + /* LYD_PRINT_WD_EXPLICIT, find out if this is some input/output */ + if (!(node->schema->flags & (LYS_IS_INPUT | LYS_IS_OUTPUT | LYS_IS_NOTIF)) && (node->schema->flags & LYS_CONFIG_W)) { + /* print only if it contains status data in its subtree */ + LYD_TREE_DFS_BEGIN(node, elem) { + if ((elem->schema->nodetype != LYS_CONTAINER) || (elem->schema->flags & LYS_PRESENCE)) { + if (elem->schema->flags & LYS_CONFIG_R) { + return 1; + } + } + LYD_TREE_DFS_END(node, elem) + } + } + return 0; + } + + return 1; +} + +LIBYANG_API_DEF LY_OUT_TYPE +ly_out_type(const struct ly_out *out) +{ + LY_CHECK_ARG_RET(NULL, out, LY_OUT_ERROR); + return out->type; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_clb(ly_write_clb writeclb, void *user_data, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, writeclb, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_CALLBACK; + (*out)->method.clb.func = writeclb; + (*out)->method.clb.arg = user_data; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF ly_write_clb +ly_out_clb(struct ly_out *out, ly_write_clb writeclb) +{ + ly_write_clb prev_clb; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_CALLBACK, NULL); + + prev_clb = out->method.clb.func; + + if (writeclb) { + out->method.clb.func = writeclb; + } + + return prev_clb; +} + +LIBYANG_API_DEF void * +ly_out_clb_arg(struct ly_out *out, void *arg) +{ + void *prev_arg; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_CALLBACK, NULL); + + prev_arg = out->method.clb.arg; + + if (arg) { + out->method.clb.arg = arg; + } + + return prev_arg; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_fd(int fd, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, fd != -1, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + (*out)->type = LY_OUT_FD; + (*out)->method.fd = fd; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF int +ly_out_fd(struct ly_out *out, int fd) +{ + int prev_fd; + + LY_CHECK_ARG_RET(NULL, out, out->type <= LY_OUT_FDSTREAM, -1); + + if (out->type == LY_OUT_FDSTREAM) { + prev_fd = out->method.fdstream.fd; + } else { /* LY_OUT_FD */ + prev_fd = out->method.fd; + } + + if (fd != -1) { + /* replace output stream */ + if (out->type == LY_OUT_FDSTREAM) { + int streamfd; + FILE *stream; + + streamfd = dup(fd); + if (streamfd < 0) { + LOGERR(NULL, LY_ESYS, "Unable to duplicate provided file descriptor (%d) for printing the output (%s).", fd, strerror(errno)); + return -1; + } + stream = fdopen(streamfd, "a"); + if (!stream) { + LOGERR(NULL, LY_ESYS, "Unable to open provided file descriptor (%d) for printing the output (%s).", fd, strerror(errno)); + close(streamfd); + return -1; + } + /* close only the internally created stream, file descriptor is returned and supposed to be closed by the caller */ + fclose(out->method.fdstream.f); + out->method.fdstream.f = stream; + out->method.fdstream.fd = streamfd; + } else { /* LY_OUT_FD */ + out->method.fd = fd; + } + } + + return prev_fd; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_file(FILE *f, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, f, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_FILE; + (*out)->method.f = f; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF FILE * +ly_out_file(struct ly_out *out, FILE *f) +{ + FILE *prev_f; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_FILE, NULL); + + prev_f = out->method.f; + + if (f) { + out->method.f = f; + } + + return prev_f; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_memory(char **strp, size_t size, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, strp, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_MEMORY; + (*out)->method.mem.buf = strp; + if (!size) { + /* buffer is supposed to be allocated */ + *strp = NULL; + } else if (*strp) { + /* there is already buffer to use */ + (*out)->method.mem.size = size; + } + + return LY_SUCCESS; +} + +char * +ly_out_memory(struct ly_out *out, char **strp, size_t size) +{ + char *data; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_MEMORY, NULL); + + data = *out->method.mem.buf; + + if (strp) { + out->method.mem.buf = strp; + out->method.mem.len = out->method.mem.size = 0; + out->printed = 0; + if (!size) { + /* buffer is supposed to be allocated */ + *strp = NULL; + } else if (*strp) { + /* there is already buffer to use */ + out->method.mem.size = size; + } + } + + return data; +} + +LIBYANG_API_DEF LY_ERR +ly_out_reset(struct ly_out *out) +{ + LY_CHECK_ARG_RET(NULL, out, LY_EINVAL); + + switch (out->type) { + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + case LY_OUT_FD: + if ((lseek(out->method.fd, 0, SEEK_SET) == -1) && (errno != ESPIPE)) { + LOGERR(NULL, LY_ESYS, "Seeking output file descriptor failed (%s).", strerror(errno)); + return LY_ESYS; + } + if ((errno != ESPIPE) && (ftruncate(out->method.fd, 0) == -1)) { + LOGERR(NULL, LY_ESYS, "Truncating output file failed (%s).", strerror(errno)); + return LY_ESYS; + } + break; + case LY_OUT_FDSTREAM: + case LY_OUT_FILE: + case LY_OUT_FILEPATH: + if ((fseek(out->method.f, 0, SEEK_SET) == -1) && (errno != ESPIPE)) { + LOGERR(NULL, LY_ESYS, "Seeking output file stream failed (%s).", strerror(errno)); + return LY_ESYS; + } + if ((errno != ESPIPE) && (ftruncate(fileno(out->method.f), 0) == -1)) { + LOGERR(NULL, LY_ESYS, "Truncating output file failed (%s).", strerror(errno)); + return LY_ESYS; + } + break; + case LY_OUT_MEMORY: + if (out->method.mem.buf && *out->method.mem.buf) { + memset(*out->method.mem.buf, 0, out->method.mem.len); + } + out->printed = 0; + out->method.mem.len = 0; + break; + case LY_OUT_CALLBACK: + /* nothing to do (not seekable) */ + break; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_out_new_filepath(const char *filepath, struct ly_out **out) +{ + LY_CHECK_ARG_RET(NULL, out, filepath, LY_EINVAL); + + *out = calloc(1, sizeof **out); + LY_CHECK_ERR_RET(!*out, LOGMEM(NULL), LY_EMEM); + + (*out)->type = LY_OUT_FILEPATH; + (*out)->method.fpath.f = fopen(filepath, "wb"); + if (!(*out)->method.fpath.f) { + LOGERR(NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", filepath, strerror(errno)); + free(*out); + *out = NULL; + return LY_ESYS; + } + (*out)->method.fpath.filepath = strdup(filepath); + return LY_SUCCESS; +} + +LIBYANG_API_DEF const char * +ly_out_filepath(struct ly_out *out, const char *filepath) +{ + FILE *f; + + LY_CHECK_ARG_RET(NULL, out, out->type == LY_OUT_FILEPATH, filepath ? NULL : ((void *)-1)); + + if (!filepath) { + return out->method.fpath.filepath; + } + + /* replace filepath */ + f = out->method.fpath.f; + out->method.fpath.f = fopen(filepath, "wb"); + if (!out->method.fpath.f) { + LOGERR(NULL, LY_ESYS, "Failed to open file \"%s\" (%s).", filepath, strerror(errno)); + out->method.fpath.f = f; + return (void *)-1; + } + fclose(f); + free(out->method.fpath.filepath); + out->method.fpath.filepath = strdup(filepath); + + return NULL; +} + +LIBYANG_API_DEF void +ly_out_free(struct ly_out *out, void (*clb_arg_destructor)(void *arg), ly_bool destroy) +{ + if (!out) { + return; + } + + switch (out->type) { + case LY_OUT_CALLBACK: + if (clb_arg_destructor) { + clb_arg_destructor(out->method.clb.arg); + } + break; + case LY_OUT_FDSTREAM: + fclose(out->method.fdstream.f); + if (destroy) { + close(out->method.fdstream.fd); + } + break; + case LY_OUT_FD: + if (destroy) { + close(out->method.fd); + } + break; + case LY_OUT_FILE: + if (destroy) { + fclose(out->method.f); + } + break; + case LY_OUT_MEMORY: + if (destroy) { + free(*out->method.mem.buf); + } + break; + case LY_OUT_FILEPATH: + free(out->method.fpath.filepath); + fclose(out->method.fpath.f); + break; + case LY_OUT_ERROR: + LOGINT(NULL); + } + + free(out->buffered); + free(out); +} + +static LY_ERR +ly_vprint_(struct ly_out *out, const char *format, va_list ap) +{ + LY_ERR ret; + int written = 0; + char *msg = NULL, *aux; + + switch (out->type) { + case LY_OUT_FD: + written = vdprintf(out->method.fd, format, ap); + break; + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + written = vfprintf(out->method.f, format, ap); + break; + case LY_OUT_MEMORY: + if ((written = vasprintf(&msg, format, ap)) < 0) { + break; + } + if (out->method.mem.len + written + 1 > out->method.mem.size) { + aux = ly_realloc(*out->method.mem.buf, out->method.mem.len + written + 1); + if (!aux) { + out->method.mem.buf = NULL; + out->method.mem.len = 0; + out->method.mem.size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + *out->method.mem.buf = aux; + out->method.mem.size = out->method.mem.len + written + 1; + } + if (written) { + memcpy(&(*out->method.mem.buf)[out->method.mem.len], msg, written); + } + out->method.mem.len += written; + (*out->method.mem.buf)[out->method.mem.len] = '\0'; + free(msg); + break; + case LY_OUT_CALLBACK: + if ((written = vasprintf(&msg, format, ap)) < 0) { + break; + } + written = out->method.clb.func(out->method.clb.arg, msg, written); + free(msg); + break; + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + if (written < 0) { + LOGERR(NULL, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); + written = 0; + ret = LY_ESYS; + } else { + if (out->type == LY_OUT_FDSTREAM) { + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + } + ret = LY_SUCCESS; + } + + out->printed += written; + out->func_printed += written; + return ret; +} + +LY_ERR +ly_print_(struct ly_out *out, const char *format, ...) +{ + LY_ERR ret; + va_list ap; + + va_start(ap, format); + ret = ly_vprint_(out, format, ap); + va_end(ap); + + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_print(struct ly_out *out, const char *format, ...) +{ + LY_ERR ret; + va_list ap; + + out->func_printed = 0; + + va_start(ap, format); + ret = ly_vprint_(out, format, ap); + va_end(ap); + + return ret; +} + +LIBYANG_API_DEF void +ly_print_flush(struct ly_out *out) +{ + switch (out->type) { + case LY_OUT_FDSTREAM: + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + fflush(out->method.fdstream.f); + break; + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + fflush(out->method.f); + break; + case LY_OUT_FD: + fsync(out->method.fd); + break; + case LY_OUT_MEMORY: + case LY_OUT_CALLBACK: + /* nothing to do */ + break; + case LY_OUT_ERROR: + LOGINT(NULL); + } + + free(out->buffered); + out->buf_size = out->buf_len = 0; +} + +LY_ERR +ly_write_(struct ly_out *out, const char *buf, size_t len) +{ + LY_ERR ret = LY_SUCCESS; + size_t written = 0, new_mem_size; + + if (out->hole_count) { + /* we are buffering data after a hole */ + if (out->buf_len + len > out->buf_size) { + out->buffered = ly_realloc(out->buffered, out->buf_len + len); + if (!out->buffered) { + out->buf_len = 0; + out->buf_size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->buf_size = out->buf_len + len; + } + + if (len) { + memcpy(&out->buffered[out->buf_len], buf, len); + } + out->buf_len += len; + + out->printed += len; + out->func_printed += len; + return LY_SUCCESS; + } + +repeat: + switch (out->type) { + case LY_OUT_MEMORY: + new_mem_size = out->method.mem.len + len + 1; + if (new_mem_size > out->method.mem.size) { + new_mem_size = REALLOC_CHUNK(new_mem_size); + *out->method.mem.buf = ly_realloc(*out->method.mem.buf, new_mem_size); + if (!*out->method.mem.buf) { + out->method.mem.len = 0; + out->method.mem.size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->method.mem.size = new_mem_size; + } + if (len) { + memcpy(&(*out->method.mem.buf)[out->method.mem.len], buf, len); + } + out->method.mem.len += len; + (*out->method.mem.buf)[out->method.mem.len] = '\0'; + + written = len; + break; + case LY_OUT_FD: { + ssize_t r; + + r = write(out->method.fd, buf, len); + if (r < 0) { + ret = LY_ESYS; + } else { + written = (size_t)r; + } + break; + } + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + written = fwrite(buf, sizeof *buf, len, out->method.f); + if (written != len) { + ret = LY_ESYS; + } + break; + case LY_OUT_CALLBACK: { + ssize_t r; + + r = out->method.clb.func(out->method.clb.arg, buf, len); + if (r < 0) { + ret = LY_ESYS; + } else { + written = (size_t)r; + } + break; + } + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + if (ret) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { + ret = LY_SUCCESS; + goto repeat; + } + LOGERR(NULL, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); + written = 0; + } else if ((size_t)written != len) { + LOGERR(NULL, LY_ESYS, "%s: writing data failed (unable to write %u from %u data).", __func__, + len - (size_t)written, len); + ret = LY_ESYS; + } else { + if (out->type == LY_OUT_FDSTREAM) { + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + } + ret = LY_SUCCESS; + } + + out->printed += written; + out->func_printed += written; + return ret; +} + +LIBYANG_API_DEF LY_ERR +ly_write(struct ly_out *out, const char *buf, size_t len) +{ + out->func_printed = 0; + + return ly_write_(out, buf, len); +} + +LIBYANG_API_DEF size_t +ly_out_printed(const struct ly_out *out) +{ + return out->func_printed; +} + +LY_ERR +ly_write_skip(struct ly_out *out, size_t count, size_t *position) +{ + switch (out->type) { + case LY_OUT_MEMORY: + if (out->method.mem.len + count > out->method.mem.size) { + *out->method.mem.buf = ly_realloc(*out->method.mem.buf, out->method.mem.len + count); + if (!(*out->method.mem.buf)) { + out->method.mem.len = 0; + out->method.mem.size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->method.mem.size = out->method.mem.len + count; + } + + /* save the current position */ + *position = out->method.mem.len; + + /* skip the memory */ + out->method.mem.len += count; + break; + case LY_OUT_FD: + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + case LY_OUT_CALLBACK: + /* buffer the hole */ + if (out->buf_len + count > out->buf_size) { + out->buffered = ly_realloc(out->buffered, out->buf_len + count); + if (!out->buffered) { + out->buf_len = 0; + out->buf_size = 0; + LOGMEM(NULL); + return LY_EMEM; + } + out->buf_size = out->buf_len + count; + } + + /* save the current position */ + *position = out->buf_len; + + /* skip the memory */ + out->buf_len += count; + + /* increase hole counter */ + ++out->hole_count; + break; + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + /* update printed bytes counter despite we actually printed just a hole */ + out->printed += count; + out->func_printed += count; + return LY_SUCCESS; +} + +LY_ERR +ly_write_skipped(struct ly_out *out, size_t position, const char *buf, size_t count) +{ + LY_ERR ret = LY_SUCCESS; + + assert(count); + + switch (out->type) { + case LY_OUT_MEMORY: + /* write */ + memcpy(&(*out->method.mem.buf)[position], buf, count); + break; + case LY_OUT_FD: + case LY_OUT_FDSTREAM: + case LY_OUT_FILEPATH: + case LY_OUT_FILE: + case LY_OUT_CALLBACK: + if (out->buf_len < position + count) { + LOGMEM(NULL); + return LY_EMEM; + } + + /* write into the hole */ + memcpy(&out->buffered[position], buf, count); + + /* decrease hole counter */ + --out->hole_count; + + if (!out->hole_count) { + /* all holes filled, we can write the buffer, + * printed bytes counter is updated by ly_write_() */ + ret = ly_write_(out, out->buffered, out->buf_len); + out->buf_len = 0; + } + break; + case LY_OUT_ERROR: + LOGINT(NULL); + return LY_EINT; + } + + if (out->type == LY_OUT_FILEPATH) { + /* move the original file descriptor to the end of the output file */ + lseek(out->method.fdstream.fd, 0, SEEK_END); + } + return ret; +} diff --git a/src/out.h b/src/out.h new file mode 100644 index 0000000..ca97eff --- /dev/null +++ b/src/out.h @@ -0,0 +1,307 @@ +/** + * @file out.h + * @author Radek Krejci + * @brief libyang output structures and functions + * + * Copyright (c) 2015-2020 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_OUT_H_ +#define LY_OUT_H_ + +#include +#include +#ifdef _MSC_VER +# define ssize_t SSIZE_T +#endif + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page howtoOutput Output Processing + * + * libyang provides a mechanism to generalize work with the outputs (and [inputs](@ref howtoInput)) of + * the different types. The ::ly_out handler can be created providing necessary information connected with the specific + * output type and then used throughout the printers functions. The API allows to combine output from libyang (data or schema) + * printers and output directly provided by the caller (via ::ly_print() or ::ly_write()). + * + * Using a generic output handler avoids need to have a set of functions for each printer functionality and results in simpler API. + * + * The API allows to alter the target of the data behind the handler by another target (of the same type). Also resetting + * a seekable output is possible with ::ly_out_reset() to re-write the output. + * + * @note + * This mechanism was introduced in libyang 2.0. To simplify transition from libyang 1.0 to version 2.0 and also for + * some simple use case where using the output handler would be an overkill, there are some basic printer functions + * that do not require output handler. But remember, that functionality of these function can be limited in particular cases + * in contrast to the functions using output handlers. + * + * Functions List + * -------------- + * - ::ly_out_new_clb() + * - ::ly_out_new_fd() + * - ::ly_out_new_file() + * - ::ly_out_new_filepath() + * - ::ly_out_new_memory() + * + * - ::ly_out_clb() + * - ::ly_out_clb_arg() + * - ::ly_out_fd() + * - ::ly_out_file() + * - ::ly_out_filepath() + * - ::ly_out_memory() + * + * - ::ly_out_type() + * - ::ly_out_printed() + * + * - ::ly_out_reset() + * - ::ly_out_free() + * + * - ::ly_print() + * - ::ly_print_flush() + * - ::ly_write() + * + * libyang Printers List + * -------------------- + * - @subpage howtoSchemaPrinters + * - @subpage howtoDataPrinters + */ + +/** + * @struct ly_out + * @brief Printer output structure specifying where the data are printed. + */ +struct ly_out; + +/** + * @brief Common value for data as well as schema printers to avoid formatting indentations and new lines + */ +#define LY_PRINT_SHRINK 0x02 + +/** + * @brief Types of the printer's output + */ +typedef enum LY_OUT_TYPE { + LY_OUT_ERROR = -1, /**< error value to indicate failure of the functions returning LY_OUT_TYPE */ + LY_OUT_FD, /**< file descriptor printer */ + LY_OUT_FDSTREAM, /**< internal replacement for LY_OUT_FD in case vdprintf() is not available */ + LY_OUT_FILE, /**< FILE stream printer */ + LY_OUT_FILEPATH, /**< filepath printer */ + LY_OUT_MEMORY, /**< memory printer */ + LY_OUT_CALLBACK /**< callback printer */ +} LY_OUT_TYPE; + +/** + * @brief Get output type of the printer handler. + * + * @param[in] out Printer handler. + * @return Type of the printer's output. + */ +LIBYANG_API_DECL LY_OUT_TYPE ly_out_type(const struct ly_out *out); + +/** + * @brief Reset the output medium to write from its beginning, so the following printer function will rewrite the current data + * instead of appending. + * + * Note that in case the underlying output is not seekable (stream referring a pipe/FIFO/socket or the callback output type), + * nothing actually happens despite the function succeeds. Also note that the medium is not returned to the state it was when + * the handler was created. For example, file is seeked into the offset zero and truncated, the content from the time it was opened with + * ::ly_out_new_file() is not restored. + * + * @param[in] out Printer handler. + * @return LY_SUCCESS in case of success + * @return LY_ESYS in case of failure + */ +LIBYANG_API_DECL LY_ERR ly_out_reset(struct ly_out *out); + +/** + * @brief Generic write callback for data printed by libyang. + * + * @param[in] user_data Optional caller-specific argument. + * @param[in] buf Data to write. + * @param[in] count Number of bytes to write. + * @return Number of printed bytes. + * @return Negative value in case of error. + */ +typedef ssize_t (*ly_write_clb)(void *user_data, const void *buf, size_t count); + +/** + * @brief Create printer handler using callback printer function. + * + * @param[in] writeclb Pointer to the printer callback function writing the data (see write(2)). + * @param[in] user_data Optional caller-specific argument to be passed to the @p writeclb callback. + * @param[out] out Created printer handler supposed to be passed to different ly*_print() functions. + * @return LY_SUCCESS in case of success + * @return LY_EMEM in case allocating the @p out handler fails. + */ +LIBYANG_API_DECL LY_ERR ly_out_new_clb(ly_write_clb writeclb, void *user_data, struct ly_out **out); + +/** + * @brief Get or reset callback function associated with a callback printer handler. + * + * @param[in] out Printer handler. + * @param[in] writeclb Optional argument providing a new printer callback function for the handler. If NULL, only the current + * printer callback is returned. + * @return Previous printer callback. + */ +LIBYANG_API_DECL ly_write_clb ly_out_clb(struct ly_out *out, ly_write_clb writeclb); + +/** + * @brief Get or reset callback function's argument associated with a callback printer handler. + * + * @param[in] out Printer handler. + * @param[in] arg caller-specific argument to be passed to the callback function associated with the printer handler. + * If NULL, only the current file descriptor value is returned. + * @return The previous callback argument. + */ +LIBYANG_API_DECL void *ly_out_clb_arg(struct ly_out *out, void *arg); + +/** + * @brief Create printer handler using file descriptor. + * + * @param[in] fd File descriptor to use. + * @param[out] out Created printer handler supposed to be passed to different ly*_print() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_out_new_fd(int fd, struct ly_out **out); + +/** + * @brief Get or reset file descriptor printer handler. + * + * @param[in] out Printer handler. + * @param[in] fd Optional value of a new file descriptor for the handler. If -1, only the current file descriptor value is returned. + * @return Previous value of the file descriptor. Note that caller is responsible for closing the returned file descriptor in case of setting new descriptor @p fd. + * @return -1 in case of error when setting up the new file descriptor. + */ +LIBYANG_API_DECL int ly_out_fd(struct ly_out *out, int fd); + +/** + * @brief Create printer handler using file stream. + * + * @param[in] f File stream to use. + * @param[out] out Created printer handler supposed to be passed to different ly*_print() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_out_new_file(FILE *f, struct ly_out **out); + +/** + * @brief Get or reset file stream printer handler. + * + * @param[in] out Printer handler. + * @param[in] f Optional new file stream for the handler. If NULL, only the current file stream is returned. + * @return Previous file stream of the handler. Note that caller is responsible for closing the returned stream in case of setting new stream @p f. + */ +LIBYANG_API_DECL FILE *ly_out_file(struct ly_out *out, FILE *f); + +/** + * @brief Create printer handler using memory to dump data. + * + * @param[in] strp Pointer to store the resulting data. If it points to a pointer to an allocated buffer and + * @p size of the buffer is set, the buffer is used (and extended if needed) to store the printed data. + * @param[in] size Size of the buffer provided via @p strp. In case it is 0, the buffer for the printed data + * is newly allocated even if @p strp points to a pointer to an existing buffer. + * @param[out] out Created printer handler supposed to be passed to different ly*_print() functions. + * @return LY_SUCCESS in case of success + * @return LY_ERR value in case of failure. + */ +LIBYANG_API_DECL LY_ERR ly_out_new_memory(char **strp, size_t size, struct ly_out **out); + +/** + * @brief Get or change memory where the data are dumped. + * + * @param[in] out Printer handler. + * @param[in] strp Optional new string pointer to store the resulting data, same rules as in ::ly_out_new_memory() are applied. + * @param[in] size Size of the buffer provided via @p strp. In case it is 0, the buffer for the printed data + * is newly allocated even if @p strp points to a pointer to an existing buffer. In case the @p strp is NULL, this + * parameter is ignored. + * @return Previous dumped data. Note that the caller is responsible to free the data in case of changing string pointer @p strp. + */ +LIBYANG_API_DECL char *ly_out_memory(struct ly_out *out, char **strp, size_t size); + +/** + * @brief Create printer handler file of the given filename. + * + * @param[in] filepath Path of the file where to write data. + * @param[out] out Created printer handler supposed to be passed to different ly*_print() functions. + * @return NULL in case of error. + * @return Created printer handler supposed to be passed to different ly*_print_*() functions. + */ +LIBYANG_API_DECL LY_ERR ly_out_new_filepath(const char *filepath, struct ly_out **out); + +/** + * @brief Get or change the filepath of the file where the printer prints the data. + * + * Note that in case of changing the filepath, the current file is closed and a new one is + * created/opened instead of renaming the previous file. Also note that the previous filepath + * string is returned only in case of not changing it's value. + * + * @param[in] out Printer handler. + * @param[in] filepath Optional new filepath for the handler. If and only if NULL, the current filepath string is returned. + * @return Previous filepath string in case the @p filepath argument is NULL. + * @return NULL if changing filepath succeeds and ((void *)-1) otherwise. + */ +LIBYANG_API_DECL const char *ly_out_filepath(struct ly_out *out, const char *filepath); + +/** + * @brief Generic printer of the given format string into the specified output. + * + * Alternatively, ::ly_write() can be used. + * + * @param[in] out Output specification. + * @param[in] format Format string to be printed. + * @return LY_ERR value, get number of the printed bytes using ::ly_out_printed. + */ +LIBYANG_API_DECL LY_ERR ly_print(struct ly_out *out, const char *format, ...); + +/** + * @brief Flush the output from any internal buffers and clean any auxiliary data. + * @param[in] out Output specification. + */ +LIBYANG_API_DECL void ly_print_flush(struct ly_out *out); + +/** + * @brief Generic printer of the given string buffer into the specified output. + * + * Alternatively, ::ly_print() can be used. + * + * @param[in] out Output specification. + * @param[in] buf Memory buffer with the data to print. + * @param[in] len Length of the data to print in the @p buf. + * @return LY_ERR value, get number of the printed bytes using ::ly_out_printed. + */ +LIBYANG_API_DECL LY_ERR ly_write(struct ly_out *out, const char *buf, size_t len); + +/** + * @brief Get the number of printed bytes by the last function. + * + * @param[in] out Out structure used. + * @return Number of printed bytes. + */ +LIBYANG_API_DECL size_t ly_out_printed(const struct ly_out *out); + +/** + * @brief Free the printer handler. + * @param[in] out Printer handler to free. + * @param[in] clb_arg_destructor Freeing function for printer callback (LY_OUT_CALLBACK) argument. + * @param[in] destroy Flag to free allocated buffer (for LY_OUT_MEMORY) or to + * close stream/file descriptor (for LY_OUT_FD, LY_OUT_FDSTREAM and LY_OUT_FILE) + */ +LIBYANG_API_DECL void ly_out_free(struct ly_out *out, void (*clb_arg_destructor)(void *arg), ly_bool destroy); + +#ifdef __cplusplus +} +#endif + +#endif /* LY_OUT_H_ */ diff --git a/src/out_internal.h b/src/out_internal.h new file mode 100644 index 0000000..96c468c --- /dev/null +++ b/src/out_internal.h @@ -0,0 +1,110 @@ +/** + * @file out_internal.h + * @author Radek Krejci + * @brief Internal structures and functions for libyang + * + * Copyright (c) 2015-2020 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_OUT_INTERNAL_H_ +#define LY_OUT_INTERNAL_H_ + +#include "out.h" + +struct lyd_node; + +/** + * @brief Printer output structure specifying where the data are printed. + */ +struct ly_out { + LY_OUT_TYPE type; /**< type of the output to select the output method */ + + union { + int fd; /**< file descriptor for LY_OUT_FD type */ + FILE *f; /**< file structure for LY_OUT_FILE, LY_OUT_FDSTREAM and LY_OUT_FILEPATH types */ + + struct { + FILE *f; /**< file stream from the original file descriptor, variable is mapped to the LY_OUT_FILE's f */ + int fd; /**< original file descriptor, which was not used directly because of missing vdprintf() */ + } fdstream; /**< structure for LY_OUT_FDSTREAM type, which is LY_OUT_FD when vdprintf() is missing */ + struct { + FILE *f; /**< file structure for LY_OUT_FILEPATH, variable is mapped to the LY_OUT_FILE's f */ + char *filepath; /**< stored original filepath */ + } fpath; /**< filepath structure for LY_OUT_FILEPATH */ + struct { + char **buf; /**< storage for the pointer to the memory buffer to store the output */ + size_t len; /**< number of used bytes in the buffer */ + size_t size; /**< allocated size of the buffer */ + } mem; /**< memory buffer information for LY_OUT_MEMORY type */ + struct { + ssize_t (*func)(void *arg, const void *buf, size_t count); /**< callback function */ + void *arg; /**< optional argument for the callback function */ + } clb; /**< printer callback for LY_OUT_CALLBACK type */ + } method; /**< type-specific information about the output */ + + /* LYB only */ + char *buffered; /**< additional buffer for holes */ + size_t buf_len; /**< number of used bytes in the additional buffer for holes */ + size_t buf_size; /**< allocated size of the buffer for holes */ + size_t hole_count; /**< hole counter */ + + size_t printed; /**< Total number of printed bytes */ + size_t func_printed; /**< Number of bytes printed by the last function */ +}; + +/** + * @brief Generic printer of the given format string into the specified output. + * + * Does not reset printed bytes. Adds to printed bytes. + * + * @param[in] out Output specification. + * @param[in] format Format string to be printed. + * @return LY_ERR value. + */ +LY_ERR ly_print_(struct ly_out *out, const char *format, ...); + +/** + * @brief Generic printer of the given string buffer into the specified output. + * + * Does not reset printed bytes. Adds to printed bytes. + * + * @param[in] out Output specification. + * @param[in] buf Memory buffer with the data to print. + * @param[in] len Length of the data to print in the @p buf. + * @return LY_ERR value. + */ +LY_ERR ly_write_(struct ly_out *out, const char *buf, size_t len); + +/** + * @brief Create a hole in the output data that will be filled later. + * + * Adds printed bytes. + * + * @param[in] out Output specification. + * @param[in] len Length of the created hole. + * @param[out] position Position of the hole, value must be later provided to the ::ly_write_skipped() call. + * @return LY_ERR value. + */ +LY_ERR ly_write_skip(struct ly_out *out, size_t len, size_t *position); + +/** + * @brief Write data into the hole at given position. + * + * Does not change printed bytes. + * + * @param[in] out Output specification. + * @param[in] position Position of the hole to fill, the value was provided by ::ly_write_skip(). + * @param[in] buf Memory buffer with the data to print. + * @param[in] len Length of the data to print in the @p buf. Not that the length must correspond + * to the len value specified in the corresponding ::ly_write_skip() call. + * @return LY_ERR value. + */ +LY_ERR ly_write_skipped(struct ly_out *out, size_t position, const char *buf, size_t len); + +#endif /* LY_OUT_INTERNAL_H_ */ diff --git a/src/parser_common.c b/src/parser_common.c new file mode 100644 index 0000000..6fe068b --- /dev/null +++ b/src/parser_common.c @@ -0,0 +1,3567 @@ +/** + * @file parser_common.c + * @author Michal Vasko + * @brief libyang common parser functions. + * + * Copyright (c) 2015 - 2022 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 +#define _POSIX_C_SOURCE 200809L /* strdup, strndup */ + +#ifdef __APPLE__ +#define _DARWIN_C_SOURCE /* F_GETPATH */ +#endif + +#if defined (__NetBSD__) || defined (__OpenBSD__) +/* realpath */ +#define _XOPEN_SOURCE 1 +#define _XOPEN_SOURCE_EXTENDED 1 +#endif + +#include "parser_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "dict.h" +#include "in_internal.h" +#include "log.h" +#include "parser_data.h" +#include "path.h" +#include "plugins_exts/metadata.h" +#include "schema_features.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" + +void +lyd_ctx_free(struct lyd_ctx *lydctx) +{ + ly_set_erase(&lydctx->node_types, NULL); + ly_set_erase(&lydctx->meta_types, NULL); + ly_set_erase(&lydctx->node_when, NULL); + ly_set_erase(&lydctx->ext_node, free); + ly_set_erase(&lydctx->ext_val, free); +} + +LY_ERR +lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, struct lyd_node **op) +{ + const struct lyd_node *iter; + + *op = NULL; + + if (!parent) { + /* no parent, nothing to look for */ + return LY_SUCCESS; + } + + /* we need to find the operation node if it already exists */ + for (iter = parent; iter; iter = lyd_parent(iter)) { + if (iter->schema && (iter->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF))) { + break; + } + } + + if (!iter) { + /* no operation found */ + return LY_SUCCESS; + } + + if (!(int_opts & LYD_INTOPT_ANY)) { + if (!(int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY))) { + LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent %s \"%s\" node when not parsing any operation.", + lys_nodetype2str(iter->schema->nodetype), iter->schema->name); + return LY_EINVAL; + } else if ((iter->schema->nodetype == LYS_RPC) && !(int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY))) { + LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent RPC \"%s\" node when not parsing RPC nor rpc-reply.", + iter->schema->name); + return LY_EINVAL; + } else if ((iter->schema->nodetype == LYS_ACTION) && !(int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY))) { + LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent action \"%s\" node when not parsing action nor rpc-reply.", + iter->schema->name); + return LY_EINVAL; + } else if ((iter->schema->nodetype == LYS_NOTIF) && !(int_opts & LYD_INTOPT_NOTIF)) { + LOGERR(LYD_CTX(parent), LY_EINVAL, "Invalid parent notification \"%s\" node when not parsing a notification.", + iter->schema->name); + return LY_EINVAL; + } + } + + *op = (struct lyd_node *)iter; + return LY_SUCCESS; +} + +LY_ERR +lyd_parser_check_schema(struct lyd_ctx *lydctx, const struct lysc_node *snode) +{ + LY_ERR rc = LY_SUCCESS; + + LOG_LOCSET(snode, NULL, NULL, NULL); + + if (lydctx->int_opts & LYD_INTOPT_ANY) { + /* nothing to check, everything is allowed */ + goto cleanup; + } + + if ((lydctx->parse_opts & LYD_PARSE_NO_STATE) && (snode->flags & LYS_CONFIG_R)) { + LOGVAL(lydctx->data_ctx->ctx, LY_VCODE_UNEXPNODE, "state", snode->name); + rc = LY_EVALID; + goto cleanup; + } + + if (snode->nodetype == LYS_RPC) { + if (lydctx->int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) { + if (lydctx->op_node) { + goto error_node_dup; + } + } else { + goto error_node_inval; + } + } else if (snode->nodetype == LYS_ACTION) { + if (lydctx->int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) { + if (lydctx->op_node) { + goto error_node_dup; + } + } else { + goto error_node_inval; + } + } else if (snode->nodetype == LYS_NOTIF) { + if (lydctx->int_opts & LYD_INTOPT_NOTIF) { + if (lydctx->op_node) { + goto error_node_dup; + } + } else { + goto error_node_inval; + } + } + + /* success */ + goto cleanup; + +error_node_dup: + LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\", %s \"%s\" already parsed.", + lys_nodetype2str(snode->nodetype), snode->name, lys_nodetype2str(lydctx->op_node->schema->nodetype), + lydctx->op_node->schema->name); + rc = LY_EVALID; + goto cleanup; + +error_node_inval: + LOGVAL(lydctx->data_ctx->ctx, LYVE_DATA, "Unexpected %s element \"%s\".", lys_nodetype2str(snode->nodetype), + snode->name); + rc = LY_EVALID; + +cleanup: + LOG_LOCBACK(1, 0, 0, 0); + return rc; +} + +LY_ERR +lyd_parser_create_term(struct lyd_ctx *lydctx, const struct lysc_node *schema, const void *value, size_t value_len, + ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, struct lyd_node **node) +{ + ly_bool incomplete; + + LY_CHECK_RET(lyd_create_term(schema, value, value_len, dynamic, format, prefix_data, hints, &incomplete, node)); + + if (incomplete && !(lydctx->parse_opts & LYD_PARSE_ONLY)) { + LY_CHECK_RET(ly_set_add(&lydctx->node_types, *node, 1, NULL)); + } + return LY_SUCCESS; +} + +LY_ERR +lyd_parser_create_meta(struct lyd_ctx *lydctx, struct lyd_node *parent, struct lyd_meta **meta, const struct lys_module *mod, + const char *name, size_t name_len, const void *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, + void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node) +{ + ly_bool incomplete; + struct lyd_meta *first = NULL; + + if (meta && *meta) { + /* remember the first metadata */ + first = *meta; + } + + LY_CHECK_RET(lyd_create_meta(parent, meta, mod, name, name_len, value, value_len, dynamic, format, prefix_data, + hints, ctx_node, 0, &incomplete)); + + if (incomplete && !(lydctx->parse_opts & LYD_PARSE_ONLY)) { + LY_CHECK_RET(ly_set_add(&lydctx->meta_types, *meta, 1, NULL)); + } + + if (first) { + /* always return the first metadata */ + *meta = first; + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_parse_check_keys(struct lyd_node *node) +{ + const struct lysc_node *skey = NULL; + const struct lyd_node *key; + + assert(node->schema->nodetype == LYS_LIST); + + key = lyd_child(node); + while ((skey = lys_getnext(skey, node->schema, NULL, 0)) && (skey->flags & LYS_KEY)) { + if (!key || (key->schema != skey)) { + LOGVAL(LYD_CTX(node), LY_VCODE_NOKEY, skey->name); + return LY_EVALID; + } + + key = key->next; + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_parse_set_data_flags(struct lyd_node *node, struct lyd_meta **meta, struct lyd_ctx *lydctx, + struct lysc_ext_instance *ext) +{ + struct lyd_meta *meta2, *prev_meta = NULL; + struct lyd_ctx_ext_val *ext_val; + + if (lydctx->parse_opts & LYD_PARSE_NO_NEW) { + node->flags &= ~LYD_NEW; + } + + if (lysc_has_when(node->schema)) { + if (lydctx->parse_opts & LYD_PARSE_WHEN_TRUE) { + /* the condition was true before */ + node->flags |= LYD_WHEN_TRUE; + } + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { + /* remember we need to evaluate this node's when */ + LY_CHECK_RET(ly_set_add(&lydctx->node_when, node, 1, NULL)); + } + } + + LY_LIST_FOR(*meta, meta2) { + if (!strcmp(meta2->name, "default") && !strcmp(meta2->annotation->module->name, "ietf-netconf-with-defaults") && + meta2->value.boolean) { + /* node is default according to the metadata */ + node->flags |= LYD_DEFAULT; + + /* delete the metadata */ + if (prev_meta) { + prev_meta->next = meta2->next; + } else if (meta != &node->meta) { + *meta = (*meta)->next; + } + lyd_free_meta_single(meta2); + + /* update dflt flag for all parent NP containers */ + lyd_cont_set_dflt(lyd_parent(node)); + break; + } + + prev_meta = meta2; + } + + if (ext) { + /* parsed for an extension */ + node->flags |= LYD_EXT; + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { + /* rememeber for validation */ + ext_val = malloc(sizeof *ext_val); + LY_CHECK_ERR_RET(!ext_val, LOGMEM(LYD_CTX(node)), LY_EMEM); + ext_val->ext = ext; + ext_val->sibling = node; + LY_CHECK_RET(ly_set_add(&lydctx->ext_val, ext_val, 1, NULL)); + } + } + + return LY_SUCCESS; +} + +void +lys_parser_fill_filepath(struct ly_ctx *ctx, struct ly_in *in, const char **filepath) +{ + char path[PATH_MAX]; + +#ifndef __APPLE__ + char proc_path[32]; + int len; +#endif + + LY_CHECK_ARG_RET(NULL, ctx, in, filepath, ); + if (*filepath) { + /* filepath already set */ + return; + } + + switch (in->type) { + case LY_IN_FILEPATH: + if (realpath(in->method.fpath.filepath, path) != NULL) { + lydict_insert(ctx, path, 0, filepath); + } else { + lydict_insert(ctx, in->method.fpath.filepath, 0, filepath); + } + + break; + case LY_IN_FD: +#ifdef __APPLE__ + if (fcntl(in->method.fd, F_GETPATH, path) != -1) { + lydict_insert(ctx, path, 0, filepath); + } +#elif defined _WIN32 + HANDLE h = _get_osfhandle(in->method.fd); + FILE_NAME_INFO info; + + if (GetFileInformationByHandleEx(h, FileNameInfo, &info, sizeof info)) { + char *buf = calloc(info.FileNameLength + 1 /* trailing NULL */, MB_CUR_MAX); + + len = wcstombs(buf, info.FileName, info.FileNameLength * MB_CUR_MAX); + lydict_insert(ctx, buf, len, filepath); + } +#else + /* get URI if there is /proc */ + sprintf(proc_path, "/proc/self/fd/%d", in->method.fd); + if ((len = readlink(proc_path, path, PATH_MAX - 1)) > 0) { + lydict_insert(ctx, path, len, filepath); + } +#endif + break; + case LY_IN_MEMORY: + case LY_IN_FILE: + /* nothing to do */ + break; + default: + LOGINT(ctx); + break; + } +} + +static LY_ERR lysp_stmt_container(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings); +static LY_ERR lysp_stmt_choice(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings); +static LY_ERR lysp_stmt_case(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings); +static LY_ERR lysp_stmt_uses(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings); +static LY_ERR lysp_stmt_grouping(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node_grp **groupings); +static LY_ERR lysp_stmt_list(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings); + +/** + * @brief Validate stmt string value. + * + * @param[in] ctx Parser context. + * @param[in] val_type String value type. + * @param[in] val Value to validate. + * @return LY_ERR value. + */ +static LY_ERR +lysp_stmt_validate_value(struct lysp_ctx *ctx, enum yang_arg val_type, const char *val) +{ + uint8_t prefix = 0; + ly_bool first = 1; + uint32_t c; + size_t utf8_char_len; + + while (*val) { + LY_CHECK_ERR_RET(ly_getutf8(&val, &c, &utf8_char_len), + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, (val)[-utf8_char_len]), LY_EVALID); + + switch (val_type) { + case Y_IDENTIF_ARG: + LY_CHECK_RET(lysp_check_identifierchar(ctx, c, first, NULL)); + break; + case Y_PREF_IDENTIF_ARG: + LY_CHECK_RET(lysp_check_identifierchar(ctx, c, first, &prefix)); + break; + case Y_STR_ARG: + case Y_MAYBE_STR_ARG: + LY_CHECK_RET(lysp_check_stringchar(ctx, c)); + break; + } + first = 0; + } + + return LY_SUCCESS; +} + +/** + * @brief Duplicate statement siblings, recursively. + * + * @param[in] ctx Parser context. + * @param[in] stmt Statement to duplicate. + * @param[out] first First duplicated statement, the rest follow. + * @return LY_ERR value. + */ +static LY_ERR +lysp_stmt_dup(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_stmt **first) +{ + struct lysp_stmt *child, *last = NULL; + + LY_LIST_FOR(stmt, stmt) { + child = calloc(1, sizeof *child); + LY_CHECK_ERR_RET(!child, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + + if (last) { + last->next = child; + } else { + assert(!*first); + *first = child; + } + last = child; + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->stmt, 0, &child->stmt)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &child->arg)); + child->format = stmt->format; + LY_CHECK_RET(ly_dup_prefix_data(PARSER_CTX(ctx), stmt->format, stmt->prefix_data, &child->prefix_data)); + child->flags = stmt->flags; + child->kw = stmt->kw; + + /* recursively */ + LY_CHECK_RET(lysp_stmt_dup(ctx, stmt->child, &child->child)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse extension instance. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] insubstmt The statement this extension instance is a substatement of. + * @param[in] insubstmt_index Index of the keyword instance this extension instance is a substatement of. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_ext(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, enum ly_stmt insubstmt, + LY_ARRAY_COUNT_TYPE insubstmt_index, struct lysp_ext_instance **exts) +{ + struct lysp_ext_instance *e; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *exts, e, LY_EMEM); + + /* store name and insubstmt info */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->stmt, 0, &e->name)); + e->parent_stmt = insubstmt; + e->parent_stmt_index = insubstmt_index; + e->parsed = NULL; + LY_CHECK_RET(lysp_stmt_dup(ctx, stmt->child, &e->child)); + + /* get optional argument */ + if (stmt->arg) { + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &e->argument)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse a generic text field without specific constraints. Those are contact, organization, + * description, etc... + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] substmt_index Index of this substatement. + * @param[in,out] value Place to store the parsed value. + * @param[in] arg Type of the YANG keyword argument (of the value). + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_text_field(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32_t substmt_index, const char **value, + enum yang_arg arg, struct lysp_ext_instance **exts) +{ + if (*value) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, arg, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, value)); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, substmt_index, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse a qname that can have more instances such as if-feature. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] qnames Parsed qnames to add to. + * @param[in] arg Type of the expected argument. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_qnames(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_qname **qnames, enum yang_arg arg, + struct lysp_ext_instance **exts) +{ + struct lysp_qname *item; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, arg, stmt->arg)); + + /* allocate new pointer */ + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *qnames, item, LY_EMEM); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &item->str)); + item->mod = PARSER_CUR_PMOD(ctx); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, LY_ARRAY_COUNT(*qnames) - 1, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse a generic text field that can have more instances such as base. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] texts Parsed values to add to. + * @param[in] arg Type of the expected argument. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_text_fields(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, const char ***texts, enum yang_arg arg, + struct lysp_ext_instance **exts) +{ + const char **item; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, arg, stmt->arg)); + + /* allocate new pointer */ + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *texts, item, LY_EMEM); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, item)); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, LY_ARRAY_COUNT(*texts) - 1, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the status statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_status(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) +{ + size_t arg_len; + + if (*flags & LYS_STATUS_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "status"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if ((arg_len == ly_strlen_const("current")) && !strncmp(stmt->arg, "current", arg_len)) { + *flags |= LYS_STATUS_CURR; + } else if ((arg_len == ly_strlen_const("deprecated")) && !strncmp(stmt->arg, "deprecated", arg_len)) { + *flags |= LYS_STATUS_DEPRC; + } else if ((arg_len == ly_strlen_const("obsolete")) && !strncmp(stmt->arg, "obsolete", arg_len)) { + *flags |= LYS_STATUS_OBSLT; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "status"); + return LY_EVALID; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_STATUS, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "status"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the when statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] when_p When pointer to parse to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_when(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_when **when_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_when *when; + + if (*when_p) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "when"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + + when = calloc(1, sizeof *when); + LY_CHECK_ERR_RET(!when, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + *when_p = when; + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &when->cond)); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &when->dsc, Y_STR_ARG, &when->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &when->ref, Y_STR_ARG, &when->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_WHEN, 0, &when->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "when"); + return LY_EVALID; + } + } + return ret; +} + +/** + * @brief Parse the config statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_config(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) +{ + size_t arg_len; + + if (*flags & LYS_CONFIG_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "config"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if ((arg_len == ly_strlen_const("true")) && !strncmp(stmt->arg, "true", arg_len)) { + *flags |= LYS_CONFIG_W; + } else if ((arg_len == ly_strlen_const("false")) && !strncmp(stmt->arg, "false", arg_len)) { + *flags |= LYS_CONFIG_R; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "config"); + return LY_EVALID; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_CONFIG, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "config"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the mandatory statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_mandatory(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, + struct lysp_ext_instance **exts) +{ + size_t arg_len; + + if (*flags & LYS_MAND_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "mandatory"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if ((arg_len == ly_strlen_const("true")) && !strncmp(stmt->arg, "true", arg_len)) { + *flags |= LYS_MAND_TRUE; + } else if ((arg_len == ly_strlen_const("false")) && !strncmp(stmt->arg, "false", arg_len)) { + *flags |= LYS_MAND_FALSE; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "mandatory"); + return LY_EVALID; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_MANDATORY, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "mandatory"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse a restriction such as range or length. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_restr(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_restr *restr) +{ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &restr->arg.str)); + restr->arg.mod = PARSER_CUR_PMOD(ctx); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->dsc, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->ref, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_APP_TAG: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->eapptag, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_MESSAGE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->emsg, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &restr->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse a restriction that can have more instances such as must. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] restrs Restrictions to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_restrs(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_restr **restrs) +{ + struct lysp_restr *restr; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *restrs, restr, LY_EMEM); + return lysp_stmt_restr(ctx, stmt, restr); +} + +/** + * @brief Parse the anydata or anyxml statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_any(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, struct lysp_node **siblings) +{ + struct lysp_node_anydata *any; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new structure and insert into siblings */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, any, next, LY_EMEM); + + any->nodetype = stmt->kw == LY_STMT_ANYDATA ? LYS_ANYDATA : LYS_ANYXML; + any->parent = parent; + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &any->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &any->flags, &any->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &any->dsc, Y_STR_ARG, &any->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &any->iffeatures, Y_STR_ARG, &any->exts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(lysp_stmt_mandatory(ctx, child, &any->flags, &any->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &any->musts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &any->ref, Y_STR_ARG, &any->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &any->flags, &any->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &any->when)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &any->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), + (any->nodetype & LYS_ANYDATA) == LYS_ANYDATA ? lyplg_ext_stmt2str(LY_STMT_ANYDATA) : lyplg_ext_stmt2str(LY_STMT_ANYXML)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the value or position statement. Substatement of type enum statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] value Value to write to. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type_enum_value_pos(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, int64_t *value, uint16_t *flags, + struct lysp_ext_instance **exts) +{ + size_t arg_len; + char *ptr = NULL; + long long num = 0; + unsigned long long unum = 0; + + if (*flags & LYS_SET_VALUE) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + *flags |= LYS_SET_VALUE; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + + arg_len = strlen(stmt->arg); + if (!arg_len || (stmt->arg[0] == '+') || ((stmt->arg[0] == '0') && (arg_len > 1)) || + ((stmt->kw == LY_STMT_POSITION) && !strncmp(stmt->arg, "-0", 2))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, lyplg_ext_stmt2str(stmt->kw)); + goto error; + } + + errno = 0; + if (stmt->kw == LY_STMT_VALUE) { + num = strtoll(stmt->arg, &ptr, LY_BASE_DEC); + if ((num < INT64_C(-2147483648)) || (num > INT64_C(2147483647))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, lyplg_ext_stmt2str(stmt->kw)); + goto error; + } + } else { + unum = strtoull(stmt->arg, &ptr, LY_BASE_DEC); + if (unum > UINT64_C(4294967295)) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, lyplg_ext_stmt2str(stmt->kw)); + goto error; + } + } + /* we have not parsed the whole argument */ + if ((size_t)(ptr - stmt->arg) != arg_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, lyplg_ext_stmt2str(stmt->kw)); + goto error; + } + if (errno == ERANGE) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, arg_len, stmt->arg, lyplg_ext_stmt2str(stmt->kw)); + goto error; + } + if (stmt->kw == LY_STMT_VALUE) { + *value = num; + } else { + *value = unum; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw == LY_STMT_VALUE ? LY_STMT_VALUE : LY_STMT_POSITION, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + return LY_SUCCESS; + +error: + return LY_EVALID; +} + +/** + * @brief Parse the enum or bit statement. Substatement of type statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] enums Enums or bits to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type_enum(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_type_enum **enums) +{ + struct lysp_type_enum *enm; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, stmt->kw == LY_STMT_ENUM ? Y_STR_ARG : Y_IDENTIF_ARG, stmt->arg)); + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *enums, enm, LY_EMEM); + + if (stmt->kw == LY_STMT_ENUM) { + LY_CHECK_RET(lysp_check_enum_name(ctx, stmt->arg, strlen(stmt->arg))); + } /* else nothing specific for YANG_BIT */ + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &enm->name)); + CHECK_UNIQUENESS(ctx, *enums, name, lyplg_ext_stmt2str(stmt->kw), enm->name); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &enm->dsc, Y_STR_ARG, &enm->exts)); + break; + case LY_STMT_IF_FEATURE: + PARSER_CHECK_STMTVER2_RET(ctx, "if-feature", lyplg_ext_stmt2str(stmt->kw)); + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &enm->iffeatures, Y_STR_ARG, &enm->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &enm->ref, Y_STR_ARG, &enm->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &enm->flags, &enm->exts)); + break; + case LY_STMT_VALUE: + LY_CHECK_ERR_RET(stmt->kw == LY_STMT_BIT, LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), + lyplg_ext_stmt2str(stmt->kw)), LY_EVALID); + LY_CHECK_RET(lysp_stmt_type_enum_value_pos(ctx, child, &enm->value, &enm->flags, &enm->exts)); + break; + case LY_STMT_POSITION: + LY_CHECK_ERR_RET(stmt->kw == LY_STMT_ENUM, LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), + lyplg_ext_stmt2str(stmt->kw)), LY_EVALID); + LY_CHECK_RET(lysp_stmt_type_enum_value_pos(ctx, child, &enm->value, &enm->flags, &enm->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &enm->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the fraction-digits statement. Substatement of type statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] fracdig Value to write to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type_fracdigits(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint8_t *fracdig, + struct lysp_ext_instance **exts) +{ + char *ptr; + size_t arg_len; + unsigned long long num; + + if (*fracdig) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "fraction-digits"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if (!arg_len || (stmt->arg[0] == '0') || !isdigit(stmt->arg[0])) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "fraction-digits"); + return LY_EVALID; + } + + errno = 0; + num = strtoull(stmt->arg, &ptr, LY_BASE_DEC); + /* we have not parsed the whole argument */ + if ((size_t)(ptr - stmt->arg) != arg_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "fraction-digits"); + return LY_EVALID; + } + if ((errno == ERANGE) || (num > LY_TYPE_DEC64_FD_MAX)) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, arg_len, stmt->arg, "fraction-digits"); + return LY_EVALID; + } + *fracdig = num; + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_FRACTION_DIGITS, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "fraction-digits"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the require-instance statement. Substatement of type statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] reqinst Value to write to. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type_reqinstance(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint8_t *reqinst, uint16_t *flags, + struct lysp_ext_instance **exts) +{ + size_t arg_len; + + if (*flags & LYS_SET_REQINST) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "require-instance"); + return LY_EVALID; + } + *flags |= LYS_SET_REQINST; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if ((arg_len == ly_strlen_const("true")) && !strncmp(stmt->arg, "true", arg_len)) { + *reqinst = 1; + } else if ((arg_len != ly_strlen_const("false")) || strncmp(stmt->arg, "false", arg_len)) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "require-instance"); + return LY_EVALID; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_REQUIRE_INSTANCE, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "require-instance"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the modifier statement. Substatement of type pattern statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] pat Value to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type_pattern_modifier(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, const char **pat, + struct lysp_ext_instance **exts) +{ + size_t arg_len; + char *buf; + + if ((*pat)[0] == LYSP_RESTR_PATTERN_NACK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "modifier"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if ((arg_len != ly_strlen_const("invert-match")) || strncmp(stmt->arg, "invert-match", arg_len)) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "modifier"); + return LY_EVALID; + } + + /* replace the value in the dictionary */ + buf = malloc(strlen(*pat) + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + strcpy(buf, *pat); + lydict_remove(PARSER_CTX(ctx), *pat); + + assert(buf[0] == LYSP_RESTR_PATTERN_ACK); + buf[0] = LYSP_RESTR_PATTERN_NACK; + LY_CHECK_RET(lydict_insert_zc(PARSER_CTX(ctx), buf, pat)); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_MODIFIER, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "modifier"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the pattern statement. Substatement of type statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] patterns Restrictions to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type_pattern(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_restr **patterns) +{ + char *buf; + size_t arg_len; + struct lysp_restr *restr; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *patterns, restr, LY_EMEM); + arg_len = strlen(stmt->arg); + + /* add special meaning first byte */ + buf = malloc(arg_len + 2); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + memmove(buf + 1, stmt->arg, arg_len); + buf[0] = LYSP_RESTR_PATTERN_ACK; /* pattern's default regular-match flag */ + buf[arg_len + 1] = '\0'; /* terminating NULL byte */ + LY_CHECK_RET(lydict_insert_zc(PARSER_CTX(ctx), buf, &restr->arg.str)); + restr->arg.mod = PARSER_CUR_PMOD(ctx); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->dsc, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->ref, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_APP_TAG: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->eapptag, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_MESSAGE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &restr->emsg, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_MODIFIER: + PARSER_CHECK_STMTVER2_RET(ctx, "modifier", "pattern"); + LY_CHECK_RET(lysp_stmt_type_pattern_modifier(ctx, child, &restr->arg.str, &restr->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_PATTERN, 0, &restr->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "pattern"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the deviate statement. Substatement of deviation statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] devs Array of deviates to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_deviate(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_deviate **devs, struct lysp_ext_instance **exts) +{ + (void)stmt; + (void)devs; + (void)exts; + + /* TODO */ + LOGERR(PARSER_CTX(ctx), LY_EINVAL, "Extension instance \"deviate\" substatement is not supported."); + return LY_EINVAL; +} + +/** + * @brief Parse the deviation statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] deviations Array of deviations to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_deviation(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_deviation **deviations) +{ + struct lysp_deviation *dev; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *deviations, dev, LY_EMEM); + + /* store nodeid */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &dev->nodeid)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &dev->dsc, Y_STR_ARG, &dev->exts)); + break; + case LY_STMT_DEVIATE: + LY_CHECK_RET(lysp_stmt_deviate(ctx, child, &dev->deviates, &dev->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &dev->ref, Y_STR_ARG, &dev->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &dev->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_DEVIATION)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the yang-version statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[out] version Version to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_yangver(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint8_t *version, struct lysp_ext_instance **exts) +{ + if (*version) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "yin-element"); + return LY_EVALID; + } + + /* store flag */ + if (!strcmp(stmt->arg, "1")) { + *version = LYS_VERSION_1_0; + } else if (!strcmp(stmt->arg, "1.1")) { + *version = LYS_VERSION_1_1; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, strlen(stmt->arg), stmt->arg, "yang-version"); + return LY_EVALID; + } + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_YANG_VERSION)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the module statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] mod Module to fill. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_module(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_module *mod) +{ + (void)stmt; + (void)mod; + + /* TODO */ + LOGERR(PARSER_CTX(ctx), LY_EINVAL, "Extension instance \"module\" substatement is not supported."); + return LY_EINVAL; +} + +/** + * @brief Parse the submodule statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] submod Module to fill. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_submodule(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_submodule *submod) +{ + (void)stmt; + (void)submod; + + /* TODO */ + LOGERR(PARSER_CTX(ctx), LY_EINVAL, "Extension instance \"submodule\" substatement is not supported."); + return LY_EINVAL; +} + +/** + * @brief Parse the yin-element statement. Substatement of argument statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_yinelem(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) +{ + if (*flags & LYS_YINELEM_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "yin-element"); + return LY_EVALID; + } + + /* store flag */ + if (!strcmp(stmt->arg, "true")) { + *flags |= LYS_YINELEM_TRUE; + } else if (!strcmp(stmt->arg, "false")) { + *flags |= LYS_YINELEM_FALSE; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, strlen(stmt->arg), stmt->arg, "yin-element"); + return LY_EVALID; + } + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_YIN_ELEMENT)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the argument statement. Substatement of extension statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] ex Extension to fill. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_argument(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_ext *ex) +{ + if (ex->argname) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "argument"); + return LY_EVALID; + } + + /* store argument name */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_PREF_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &ex->argname)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_YIN_ELEMENT: + LY_CHECK_RET(lysp_stmt_yinelem(ctx, child, &ex->flags, &ex->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &ex->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_ARGUMENT)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the extension statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] extensions Array of extensions to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_extension(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_ext **extensions) +{ + struct lysp_ext *ex; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *extensions, ex, LY_EMEM); + + /* store name */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &ex->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &ex->dsc, Y_STR_ARG, &ex->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &ex->ref, Y_STR_ARG, &ex->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &ex->flags, &ex->exts)); + break; + case LY_STMT_ARGUMENT: + LY_CHECK_RET(lysp_stmt_argument(ctx, child, ex)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &ex->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_EXTENSION)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the feature statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] features Array of features to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_feature(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_feature **features) +{ + struct lysp_feature *feat; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *features, feat, LY_EMEM); + + /* store name */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &feat->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &feat->dsc, Y_STR_ARG, &feat->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &feat->iffeatures, Y_STR_ARG, &feat->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &feat->ref, Y_STR_ARG, &feat->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &feat->flags, &feat->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &feat->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_FEATURE)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the identity statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] identities Array of identities to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_identity(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_ident **identities) +{ + struct lysp_ident *ident; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *identities, ident, LY_EMEM); + + /* store name */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &ident->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &ident->dsc, Y_STR_ARG, &ident->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &ident->iffeatures, Y_STR_ARG, &ident->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &ident->ref, Y_STR_ARG, &ident->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &ident->flags, &ident->exts)); + break; + case LY_STMT_BASE: + LY_CHECK_RET(lysp_stmt_text_fields(ctx, child, &ident->bases, Y_PREF_IDENTIF_ARG, &ident->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &ident->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_IDENTITY)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the import statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] imports Array of imports to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_import(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_import **imports) +{ + struct lysp_import *imp; + const char *str = NULL; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *imports, imp, LY_EMEM); + + /* store name */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &imp->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_PREFIX: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &imp->prefix, Y_IDENTIF_ARG, &imp->exts)); + LY_CHECK_RET(lysp_check_prefix(ctx, *imports, NULL, &imp->prefix), LY_EVALID); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &imp->dsc, Y_STR_ARG, &imp->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &imp->ref, Y_STR_ARG, &imp->exts)); + break; + case LY_STMT_REVISION_DATE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, stmt, 0, &str, Y_STR_ARG, &imp->exts)); + strcpy(imp->rev, str); + lydict_remove(PARSER_CTX(ctx), str); + LY_CHECK_RET(lysp_check_date(ctx, imp->rev, LY_REV_SIZE - 1, "revision-date")); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &imp->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_IMPORT)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the include statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] includes Array of identities to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_include(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_include **includes) +{ + struct lysp_include *inc; + const char *str = NULL; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *includes, inc, LY_EMEM); + + /* store name */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &inc->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &inc->dsc, Y_STR_ARG, &inc->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &inc->ref, Y_STR_ARG, &inc->exts)); + break; + case LY_STMT_REVISION_DATE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, stmt, 0, &str, Y_STR_ARG, &inc->exts)); + strcpy(inc->rev, str); + lydict_remove(PARSER_CTX(ctx), str); + LY_CHECK_RET(lysp_check_date(ctx, inc->rev, LY_REV_SIZE - 1, "revision-date")); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &inc->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_INCLUDE)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the revision statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] includes Array of identities to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_revision(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_revision **revs) +{ + struct lysp_revision *rev; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *revs, rev, LY_EMEM); + + /* store date */ + LY_CHECK_RET(lysp_check_date(ctx, stmt->arg, strlen(stmt->arg), "revision")); + strncpy(rev->date, stmt->arg, LY_REV_SIZE - 1); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &rev->dsc, Y_STR_ARG, &rev->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &rev->ref, Y_STR_ARG, &rev->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &rev->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(LY_STMT_REVISION)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the type statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] type Type to wrote to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_type(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_type *type) +{ + struct lysp_type *nest_type; + const char *str_path = NULL; + LY_ERR ret; + + if (type->name) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "type"); + return LY_EVALID; + } + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_PREF_IDENTIF_ARG, stmt->arg)); + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &type->name)); + type->pmod = PARSER_CUR_PMOD(ctx); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_BASE: + LY_CHECK_RET(lysp_stmt_text_fields(ctx, child, &type->bases, Y_PREF_IDENTIF_ARG, &type->exts)); + type->flags |= LYS_SET_BASE; + break; + case LY_STMT_BIT: + LY_CHECK_RET(lysp_stmt_type_enum(ctx, child, &type->bits)); + type->flags |= LYS_SET_BIT; + break; + case LY_STMT_ENUM: + LY_CHECK_RET(lysp_stmt_type_enum(ctx, child, &type->enums)); + type->flags |= LYS_SET_ENUM; + break; + case LY_STMT_FRACTION_DIGITS: + LY_CHECK_RET(lysp_stmt_type_fracdigits(ctx, child, &type->fraction_digits, &type->exts)); + type->flags |= LYS_SET_FRDIGITS; + break; + case LY_STMT_LENGTH: + if (type->length) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(child->kw)); + return LY_EVALID; + } + type->length = calloc(1, sizeof *type->length); + LY_CHECK_ERR_RET(!type->length, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + + LY_CHECK_RET(lysp_stmt_restr(ctx, child, type->length)); + type->flags |= LYS_SET_LENGTH; + break; + case LY_STMT_PATH: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &str_path, Y_STR_ARG, &type->exts)); + ret = ly_path_parse(PARSER_CTX(ctx), NULL, str_path, 0, 1, LY_PATH_BEGIN_EITHER, + LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &type->path); + lydict_remove(PARSER_CTX(ctx), str_path); + LY_CHECK_RET(ret); + type->flags |= LYS_SET_PATH; + break; + case LY_STMT_PATTERN: + LY_CHECK_RET(lysp_stmt_type_pattern(ctx, child, &type->patterns)); + type->flags |= LYS_SET_PATTERN; + break; + case LY_STMT_RANGE: + if (type->range) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(child->kw)); + return LY_EVALID; + } + type->range = calloc(1, sizeof *type->range); + LY_CHECK_ERR_RET(!type->range, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + + LY_CHECK_RET(lysp_stmt_restr(ctx, child, type->range)); + type->flags |= LYS_SET_RANGE; + break; + case LY_STMT_REQUIRE_INSTANCE: + LY_CHECK_RET(lysp_stmt_type_reqinstance(ctx, child, &type->require_instance, &type->flags, &type->exts)); + /* LYS_SET_REQINST checked and set inside lysp_stmt_type_reqinstance() */ + break; + case LY_STMT_TYPE: + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), type->types, nest_type, LY_EMEM); + LY_CHECK_RET(lysp_stmt_type(ctx, child, nest_type)); + type->flags |= LYS_SET_TYPE; + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_TYPE, 0, &type->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "type"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the leaf statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_leaf(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, struct lysp_node **siblings) +{ + struct lysp_node_leaf *leaf; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new leaf structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, leaf, next, LY_EMEM); + leaf->nodetype = LYS_LEAF; + leaf->parent = parent; + + /* get name */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &leaf->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &leaf->flags, &leaf->exts)); + break; + case LY_STMT_DEFAULT: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &leaf->dflt.str, Y_STR_ARG, &leaf->exts)); + leaf->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &leaf->dsc, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &leaf->iffeatures, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(lysp_stmt_mandatory(ctx, child, &leaf->flags, &leaf->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &leaf->musts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &leaf->ref, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &leaf->flags, &leaf->exts)); + break; + case LY_STMT_TYPE: + LY_CHECK_RET(lysp_stmt_type(ctx, child, &leaf->type)); + break; + case LY_STMT_UNITS: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &leaf->units, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &leaf->when)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_LEAF, 0, &leaf->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "leaf"); + return LY_EVALID; + } + } + + /* mandatory substatements */ + if (!leaf->type.name) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "type", "leaf"); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the max-elements statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] max Value to write to. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_maxelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32_t *max, uint16_t *flags, + struct lysp_ext_instance **exts) +{ + size_t arg_len; + char *ptr; + unsigned long long num; + + if (*flags & LYS_SET_MAX) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "max-elements"); + return LY_EVALID; + } + *flags |= LYS_SET_MAX; + + /* get value */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + + if (!arg_len || (stmt->arg[0] == '0') || ((stmt->arg[0] != 'u') && !isdigit(stmt->arg[0]))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "max-elements"); + return LY_EVALID; + } + + if ((arg_len != ly_strlen_const("unbounded")) || strncmp(stmt->arg, "unbounded", arg_len)) { + errno = 0; + num = strtoull(stmt->arg, &ptr, LY_BASE_DEC); + /* we have not parsed the whole argument */ + if ((size_t)(ptr - stmt->arg) != arg_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "max-elements"); + return LY_EVALID; + } + if ((errno == ERANGE) || (num > UINT32_MAX)) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, arg_len, stmt->arg, "max-elements"); + return LY_EVALID; + } + + *max = num; + } else { + /* unbounded */ + *max = 0; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_MAX_ELEMENTS, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "max-elements"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the min-elements statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] min Value to write to. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_minelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32_t *min, uint16_t *flags, + struct lysp_ext_instance **exts) +{ + size_t arg_len; + char *ptr; + unsigned long long num; + + if (*flags & LYS_SET_MIN) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "min-elements"); + return LY_EVALID; + } + *flags |= LYS_SET_MIN; + + /* get value */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + + if (!arg_len || !isdigit(stmt->arg[0]) || ((stmt->arg[0] == '0') && (arg_len > 1))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "min-elements"); + return LY_EVALID; + } + + errno = 0; + num = strtoull(stmt->arg, &ptr, LY_BASE_DEC); + /* we have not parsed the whole argument */ + if ((size_t)(ptr - stmt->arg) != arg_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "min-elements"); + return LY_EVALID; + } + if ((errno == ERANGE) || (num > UINT32_MAX)) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, arg_len, stmt->arg, "min-elements"); + return LY_EVALID; + } + *min = num; + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_MIN_ELEMENTS, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "min-elements"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the ordered-by statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_orderedby(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) +{ + size_t arg_len; + + if (*flags & LYS_ORDBY_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "ordered-by"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + arg_len = strlen(stmt->arg); + if ((arg_len == ly_strlen_const("system")) && !strncmp(stmt->arg, "system", arg_len)) { + *flags |= LYS_MAND_TRUE; + } else if ((arg_len == ly_strlen_const("user")) && !strncmp(stmt->arg, "user", arg_len)) { + *flags |= LYS_MAND_FALSE; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "ordered-by"); + return LY_EVALID; + } + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_ORDERED_BY, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "ordered-by"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the leaf-list statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_leaflist(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings) +{ + struct lysp_node_leaflist *llist; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new leaf-list structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, llist, next, LY_EMEM); + llist->nodetype = LYS_LEAFLIST; + llist->parent = parent; + + /* get name */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &llist->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &llist->flags, &llist->exts)); + break; + case LY_STMT_DEFAULT: + PARSER_CHECK_STMTVER2_RET(ctx, "default", "leaf-list"); + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &llist->dflts, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &llist->dsc, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &llist->iffeatures, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_MAX_ELEMENTS: + LY_CHECK_RET(lysp_stmt_maxelements(ctx, child, &llist->max, &llist->flags, &llist->exts)); + break; + case LY_STMT_MIN_ELEMENTS: + LY_CHECK_RET(lysp_stmt_minelements(ctx, child, &llist->min, &llist->flags, &llist->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &llist->musts)); + break; + case LY_STMT_ORDERED_BY: + LY_CHECK_RET(lysp_stmt_orderedby(ctx, child, &llist->flags, &llist->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &llist->ref, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &llist->flags, &llist->exts)); + break; + case LY_STMT_TYPE: + LY_CHECK_RET(lysp_stmt_type(ctx, child, &llist->type)); + break; + case LY_STMT_UNITS: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &llist->units, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &llist->when)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_LEAF_LIST, 0, &llist->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "llist"); + return LY_EVALID; + } + } + + /* mandatory substatements */ + if (!llist->type.name) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "type", "leaf-list"); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the refine statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in,out] refines Refines to add to. + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_refine(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_refine **refines) +{ + struct lysp_refine *rf; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *refines, rf, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &rf->nodeid)); + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &rf->flags, &rf->exts)); + break; + case LY_STMT_DEFAULT: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &rf->dflts, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &rf->dsc, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_IF_FEATURE: + PARSER_CHECK_STMTVER2_RET(ctx, "if-feature", "refine"); + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &rf->iffeatures, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_MAX_ELEMENTS: + LY_CHECK_RET(lysp_stmt_maxelements(ctx, child, &rf->max, &rf->flags, &rf->exts)); + break; + case LY_STMT_MIN_ELEMENTS: + LY_CHECK_RET(lysp_stmt_minelements(ctx, child, &rf->min, &rf->flags, &rf->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &rf->musts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(lysp_stmt_mandatory(ctx, child, &rf->flags, &rf->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &rf->ref, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_PRESENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &rf->presence, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_REFINE, 0, &rf->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "refine"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the typedef statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] typedefs Typedefs to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_typedef(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_tpdf **typedefs) +{ + struct lysp_tpdf *tpdf; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *typedefs, tpdf, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &tpdf->name)); + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DEFAULT: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &tpdf->dflt.str, Y_STR_ARG, &tpdf->exts)); + tpdf->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &tpdf->dsc, Y_STR_ARG, &tpdf->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &tpdf->ref, Y_STR_ARG, &tpdf->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &tpdf->flags, &tpdf->exts)); + break; + case LY_STMT_TYPE: + LY_CHECK_RET(lysp_stmt_type(ctx, child, &tpdf->type)); + break; + case LY_STMT_UNITS: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &tpdf->units, Y_STR_ARG, &tpdf->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_TYPEDEF, 0, &tpdf->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "typedef"); + return LY_EVALID; + } + } + + /* mandatory substatements */ + if (!tpdf->type.name) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "type", "typedef"); + return LY_EVALID; + } + + /* store data for collision check */ + if (parent && !(parent->nodetype & (LYS_GROUPING | LYS_RPC | LYS_ACTION | LYS_INPUT | LYS_OUTPUT | LYS_NOTIF))) { + LY_CHECK_RET(ly_set_add(&ctx->tpdfs_nodes, parent, 0, NULL)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the input or output statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] inout_p Input/output pointer to write to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_inout(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node_action_inout *inout_p) +{ + if (inout_p->nodetype) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + + /* initiate structure */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->kw == LY_STMT_INPUT ? "input" : "output", 0, &inout_p->name)); + inout_p->nodetype = stmt->kw == LY_STMT_INPUT ? LYS_INPUT : LYS_OUTPUT; + inout_p->parent = parent; + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", lyplg_ext_stmt2str(stmt->kw)); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, &inout_p->node, &inout_p->child)); + break; + case LY_STMT_TYPEDEF: + LY_CHECK_RET(lysp_stmt_typedef(ctx, child, &inout_p->node, &inout_p->typedefs)); + break; + case LY_STMT_MUST: + PARSER_CHECK_STMTVER2_RET(ctx, "must", lyplg_ext_stmt2str(stmt->kw)); + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &inout_p->musts)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(lysp_stmt_grouping(ctx, child, &inout_p->node, &inout_p->groupings)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, stmt->kw, 0, &inout_p->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + } + + if (!inout_p->child) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "data-def-stmt", lyplg_ext_stmt2str(stmt->kw)); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the action statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] actions Actions to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_action(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node_action **actions) +{ + struct lysp_node_action *act; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + LY_LIST_NEW_RET(PARSER_CTX(ctx), actions, act, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &act->name)); + act->nodetype = parent ? LYS_ACTION : LYS_RPC; + act->parent = parent; + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &act->dsc, Y_STR_ARG, &act->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &act->iffeatures, Y_STR_ARG, &act->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &act->ref, Y_STR_ARG, &act->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &act->flags, &act->exts)); + break; + + case LY_STMT_INPUT: + LY_CHECK_RET(lysp_stmt_inout(ctx, child, &act->node, &act->input)); + break; + case LY_STMT_OUTPUT: + LY_CHECK_RET(lysp_stmt_inout(ctx, child, &act->node, &act->output)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(lysp_stmt_typedef(ctx, child, &act->node, &act->typedefs)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(lysp_stmt_grouping(ctx, child, &act->node, &act->groupings)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, parent ? LY_STMT_ACTION : LY_STMT_RPC, 0, &act->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), parent ? "action" : "rpc"); + return LY_EVALID; + } + } + + /* always initialize inout, they are technically present (needed for later deviations/refines) */ + if (!act->input.nodetype) { + act->input.nodetype = LYS_INPUT; + act->input.parent = &act->node; + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), "input", 0, &act->input.name)); + } + if (!act->output.nodetype) { + act->output.nodetype = LYS_OUTPUT; + act->output.parent = &act->node; + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), "output", 0, &act->output.name)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the notification statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] notifs Notifications to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_notif(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node_notif **notifs) +{ + struct lysp_node_notif *notif; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + LY_LIST_NEW_RET(PARSER_CTX(ctx), notifs, notif, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, ¬if->name)); + notif->nodetype = LYS_NOTIF; + notif->parent = parent; + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, ¬if->dsc, Y_STR_ARG, ¬if->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, ¬if->iffeatures, Y_STR_ARG, ¬if->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, ¬if->ref, Y_STR_ARG, ¬if->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, ¬if->flags, ¬if->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "notification"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, ¬if->node, ¬if->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_case(ctx, child, ¬if->node, ¬if->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, ¬if->node, ¬if->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, ¬if->node, ¬if->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, ¬if->node, ¬if->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, ¬if->node, ¬if->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, ¬if->node, ¬if->child)); + break; + + case LY_STMT_MUST: + PARSER_CHECK_STMTVER2_RET(ctx, "must", "notification"); + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, ¬if->musts)); + break; + case LY_STMT_TYPEDEF: + LY_CHECK_RET(lysp_stmt_typedef(ctx, child, ¬if->node, ¬if->typedefs)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(lysp_stmt_grouping(ctx, child, ¬if->node, ¬if->groupings)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_NOTIFICATION, 0, ¬if->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "notification"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the grouping statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] groupings Groupings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_grouping(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node_grp **groupings) +{ + struct lysp_node_grp *grp; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + LY_LIST_NEW_RET(PARSER_CTX(ctx), groupings, grp, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &grp->name)); + grp->nodetype = LYS_GROUPING; + grp->parent = parent; + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &grp->dsc, Y_STR_ARG, &grp->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &grp->ref, Y_STR_ARG, &grp->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &grp->flags, &grp->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "grouping"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &grp->node, &grp->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &grp->node, &grp->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &grp->node, &grp->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &grp->node, &grp->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &grp->node, &grp->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &grp->node, &grp->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, &grp->node, &grp->child)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(lysp_stmt_typedef(ctx, child, &grp->node, &grp->typedefs)); + break; + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "grouping"); + LY_CHECK_RET(lysp_stmt_action(ctx, child, &grp->node, &grp->actions)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(lysp_stmt_grouping(ctx, child, &grp->node, &grp->groupings)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "grouping"); + LY_CHECK_RET(lysp_stmt_notif(ctx, child, &grp->node, &grp->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_GROUPING, 0, &grp->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "grouping"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the augment statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] augments Augments to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_augment(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node_augment **augments) +{ + struct lysp_node_augment *aug; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_STR_ARG, stmt->arg)); + + LY_LIST_NEW_RET(PARSER_CTX(ctx), augments, aug, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &aug->nodeid)); + aug->nodetype = LYS_AUGMENT; + aug->parent = parent; + + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &aug->dsc, Y_STR_ARG, &aug->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &aug->iffeatures, Y_STR_ARG, &aug->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &aug->ref, Y_STR_ARG, &aug->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &aug->flags, &aug->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &aug->when)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "augment"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_CASE: + LY_CHECK_RET(lysp_stmt_case(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &aug->node, &aug->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, &aug->node, &aug->child)); + break; + + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "augment"); + LY_CHECK_RET(lysp_stmt_action(ctx, child, &aug->node, &aug->actions)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "augment"); + LY_CHECK_RET(lysp_stmt_notif(ctx, child, &aug->node, &aug->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_AUGMENT, 0, &aug->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "augment"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the uses statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_uses(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings) +{ + struct lysp_node_uses *uses; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_PREF_IDENTIF_ARG, stmt->arg)); + + /* create uses structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, uses, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &uses->name)); + uses->nodetype = LYS_USES; + uses->parent = parent; + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &uses->dsc, Y_STR_ARG, &uses->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &uses->iffeatures, Y_STR_ARG, &uses->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &uses->ref, Y_STR_ARG, &uses->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &uses->flags, &uses->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &uses->when)); + break; + + case LY_STMT_REFINE: + LY_CHECK_RET(lysp_stmt_refine(ctx, child, &uses->refines)); + break; + case LY_STMT_AUGMENT: + LY_CHECK_RET(lysp_stmt_augment(ctx, child, &uses->node, &uses->augments)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_USES, 0, &uses->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "uses"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the case statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_case(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings) +{ + struct lysp_node_case *cas; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new case structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, cas, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &cas->name)); + cas->nodetype = LYS_CASE; + cas->parent = parent; + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &cas->dsc, Y_STR_ARG, &cas->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &cas->iffeatures, Y_STR_ARG, &cas->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &cas->ref, Y_STR_ARG, &cas->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &cas->flags, &cas->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &cas->when)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "case"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, &cas->node, &cas->child)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_CASE, 0, &cas->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "case"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the choice statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_choice(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings) +{ + struct lysp_node_choice *choice; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new choice structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, choice, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &choice->name)); + choice->nodetype = LYS_CHOICE; + choice->parent = parent; + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &choice->flags, &choice->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &choice->dsc, Y_STR_ARG, &choice->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &choice->iffeatures, Y_STR_ARG, &choice->exts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(lysp_stmt_mandatory(ctx, child, &choice->flags, &choice->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &choice->ref, Y_STR_ARG, &choice->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &choice->flags, &choice->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &choice->when)); + break; + case LY_STMT_DEFAULT: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &choice->dflt.str, Y_PREF_IDENTIF_ARG, &choice->exts)); + choice->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "choice"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_CASE: + LY_CHECK_RET(lysp_stmt_case(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_CHOICE: + PARSER_CHECK_STMTVER2_RET(ctx, "choice", "choice"); + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &choice->node, &choice->child)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_CHOICE, 0, &choice->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "choice"); + return LY_EVALID; + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse the container statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_container(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings) +{ + struct lysp_node_container *cont; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new container structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, cont, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &cont->name)); + cont->nodetype = LYS_CONTAINER; + cont->parent = parent; + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &cont->flags, &cont->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &cont->dsc, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &cont->iffeatures, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &cont->ref, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &cont->flags, &cont->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &cont->when)); + break; + case LY_STMT_PRESENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &cont->presence, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "container"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &cont->node, &cont->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &cont->node, &cont->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &cont->node, &cont->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &cont->node, &cont->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &cont->node, &cont->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &cont->node, &cont->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, &cont->node, &cont->child)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(lysp_stmt_typedef(ctx, child, &cont->node, &cont->typedefs)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &cont->musts)); + break; + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "container"); + LY_CHECK_RET(lysp_stmt_action(ctx, child, &cont->node, &cont->actions)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(lysp_stmt_grouping(ctx, child, &cont->node, &cont->groupings)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "container"); + LY_CHECK_RET(lysp_stmt_notif(ctx, child, &cont->node, &cont->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_CONTAINER, 0, &cont->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "container"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse the list statement. + * + * @param[in] ctx parser context. + * @param[in] stmt Source statement data from the parsed extension instance. + * @param[in] parent Parent node to connect to (not into). + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +static LY_ERR +lysp_stmt_list(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_node *parent, + struct lysp_node **siblings) +{ + struct lysp_node_list *list; + + LY_CHECK_RET(lysp_stmt_validate_value(ctx, Y_IDENTIF_ARG, stmt->arg)); + + /* create new list structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, list, next, LY_EMEM); + + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), stmt->arg, 0, &list->name)); + list->nodetype = LYS_LIST; + list->parent = parent; + + /* parse substatements */ + for (const struct lysp_stmt *child = stmt->child; child; child = child->next) { + switch (child->kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(lysp_stmt_config(ctx, child, &list->flags, &list->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &list->dsc, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &list->iffeatures, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &list->ref, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(lysp_stmt_status(ctx, child, &list->flags, &list->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(lysp_stmt_when(ctx, child, &list->when)); + break; + case LY_STMT_KEY: + LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &list->key, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_MAX_ELEMENTS: + LY_CHECK_RET(lysp_stmt_maxelements(ctx, child, &list->max, &list->flags, &list->exts)); + break; + case LY_STMT_MIN_ELEMENTS: + LY_CHECK_RET(lysp_stmt_minelements(ctx, child, &list->min, &list->flags, &list->exts)); + break; + case LY_STMT_ORDERED_BY: + LY_CHECK_RET(lysp_stmt_orderedby(ctx, child, &list->flags, &list->exts)); + break; + case LY_STMT_UNIQUE: + LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &list->uniques, Y_STR_ARG, &list->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "list"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(lysp_stmt_any(ctx, child, &list->node, &list->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(lysp_stmt_choice(ctx, child, &list->node, &list->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(lysp_stmt_container(ctx, child, &list->node, &list->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &list->node, &list->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &list->node, &list->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(lysp_stmt_list(ctx, child, &list->node, &list->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(lysp_stmt_uses(ctx, child, &list->node, &list->child)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(lysp_stmt_typedef(ctx, child, &list->node, &list->typedefs)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &list->musts)); + break; + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "list"); + LY_CHECK_RET(lysp_stmt_action(ctx, child, &list->node, &list->actions)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(lysp_stmt_grouping(ctx, child, &list->node, &list->groupings)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "list"); + LY_CHECK_RET(lysp_stmt_notif(ctx, child, &list->node, &list->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_LIST, 0, &list->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(child->kw), "list"); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse generic statement structure into a specific parsed-schema structure. + * + * @param[in] pctx Parse context of the @p stmt being processed. + * @param[in] stmt Generic statement structure to process. + * @param[out] result Specific parsed-schema structure for the given statement. For the specific type for the particular statement, check the function code. + * @param[in,out] exts [sized array](@ref sizedarrays) For extension instances in case of statements that do not store extension instances in their own list. + * @return LY_ERR value. + */ +static LY_ERR +lysp_stmt_parse(struct lysp_ctx *pctx, const struct lysp_stmt *stmt, void **result, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + uint16_t flags; + + switch (stmt->kw) { + case LY_STMT_NOTIFICATION: + ret = lysp_stmt_notif(pctx, stmt, NULL, (struct lysp_node_notif **)result); + break; + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: { + struct lysp_node_action_inout *inout; + + *result = inout = calloc(1, sizeof *inout); + LY_CHECK_ERR_RET(!inout, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + ret = lysp_stmt_inout(pctx, stmt, NULL, inout); + break; + } + case LY_STMT_ACTION: + case LY_STMT_RPC: + ret = lysp_stmt_action(pctx, stmt, NULL, (struct lysp_node_action **)result); + break; + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + ret = lysp_stmt_any(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_AUGMENT: + ret = lysp_stmt_augment(pctx, stmt, NULL, (struct lysp_node_augment **)result); + break; + case LY_STMT_CASE: + ret = lysp_stmt_case(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_CHOICE: + ret = lysp_stmt_choice(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_CONTAINER: + ret = lysp_stmt_container(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_GROUPING: + ret = lysp_stmt_grouping(pctx, stmt, NULL, (struct lysp_node_grp **)result); + break; + case LY_STMT_LEAF: + ret = lysp_stmt_leaf(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_LEAF_LIST: + ret = lysp_stmt_leaflist(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_LIST: + ret = lysp_stmt_list(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_USES: + ret = lysp_stmt_uses(pctx, stmt, NULL, (struct lysp_node **)result); + break; + case LY_STMT_BASE: + ret = lysp_stmt_text_fields(pctx, stmt, (const char ***)result, Y_PREF_IDENTIF_ARG, exts); + break; + case LY_STMT_ARGUMENT: + case LY_STMT_BELONGS_TO: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_KEY: + case LY_STMT_NAMESPACE: + case LY_STMT_ORGANIZATION: + case LY_STMT_PRESENCE: + case LY_STMT_REFERENCE: + case LY_STMT_REVISION_DATE: + case LY_STMT_UNITS: + ret = lysp_stmt_text_field(pctx, stmt, 0, (const char **)result, Y_STR_ARG, exts); + break; + case LY_STMT_BIT: + case LY_STMT_ENUM: + ret = lysp_stmt_type_enum(pctx, stmt, (struct lysp_type_enum **)result); + break; + case LY_STMT_CONFIG: + assert(*result); + ret = lysp_stmt_config(pctx, stmt, *(uint16_t **)result, exts); + break; + case LY_STMT_DEFAULT: + case LY_STMT_IF_FEATURE: + case LY_STMT_UNIQUE: + ret = lysp_stmt_qnames(pctx, stmt, (struct lysp_qname **)result, Y_STR_ARG, exts); + break; + case LY_STMT_DEVIATE: + ret = lysp_stmt_deviate(pctx, stmt, (struct lysp_deviate **)result, exts); + break; + case LY_STMT_DEVIATION: + ret = lysp_stmt_deviation(pctx, stmt, (struct lysp_deviation **)result); + break; + case LY_STMT_EXTENSION: + ret = lysp_stmt_extension(pctx, stmt, (struct lysp_ext **)result); + break; + case LY_STMT_EXTENSION_INSTANCE: + ret = lysp_stmt_ext(pctx, stmt, LY_STMT_EXTENSION_INSTANCE, 0, (struct lysp_ext_instance **)result); + break; + case LY_STMT_FEATURE: + ret = lysp_stmt_feature(pctx, stmt, (struct lysp_feature **)result); + break; + case LY_STMT_FRACTION_DIGITS: + ret = lysp_stmt_type_fracdigits(pctx, stmt, *(uint8_t **)result, exts); + break; + case LY_STMT_LENGTH: + case LY_STMT_RANGE: { + struct lysp_restr *restr; + + *result = restr = calloc(1, sizeof *restr); + LY_CHECK_ERR_RET(!restr, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + + ret = lysp_stmt_restr(pctx, stmt, restr); + break; + } + case LY_STMT_MUST: + ret = lysp_stmt_restrs(pctx, stmt, (struct lysp_restr **)result); + break; + case LY_STMT_IDENTITY: + ret = lysp_stmt_identity(pctx, stmt, (struct lysp_ident **)result); + break; + case LY_STMT_IMPORT: + ret = lysp_stmt_import(pctx, stmt, (struct lysp_import **)result); + break; + case LY_STMT_INCLUDE: + ret = lysp_stmt_include(pctx, stmt, (struct lysp_include **)result); + break; + case LY_STMT_MANDATORY: + ret = lysp_stmt_mandatory(pctx, stmt, *(uint16_t **)result, exts); + break; + case LY_STMT_MAX_ELEMENTS: + flags = 0; + ret = lysp_stmt_maxelements(pctx, stmt, *(uint32_t **)result, &flags, exts); + break; + case LY_STMT_MIN_ELEMENTS: + flags = 0; + ret = lysp_stmt_minelements(pctx, stmt, *(uint32_t **)result, &flags, exts); + break; + case LY_STMT_MODIFIER: + ret = lysp_stmt_type_pattern_modifier(pctx, stmt, (const char **)result, exts); + break; + case LY_STMT_MODULE: { + struct lysp_module *mod; + + *result = mod = calloc(1, sizeof *mod); + LY_CHECK_ERR_RET(!mod, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + ret = lysp_stmt_module(pctx, stmt, mod); + break; + } + case LY_STMT_ORDERED_BY: + ret = lysp_stmt_orderedby(pctx, stmt, *(uint16_t **)result, exts); + break; + case LY_STMT_PATH: { + const char *str_path = NULL; + + LY_CHECK_RET(lysp_stmt_text_field(pctx, stmt, 0, &str_path, Y_STR_ARG, exts)); + ret = ly_path_parse(PARSER_CTX(pctx), NULL, str_path, 0, 1, LY_PATH_BEGIN_EITHER, + LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, (struct lyxp_expr **)result); + lydict_remove(PARSER_CTX(pctx), str_path); + break; + } + case LY_STMT_PATTERN: + ret = lysp_stmt_type_pattern(pctx, stmt, (struct lysp_restr **)result); + break; + case LY_STMT_POSITION: + case LY_STMT_VALUE: + flags = 0; + ret = lysp_stmt_type_enum_value_pos(pctx, stmt, *(int64_t **)result, &flags, exts); + break; + case LY_STMT_PREFIX: + ret = lysp_stmt_text_field(pctx, stmt, 0, (const char **)result, Y_IDENTIF_ARG, exts); + break; + case LY_STMT_REFINE: + ret = lysp_stmt_refine(pctx, stmt, (struct lysp_refine **)result); + break; + case LY_STMT_REQUIRE_INSTANCE: + flags = 0; + ret = lysp_stmt_type_reqinstance(pctx, stmt, *(uint8_t **)result, &flags, exts); + break; + case LY_STMT_REVISION: + ret = lysp_stmt_revision(pctx, stmt, (struct lysp_revision **)result); + break; + case LY_STMT_STATUS: + ret = lysp_stmt_status(pctx, stmt, (uint16_t *)result, exts); + break; + case LY_STMT_SUBMODULE: { + struct lysp_submodule *submod; + + *result = submod = calloc(1, sizeof *submod); + LY_CHECK_ERR_RET(!submod, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + ret = lysp_stmt_submodule(pctx, stmt, submod); + break; + } + case LY_STMT_TYPE: { + struct lysp_type *type; + + *result = type = calloc(1, sizeof *type); + LY_CHECK_ERR_RET(!type, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + ret = lysp_stmt_type(pctx, stmt, type); + break; + } + case LY_STMT_TYPEDEF: + ret = lysp_stmt_typedef(pctx, stmt, NULL, (struct lysp_tpdf **)result); + break; + case LY_STMT_WHEN: + ret = lysp_stmt_when(pctx, stmt, (struct lysp_when **)result); + break; + case LY_STMT_YANG_VERSION: + ret = lysp_stmt_yangver(pctx, stmt, *(uint8_t **)result, exts); + break; + case LY_STMT_YIN_ELEMENT: + ret = lysp_stmt_yinelem(pctx, stmt, *(uint16_t **)result, exts); + break; + default: + LOGINT(PARSER_CTX(pctx)); + return LY_EINT; + } + + return ret; +} + +LY_ERR +lys_parse_ext_instance_stmt(struct lysp_ctx *pctx, struct lysp_ext_substmt *substmt, struct lysp_stmt *stmt) +{ + LY_ERR rc = LY_SUCCESS; + + if (!substmt->storage) { + /* nothing to parse, ignored */ + goto cleanup; + } + + switch (stmt->kw) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_AUGMENT: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_GROUPING: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_USES: { + struct lysp_node **pnodes_p, *pnode = NULL; + + /* parse the node */ + LY_CHECK_GOTO(rc = lysp_stmt_parse(pctx, stmt, (void **)&pnode, NULL), cleanup); + + /* usually is a linked-list of all the parsed schema nodes */ + pnodes_p = substmt->storage; + while (*pnodes_p) { + pnodes_p = &(*pnodes_p)->next; + } + *pnodes_p = pnode; + + break; + } + case LY_STMT_BASE: + case LY_STMT_BIT: + case LY_STMT_DEFAULT: + case LY_STMT_DEVIATE: + case LY_STMT_DEVIATION: + case LY_STMT_ENUM: + case LY_STMT_EXTENSION: + case LY_STMT_EXTENSION_INSTANCE: + case LY_STMT_FEATURE: + case LY_STMT_IDENTITY: + case LY_STMT_IF_FEATURE: + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + case LY_STMT_MUST: + case LY_STMT_PATTERN: + case LY_STMT_REFINE: + case LY_STMT_REVISION: + case LY_STMT_TYPEDEF: + case LY_STMT_UNIQUE: + /* parse, sized array */ + LY_CHECK_GOTO(rc = lysp_stmt_parse(pctx, stmt, substmt->storage, NULL), cleanup); + break; + + case LY_STMT_ARGUMENT: + case LY_STMT_BELONGS_TO: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_KEY: + case LY_STMT_LENGTH: + case LY_STMT_MANDATORY: + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + case LY_STMT_MODIFIER: + case LY_STMT_MODULE: + case LY_STMT_NAMESPACE: + case LY_STMT_ORGANIZATION: + case LY_STMT_PATH: + case LY_STMT_POSITION: + case LY_STMT_PREFIX: + case LY_STMT_PRESENCE: + case LY_STMT_RANGE: + case LY_STMT_REFERENCE: + case LY_STMT_REQUIRE_INSTANCE: + case LY_STMT_REVISION_DATE: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPE: + case LY_STMT_UNITS: + case LY_STMT_VALUE: + case LY_STMT_WHEN: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + /* single item */ + if (*(void **)substmt->storage) { + LOGVAL(PARSER_CTX(pctx), LY_VCODE_DUPSTMT, stmt->stmt); + rc = LY_EVALID; + goto cleanup; + } + + /* parse */ + LY_CHECK_GOTO(rc = lysp_stmt_parse(pctx, stmt, substmt->storage, NULL), cleanup); + break; + + case LY_STMT_CONFIG: + /* single item */ + if ((*(uint16_t *)substmt->storage) & LYS_CONFIG_MASK) { + LOGVAL(PARSER_CTX(pctx), LY_VCODE_DUPSTMT, stmt->stmt); + rc = LY_EVALID; + goto cleanup; + } + + /* parse */ + LY_CHECK_GOTO(rc = lysp_stmt_parse(pctx, stmt, substmt->storage, NULL), cleanup); + break; + + case LY_STMT_ORDERED_BY: + /* single item */ + if ((*(uint16_t *)substmt->storage) & LYS_ORDBY_MASK) { + LOGVAL(PARSER_CTX(pctx), LY_VCODE_DUPSTMT, stmt->stmt); + rc = LY_EVALID; + goto cleanup; + } + + /* parse */ + LY_CHECK_GOTO(rc = lysp_stmt_parse(pctx, stmt, substmt->storage, NULL), cleanup); + break; + + case LY_STMT_STATUS: + /* single item */ + if ((*(uint16_t *)substmt->storage) & LYS_STATUS_MASK) { + LOGVAL(PARSER_CTX(pctx), LY_VCODE_DUPSTMT, stmt->stmt); + rc = LY_EVALID; + goto cleanup; + } + + /* parse */ + LY_CHECK_GOTO(rc = lysp_stmt_parse(pctx, stmt, substmt->storage, NULL), cleanup); + break; + + default: + LOGINT(PARSER_CTX(pctx)); + rc = LY_EINT; + goto cleanup; + } + +cleanup: + return rc; +} diff --git a/src/parser_data.h b/src/parser_data.h new file mode 100644 index 0000000..050ced3 --- /dev/null +++ b/src/parser_data.h @@ -0,0 +1,461 @@ +/** + * @file parser_data.h + * @author Radek Krejci + * @brief Data parsers for libyang + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef LY_PARSER_DATA_H_ +#define LY_PARSER_DATA_H_ + +#include "tree_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_in; + +/** + * @page howtoDataParsers Parsing Data + * + * Data parser allows to read instances from a specific format. libyang supports the following data formats: + * + * - XML + * + * Original data format used in NETCONF protocol. XML mapping is part of the YANG specification + * ([RFC 6020](http://tools.ietf.org/html/rfc6020)). + * + * - JSON + * + * The alternative data format available in RESTCONF protocol. Specification of JSON encoding of data modeled by YANG + * 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. + * + * 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 + * accepts two groups of options - @ref dataparseroptions and @ref datavalidationoptions. + * + * In contrast to the schema parser, data parser also accepts empty input data if such an empty data tree is valid + * according to the schemas in the libyang context (i.e. there are no top level mandatory nodes). + * + * There are individual functions to process different types of the data instances trees: + * - ::lyd_parse_data() is intended for standard configuration data trees. According to the given + * [parser options](@ref dataparseroptions), the caller can further specify which kind of data tree is expected: + * - *complete :running datastore*: this is the default case, possibly with the use of (some of) the + * ::LYD_PARSE_STRICT, ::LYD_PARSE_OPAQ or ::LYD_VALIDATE_PRESENT options. + * - *complete configuration-only datastore* (such as :startup): in this case it is necessary to except all state data + * using ::LYD_PARSE_NO_STATE option. + * - *incomplete datastore*: there are situation when the data tree is incomplete or invalid by specification. For + * example the *:operational* datastore is not necessarily valid and results of the NETCONF's \ or \ + * oprations used with filters will be incomplete (and thus invalid). This can be allowed using ::LYD_PARSE_ONLY, + * the ::LYD_PARSE_NO_STATE should be used for the data returned by \ operation. + * - ::lyd_parse_ext_data() is used for parsing configuration data trees defined inside extension instances, such as + * instances of yang-data extension specified in [RFC 8040](http://tools.ietf.org/html/rfc8040). + * - ::lyd_parse_op() is used for parsing RPCs/actions, replies, and notifications. Even NETCONF rpc, rpc-reply, and + * notification messages are supported. + * - ::lyd_parse_ext_op() is used for parsing RPCs/actions, replies, and notifications defined inside extension instances. + * + * Further information regarding the processing input instance data can be found on the following pages. + * - @subpage howtoDataValidation + * - @subpage howtoDataWD + * + * Functions List + * -------------- + * - ::lyd_parse_data() + * - ::lyd_parse_data_mem() + * - ::lyd_parse_data_fd() + * - ::lyd_parse_data_path() + * - ::lyd_parse_ext_data() + * - ::lyd_parse_op() + * - ::lyd_parse_ext_op() + */ + +/** + * @page howtoDataValidation Validating Data + * + * Data validation is performed implicitly to the input data processed by the [parser](@ref howtoDataParsers) and + * on demand via the lyd_validate_*() functions. The explicit validation process is supposed to be used when a (complex or + * simple) change is done on the data tree (via [data manipulation](@ref howtoDataManipulation) functions) and the data + * tree is expected to be valid (it doesn't make sense to validate modified result of filtered \ operation). + * + * Similarly to the [data parser](@ref howtoDataParsers), there are individual functions to validate standard data tree + * (::lyd_validate_all()) and RPC, Action and Notification (::lyd_validate_op()). For the standard data trees, it is possible + * to modify the validation process by @ref datavalidationoptions. This way the state data can be prohibited + * (::LYD_VALIDATE_NO_STATE) and checking for mandatory nodes can be limited to the YANG modules with already present data + * instances (::LYD_VALIDATE_PRESENT). Validation of the standard data tree can be also limited with ::lyd_validate_module() + * function, which scopes only to a specified single YANG module. + * + * Since the operation data trees (RPCs, Actions or Notifications) can reference (leafref, instance-identifier, when/must + * expressions) data from a datastore tree, ::lyd_validate_op() may require additional data tree to be provided. This is a + * difference in contrast to the parsing process, when the data are loaded from an external source and invalid reference + * outside the operation tree is acceptable. + * + * Functions List + * -------------- + * - ::lyd_validate_all() + * - ::lyd_validate_module() + * - ::lyd_validate_op() + */ + +/** + * @addtogroup datatree + * @{ + */ + +/** + * @ingroup datatree + * @defgroup dataparseroptions Data parser options + * + * Various options to change the data tree parsers behavior. + * + * Default parser behavior: + * - complete input file is always parsed. In case of XML, even not well-formed XML document (multiple top-level + * elements) is parsed in its entirety, + * - parser silently ignores data without matching schema node definition (for LYB format an error), + * - list instances are checked whether they have all the keys, error is raised if not. + * + * Default parser validation behavior: + * - the provided data are expected to provide complete datastore content (both the configuration and state data) + * and performs data validation according to all YANG rules, specifics follow, + * - list instances are expected to have all the keys (it is not checked), + * - instantiated (status) obsolete data print a warning, + * - all types are fully resolved (leafref/instance-identifier targets, unions) and must be valid (lists have + * all the keys, leaf(-lists) correct values), + * - when statements on existing nodes are evaluated, if not satisfied, a validation error is raised, + * - invalid multiple data instances/data from several cases cause a validation error, + * - implicit nodes (NP containers and default values) are added. + * @{ + */ +/* note: keep the lower 16bits free for use by LYD_VALIDATE_ flags. They are not supposed to be combined together, + * but since they are used (as a separate parameter) together in some functions, we want to keep them in a separated + * range to be able detect that the caller put wrong flags into the parser/validate options parameter. */ +#define LYD_PARSE_ONLY 0x010000 /**< Data will be only parsed and no validation will be performed. When statements + are kept unevaluated, union types may not be fully resolved, and + default values are not added (only the ones parsed are present). */ +#define LYD_PARSE_STRICT 0x020000 /**< Instead of silently ignoring data without schema definition raise an error. + Do not combine with ::LYD_PARSE_OPAQ (except for ::LYD_LYB). */ +#define LYD_PARSE_OPAQ 0x040000 /**< Instead of silently ignoring data without definition, parse them into + an opaq node. Do not combine with ::LYD_PARSE_STRICT (except for ::LYD_LYB). */ +#define LYD_PARSE_NO_STATE 0x080000 /**< Forbid state data in the parsed data. Usually used with ::LYD_VALIDATE_NO_STATE. */ + +#define LYD_PARSE_LYB_MOD_UPDATE 0x100000 /**< Only for LYB format, allow parsing data printed using a specific module + revision to be loaded even with a module with the same name but newer + revision. */ +#define LYD_PARSE_ORDERED 0x200000 /**< Do not search for the correct place of each node but instead expect + that the nodes are being parsed in the correct schema-based order, + which is always true if the data were printed by libyang and not + modified manually. If this flag is used incorrectly (for unordered data), + the behavior is undefined and most functions executed with these + data will not work correctly. */ +#define LYD_PARSE_SUBTREE 0x400000 /**< Parse only the current data subtree with any descendants, no siblings. + Also, a new return value ::LY_ENOT is returned if there is a sibling + subtree following in the input data. */ +#define LYD_PARSE_WHEN_TRUE 0x800000 /**< Mark all the parsed nodes dependend on a when condition with the flag + that means the condition was satisifed before. This allows for + auto-deletion of these nodes during validation. */ +#define LYD_PARSE_NO_NEW 0x1000000 /**< Do not set ::LYD_NEW (non-validated node flag) for any nodes. Use + when parsing validated data to skip some validation tasks and modify + some validation behavior (auto-deletion of cases). */ + +#define LYD_PARSE_OPTS_MASK 0xFFFF0000 /**< Mask for all the LYD_PARSE_ options. */ + +/** @} dataparseroptions */ + +/** + * @ingroup datatree + * @defgroup datavalidationoptions Data validation options + * + * Various options to change data validation behaviour, both for the parser and separate validation. + * + * Default separate validation behavior: + * - the provided data are expected to provide complete datastore content (both the configuration and state data) + * and performs data validation according to all YANG rules, specifics follow, + * - instantiated (status) obsolete data print a warning, + * - all types are fully resolved (leafref/instance-identifier targets, unions) and must be valid (lists have + * all the keys, leaf(-lists) correct values), + * - when statements on existing nodes are evaluated. Depending on the previous when state (from previous validation + * or parsing), the node is silently auto-deleted if the state changed from true to false, otherwise a validation error + * is raised if it evaluates to false, + * - if-feature statements are evaluated, + * - data from several cases behave based on their previous state (from previous validation or parsing). If there existed + * already a case and another one was added, the previous one is silently auto-deleted. Otherwise (if data from 2 or + * more cases were created) a validation error is raised, + * - default values are added. + * + * @{ + */ +#define LYD_VALIDATE_NO_STATE 0x0001 /**< Consider state data not allowed and raise an error if they are found. + Also, no implicit state data are added. */ +#define LYD_VALIDATE_PRESENT 0x0002 /**< Validate only modules whose data actually exist. */ + +#define LYD_VALIDATE_OPTS_MASK 0x0000FFFF /**< Mask for all the LYD_VALIDATE_* options. */ + +/** @} datavalidationoptions */ + +/** + * @brief Parse (and validate) data from the input handler as a YANG data tree. + * + * @param[in] ctx Context to connect with the tree being built here. + * @param[in] parent Optional parent to connect the parsed nodes to. + * @param[in] in The input handle to provide the dumped data in the specified @p format to parse (and validate). + * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler. + * @param[in] parse_options Options for parser, see @ref dataparseroptions. + * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions. + * @param[out] tree Full parsed data tree, note that NULL can be a valid tree. If @p parent is set, set to NULL. + * @return LY_SUCCESS in case of successful parsing (and validation). + * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, 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. + * + * @param[in] ctx Context to connect with the tree being built here. + * @param[in] data The input data in the specified @p format to parse (and validate). + * @param[in] format Format of the input data to be parsed. + * @param[in] parse_options Options for parser, see @ref dataparseroptions. + * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions. + * @param[out] tree Full parsed data tree, note that NULL can be a valid tree + * @return LY_SUCCESS in case of successful parsing (and validation). + * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + */ +LIBYANG_API_DECL 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); + +/** + * @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. + * + * @param[in] ctx Context to connect with the tree being built here. + * @param[in] fd File descriptor of a regular file (e.g. sockets are not supported) containing the input data in the + * specified @p format to parse. + * @param[in] format Format of the input data to be parsed. + * @param[in] parse_options Options for parser, see @ref dataparseroptions. + * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions. + * @param[out] tree Full parsed data tree, note that NULL can be a valid tree + * @return LY_SUCCESS in case of successful parsing (and validation). + * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_data_fd(const struct ly_ctx *ctx, int fd, 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. + * + * @param[in] ctx Context to connect with the tree being built here. + * @param[in] path Path to the file with the input data in the specified @p format to parse (and validate). + * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from @p path extension. + * @param[in] parse_options Options for parser, see @ref dataparseroptions. + * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions. + * @param[out] tree Full parsed data tree, note that NULL can be a valid tree + * @return LY_SUCCESS in case of successful parsing (and validation). + * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_data_path(const struct ly_ctx *ctx, const char *path, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree); + +/** + * @brief Parse (and validate) data from the input handler as an extension data tree following the schema tree of the given + * extension instance. + * + * Note that the data being parsed are limited only to the schema tree specified by the given extension, it does not allow + * to mix them with the standard data from any module. + * + * Directly applicable to data defined as [yang-data](@ref howtoDataYangdata). + * + * @param[in] ext Extension instance providing the specific schema tree to match with the data being parsed. + * @param[in] parent Optional parent to connect the parsed nodes to. + * @param[in] in The input handle to provide the dumped data in the specified @p format to parse (and validate). + * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler. + * @param[in] parse_options Options for parser, see @ref dataparseroptions. + * @param[in] validate_options Options for the validation phase, see @ref datavalidationoptions. + * @param[out] tree Full parsed data tree, note that NULL can be a valid tree. If @p parent is set, set to NULL. + * @return LY_SUCCESS in case of successful parsing (and validation). + * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_ext_data(const struct lysc_ext_instance *ext, struct lyd_node *parent, struct ly_in *in, + LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree); + +/** + * @ingroup datatree + * @defgroup datatype Data operation type + * + * Operation provided to ::lyd_validate_op() to validate. + * + * The operation cannot be determined automatically since RPC/action and a reply to it share the common top level node + * referencing the RPC/action schema node and may not have any input/output children to use for distinction. + * + * @{ + */ +enum lyd_type { + LYD_TYPE_DATA_YANG = 0, /* generic YANG instance data */ + LYD_TYPE_RPC_YANG, /* instance of a YANG RPC/action request with only "input" data children, + including all parents in case of an action */ + LYD_TYPE_NOTIF_YANG, /* instance of a YANG notification, including all parents in case of a nested one */ + LYD_TYPE_REPLY_YANG, /* instance of a YANG RPC/action reply with only "output" data children, + including all parents in case of an action */ + + LYD_TYPE_RPC_NETCONF, /* complete NETCONF RPC invocation as defined for + [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and + [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */ + LYD_TYPE_NOTIF_NETCONF, /* complete NETCONF notification message as defined for + [notification](https://tools.ietf.org/html/rfc7950#section-7.16.2) */ + LYD_TYPE_REPLY_NETCONF /* complete NETCONF RPC reply as defined for + [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and + [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */ +}; +/** @} datatype */ + +/** + * @brief Parse YANG data into an operation data tree. + * + * At least one of @p parent, @p tree, or @p op must always be set. + * + * Specific @p data_type values have different parameter meaning as follows: + * - ::LYD_TYPE_RPC_NETCONF: + * - @p parent - must be NULL, the whole RPC is expected; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the RPC/action data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_NOTIF_NETCONF: + * - @p parent - must be NULL, the whole notification is expected; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the notification data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_REPLY_NETCONF: + * - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be NULL, the reply is appended to the RPC; + * Note that there are 3 kinds of NETCONF replies - ok, error, and data. Only data reply appends any nodes to the RPC. + * + * @param[in] ctx libyang context. + * @param[in] parent Optional parent to connect the parsed nodes to. + * @param[in] in Input handle to read the input from. + * @param[in] format Expected format of the data in @p in. + * @param[in] data_type Expected operation to parse (@ref datatype). + * @param[out] tree Optional full parsed data tree. If @p parent is set, set to NULL. + * @param[out] op Optional pointer to the operation (action/RPC) node. + * @return LY_ERR value. + * @return LY_ENOT if @p data_type is a NETCONF message and the root XML element is not the expected one. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, + enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op); + +/** + * @brief Parse extension data into an operation data tree following only the specification from the given extension instance. + * + * Directly applicable to data defined as [yang-data](@ref howtoDataYangdata). + * + * At least one of @p parent, @p tree, or @p op must always be set. + * + * Specific @p data_type values have different parameter meaning as follows: + * - ::LYD_TYPE_RPC_NETCONF: + * - @p parent - must be NULL, the whole RPC is expected; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the RPC/action data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_NOTIF_NETCONF: + * - @p parent - must be NULL, the whole notification is expected; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the notification data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_REPLY_NETCONF: + * - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be NULL, the reply is appended to the RPC; + * Note that there are 3 kinds of NETCONF replies - ok, error, and data. Only data reply appends any nodes to the RPC. + * + * @param[in] ext Extension instance providing the specific schema tree to match with the data being parsed. + * @param[in] parent Optional parent to connect the parsed nodes to. + * @param[in] in Input handle to read the input from. + * @param[in] format Expected format of the data in @p in. + * @param[in] data_type Expected operation to parse (@ref datatype). + * @param[out] tree Optional full parsed data tree. If @p parent is set, set to NULL. + * @param[out] op Optional pointer to the operation (action/RPC) node. + * @return LY_ERR value. + * @return LY_ENOT if @p data_type is a NETCONF message and the root XML element is not the expected one. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_ext_op(const struct lysc_ext_instance *ext, struct lyd_node *parent, struct ly_in *in, + LYD_FORMAT format, enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op); + +/** + * @brief Fully validate a data tree. + * + * The data tree is modified in-place. As a result of the validation, some data might be removed + * from the tree. In that case, the removed items are freed, not just unlinked. + * + * @param[in,out] tree Data tree to recursively validate. May be changed by validation, might become NULL. + * @param[in] ctx libyang context. Can be NULL if @p tree is set. + * @param[in] val_opts Validation options (@ref datavalidationoptions). + * @param[out] diff Optional diff with any changes made by the validation. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_validate_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t val_opts, + struct lyd_node **diff); + +/** + * @brief Fully validate a data tree of a module. + * + * The data tree is modified in-place. As a result of the validation, some data might be removed + * from the tree. In that case, the removed items are freed, not just unlinked. + * + * @param[in,out] tree Data tree to recursively validate. May be changed by validation, might become NULL. + * @param[in] module Module whose data (and schema restrictions) to validate. + * @param[in] val_opts Validation options (@ref datavalidationoptions). + * @param[out] diff Optional diff with any changes made by the validation. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_validate_module(struct lyd_node **tree, const struct lys_module *module, uint32_t val_opts, + struct lyd_node **diff); + +/** + * @brief Validate an RPC/action request, reply, or notification. Only the operation data tree (input/output/notif) + * is validate, any parents are ignored. + * + * @param[in,out] op_tree Operation tree with any parents. It can point to the operation itself or any of + * its parents, only the operation subtree is actually validated. + * @param[in] dep_tree Tree to be used for validating references from the operation subtree. + * @param[in] data_type Operation type to validate (only YANG operations are accepted, @ref datatype). + * @param[out] diff Optional diff with any changes made by the validation. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *dep_tree, enum lyd_type data_type, + struct lyd_node **diff); + +/** @} datatree */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PARSER_DATA_H_ */ diff --git a/src/parser_internal.h b/src/parser_internal.h new file mode 100644 index 0000000..458d297 --- /dev/null +++ b/src/parser_internal.h @@ -0,0 +1,392 @@ +/** + * @file parser_internal.h + * @author Radek Krejci + * @brief Internal structures and functions for libyang parsers + * + * Copyright (c) 2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef LY_PARSER_INTERNAL_H_ +#define LY_PARSER_INTERNAL_H_ + +#include "parser_data.h" +#include "set.h" + +struct lyd_ctx; +struct ly_in; +struct lysp_ext_substmt; +struct lysp_stmt; +struct lysp_yang_ctx; +struct lysp_yin_ctx; +struct lysp_ctx; + +/** + * @brief Callback for ::lyd_ctx to free the structure + * + * @param[in] ctx Data parser context to free. + */ +typedef void (*lyd_ctx_free_clb)(struct lyd_ctx *ctx); + +/** + * @brief Internal data parser flags. + */ +#define LYD_INTOPT_RPC 0x01 /**< RPC request is being parsed. */ +#define LYD_INTOPT_ACTION 0x02 /**< Action request is being parsed. */ +#define LYD_INTOPT_REPLY 0x04 /**< RPC/action reply is being parsed. */ +#define LYD_INTOPT_NOTIF 0x08 /**< Notification is being parsed. */ +#define LYD_INTOPT_ANY 0x10 /**< Anydata/anyxml content is being parsed, there can be anything. */ +#define LYD_INTOPT_WITH_SIBLINGS 0x20 /**< Parse the whole input with any siblings. */ +#define LYD_INTOPT_NO_SIBLINGS 0x40 /**< If there are any siblings, return an error. */ + +/** + * @brief Internal (common) context for YANG data parsers. + * + * Covers ::lyd_xml_ctx, ::lyd_json_ctx and ::lyd_lyb_ctx. + */ +struct lyd_ctx { + const struct lysc_ext_instance *ext; /**< extension instance possibly changing document root context of the data being parsed */ + 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 */ + +#define LYD_PARSER_BUFSIZE 4078 + 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 validated with LY_EINCOMPLETE result */ + struct ly_set meta_types; /**< set of metadata validated with LY_EINCOMPLETE result */ + struct ly_set ext_node; /**< set of nodes with extension instances to validate */ + struct ly_set ext_val; /**< set of nested subtrees parsed by extensions to validate */ + struct lyd_node *op_node; /**< if an RPC/action/notification is being parsed, store the pointer to it */ + + /* callbacks */ + lyd_ctx_free_clb free; /**< destructor */ + + struct { + const struct ly_ctx *ctx; /**< libyang context */ + uint64_t line; /**< current line */ + struct ly_in *in; /**< input structure */ + } *data_ctx; /**< generic pointer supposed to map to and access (common part of) XML/JSON/... parser contexts */ +}; + +/** + * @brief Internal context for XML data parser. + */ +struct lyd_xml_ctx { + const struct lysc_ext_instance *ext; + uint32_t parse_opts; + uint32_t val_opts; + uint32_t int_opts; + uint32_t path_len; + char path[LYD_PARSER_BUFSIZE]; + struct ly_set node_when; + struct ly_set node_types; + struct ly_set meta_types; + struct ly_set ext_node; + struct ly_set ext_val; + struct lyd_node *op_node; + + /* callbacks */ + lyd_ctx_free_clb free; + + struct lyxml_ctx *xmlctx; /**< XML context */ +}; + +/** + * @brief Internal context for JSON data parser. + */ +struct lyd_json_ctx { + const struct lysc_ext_instance *ext; + uint32_t parse_opts; + uint32_t val_opts; + uint32_t int_opts; + uint32_t path_len; + char path[LYD_PARSER_BUFSIZE]; + struct ly_set node_when; + struct ly_set node_types; + struct ly_set meta_types; + struct ly_set ext_node; + struct ly_set ext_val; + struct lyd_node *op_node; + + /* callbacks */ + lyd_ctx_free_clb free; + + struct lyjson_ctx *jsonctx; /**< JSON context */ +}; + +/** + * @brief Internal context for LYB data parser/printer. + */ +struct lyd_lyb_ctx { + const struct lysc_ext_instance *ext; + + union { + struct { + uint32_t parse_opts; + uint32_t val_opts; + }; + uint32_t print_options; + }; + uint32_t int_opts; + uint32_t path_len; + char path[LYD_PARSER_BUFSIZE]; + struct ly_set node_when; + struct ly_set node_types; + struct ly_set meta_types; + struct ly_set ext_node; + struct ly_set ext_val; + struct lyd_node *op_node; + + /* callbacks */ + lyd_ctx_free_clb free; + + struct lylyb_ctx *lybctx; /* LYB context */ +}; + +/** + * @brief Parsed extension instance data to validate. + */ +struct lyd_ctx_ext_val { + struct lysc_ext_instance *ext; + struct lyd_node *sibling; +}; + +/** + * @brief Parsed data node with extension instance to validate. + */ +struct lyd_ctx_ext_node { + struct lysc_ext_instance *ext; + struct lyd_node *node; +}; + +/** + * @brief Common part to supplement the specific ::lyd_ctx_free_clb callbacks. + */ +void lyd_ctx_free(struct lyd_ctx *ctx); + +/** + * @brief Parse submodule from YANG data. + * @param[in,out] context Parser context. + * @param[in] ly_ctx Context of YANG schemas. + * @param[in] main_ctx Parser context of main module. + * @param[in] in Input structure. + * @param[out] submod Pointer to the parsed submodule structure. + * @return LY_ERR value - LY_SUCCESS, LY_EINVAL or LY_EVALID. + */ +LY_ERR yang_parse_submodule(struct lysp_yang_ctx **context, struct ly_ctx *ly_ctx, struct lysp_ctx *main_ctx, + struct ly_in *in, struct lysp_submodule **submod); + +/** + * @brief Parse module from YANG data. + * @param[in] context Parser context. + * @param[in] in Input structure. + * @param[in,out] mod Prepared module structure where the parsed information, including the parsed + * module structure, will be filled in. + * @return LY_ERR values. + */ +LY_ERR yang_parse_module(struct lysp_yang_ctx **context, struct ly_in *in, struct lys_module *mod); + +/** + * @brief Parse module from YIN data. + * + * @param[in,out] yin_ctx Context created during parsing, is used to finalize lysp_model after it's completly parsed. + * @param[in] in Input structure. + * @param[in,out] mod Prepared module structure where the parsed information, including the parsed + * module structure, will be filled in. + * @return LY_ERR values. + */ +LY_ERR yin_parse_module(struct lysp_yin_ctx **yin_ctx, struct ly_in *in, struct lys_module *mod); + +/** + * @brief Parse submodule from YIN data. + * + * @param[in,out] yin_ctx Context created during parsing, is used to finalize lysp_model after it's completly parsed. + * @param[in] ctx Libyang context. + * @param[in] main_ctx Parser context of main module. + * @param[in] in Input structure. + * @param[in,out] submod Submodule structure where the parsed information, will be filled in. + * @return LY_ERR values. + */ +LY_ERR yin_parse_submodule(struct lysp_yin_ctx **yin_ctx, struct ly_ctx *ctx, struct lysp_ctx *main_ctx, + struct ly_in *in, struct lysp_submodule **submod); + +/** + * @brief Parse XML string as a YANG data tree. + * + * @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. + * @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_xml(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, + struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p); + +/** + * @brief Parse XML string as a NETCONF message. + * + * @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. + * @param[in] parse_opts Options for parser, see @ref dataparseroptions. + * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. + * @param[in] data_type Expected NETCONF data type of the data. + * @param[out] envp Individual parsed envelopes tree, may be returned possibly even on an error. + * @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_xml_netconf(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, enum lyd_type data_type, + struct lyd_node **envp, struct ly_set *parsed, struct lyd_ctx **lydctx_p); + +/** + * @brief Parse JSON string as a YANG data tree. + * + * @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. + * @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_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, + struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p); + +/** + * @brief Parse binary LYB data as a YANG data tree. + * + * @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. + * @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_lyb(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, + struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p); + +/** + * @brief Search all the parents for an operation node, check validity based on internal parser flags. + * + * @param[in] parent Parent to connect the parsed nodes to. + * @param[in] int_opts Internal parser options. + * @param[out] op Found operation, if any. + * @return LY_ERR value. + */ +LY_ERR lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, struct lyd_node **op); + +/** + * @brief Check that a data node representing the @p snode is suitable based on options. + * + * @param[in] lydctx Common data parsers context. + * @param[in] snode Schema node to check. + * @return LY_SUCCESS or LY_EVALID + */ +LY_ERR lyd_parser_check_schema(struct lyd_ctx *lydctx, const struct lysc_node *snode); + +/** + * @brief Wrapper around ::lyd_create_term() for data parsers. + * + * @param[in] lydctx Data parser context. + * @param[in] schema Schema node of the new data node. + * @param[in] value String value to be parsed. + * @param[in] value_len Length of @p value, must be set correctly. + * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. + * @param[in] format Input format of @p value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] hints [Data parser's hints](@ref lydvalhints) for the value's type. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_EINCOMPLETE in case data tree is needed to finish the validation. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_parser_create_term(struct lyd_ctx *lydctx, const struct lysc_node *schema, const void *value, size_t value_len, + ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, struct lyd_node **node); + +/** + * @brief Wrapper around ::lyd_create_meta() for data parsers. + * + * @param[in] lydctx Data parser context. + * @param[in] parent Parent of the created meta. Must be set if @p meta is NULL. + * @param[in,out] meta First existing meta to connect to or empty pointer to set. Must be set if @p parent is NULL. + * @param[in] mod Module of the created metadata. + * @param[in] name Metadata name. + * @param[in] name_len Length of @p name. + * @param[in] value Metadata value. + * @param[in] value_len Length of @p value. + * @param[in,out] dynamic Whether the @p value is dynamically allocated, is adjusted once the value is assigned. + * @param[in] format Prefix format. + * @param[in] prefix_data Prefix format data (see ::ly_resolve_prefix()). + * @param[in] hints [Value hint](@ref lydvalhints) from the parser regarding the value type. + * @param[in] ctx_node Value context node. + * @return LY_ERR value. + */ +LY_ERR lyd_parser_create_meta(struct lyd_ctx *lydctx, struct lyd_node *parent, struct lyd_meta **meta, + const struct lys_module *mod, const char *name, size_t name_len, const void *value, size_t value_len, + ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node); + +/** + * @brief Check that a list has all its keys. + * + * @param[in] node List to check. + * @return LY_SUCCESS on success. + * @return LY_ENOT on a missing key. + */ +LY_ERR lyd_parse_check_keys(struct lyd_node *node); + +/** + * @brief Set data flags for a newly parsed node. + * + * @param[in] node Node to use. + * @param[in,out] meta Node metadata, may be removed from. + * @param[in] lydctx Data parsing context. + * @param[in] ext Extension instance if @p node was parsed for one. + * @return LY_ERR value. + */ +LY_ERR lyd_parse_set_data_flags(struct lyd_node *node, struct lyd_meta **meta, struct lyd_ctx *lydctx, + struct lysc_ext_instance *ext); + +/** + * @brief Parse an instance extension statement. + * + * @param[in] pctx Parse context. + * @param[in] substmt Parsed ext instance substatement info. + * @param[in] stmt Parsed generic statement to process. + * @return LY_ERR value. + */ +LY_ERR lys_parse_ext_instance_stmt(struct lysp_ctx *pctx, struct lysp_ext_substmt *substmt, struct lysp_stmt *stmt); + +#endif /* LY_PARSER_INTERNAL_H_ */ diff --git a/src/parser_json.c b/src/parser_json.c new file mode 100644 index 0000000..6219c6e --- /dev/null +++ b/src/parser_json.c @@ -0,0 +1,1819 @@ +/** + * @file parser_json.c + * @author Radek Krejci + * @author Michal Vasko + * @brief JSON data parser for libyang + * + * Copyright (c) 2020 - 2022 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 + +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "in_internal.h" +#include "json.h" +#include "log.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 "tree_schema_internal.h" +#include "validation.h" + +/** + * @brief Free the JSON data parser context. + * + * JSON implementation of lyd_ctx_free_clb(). + */ +static void +lyd_json_ctx_free(struct lyd_ctx *lydctx) +{ + struct lyd_json_ctx *ctx = (struct lyd_json_ctx *)lydctx; + + if (lydctx) { + lyd_ctx_free(lydctx); + lyjson_ctx_free(ctx->jsonctx); + free(ctx); + } +} + +/** + * @brief Pass the responsibility for releasing the dynamic values to @p dst. + * + * @param[in] jsonctx JSON context which contains the dynamic value. + * @param[in,out] dst Pointer to which the responsibility will be submited. + * If the pointer is already pointing to some allocated memory, it is released beforehand. + */ +static void +lyjson_ctx_give_dynamic_value(struct lyjson_ctx *jsonctx, char **dst) +{ + assert(jsonctx && dst); + + if (!jsonctx->dynamic) { + return; + } + + if (dst) { + free(*dst); + } + *dst = NULL; + + /* give the dynamic value */ + *dst = (char *)jsonctx->value; + + /* responsibility for the release is now passed to dst */ + jsonctx->dynamic = 0; +} + +/** + * @brief Parse JSON 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 parameter are mandatory. Function only parse the member-name, all the appropriate checks are up to the caller. + * + * @param[in] value String to parse + * @param[in] value_len Length of the @p str. + * @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 +lydjson_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, Note that the result can be NULL in case of no local prefix + * and no context @p node to get inherited prefix. + * @param[out] prefix_len_p Pointer to the length of the resulting @p prefix_p string. Note that the result can be 0 in case + * of no local prefix and no context @p node to get inherited prefix. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_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; + } + + *prefix_p = NULL; + while (node) { + if (node->schema) { + *prefix_p = node->schema->module->name; + break; + } + onode = (struct lyd_node_opaq *)node; + if (onode->name.module_name) { + *prefix_p = onode->name.module_name; + break; + } else if (onode->name.prefix) { + *prefix_p = onode->name.prefix; + break; + } + node = lyd_parent(node); + } + *prefix_len_p = ly_strlen(module_name); + + return LY_SUCCESS; +} + +/** + * @brief Skip the current JSON object/array. + * + * @param[in] jsonctx JSON context with the input data to skip. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_data_skip(struct lyjson_ctx *jsonctx) +{ + enum LYJSON_PARSER_STATUS status, current; + uint32_t orig_depth; + + status = lyjson_ctx_status(jsonctx, 0); + assert((status == LYJSON_OBJECT) || (status == LYJSON_ARRAY)); + orig_depth = jsonctx->depth; + + /* next */ + LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); + + if ((status == LYJSON_OBJECT) && (current != LYJSON_OBJECT) && (current != LYJSON_ARRAY)) { + /* no nested objects */ + LY_CHECK_RET(lyjson_ctx_next(jsonctx, NULL)); + return LY_SUCCESS; + } + + /* skip after the content */ + while ((jsonctx->depth > orig_depth) || (current != status + 1)) { + if (current == LYJSON_ARRAY) { + /* skip the array separately */ + LY_CHECK_RET(lydjson_data_skip(jsonctx)); + current = lyjson_ctx_status(jsonctx, 0); + } else { + LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); + } + + if (current == LYJSON_END) { + break; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Get schema node corresponding to the input parameters. + * + * @param[in] lydctx JSON data parser context. + * @param[in] is_attr Flag if the reference to the node is an attribute, for logging only. + * @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, can be NULL in case of top-level. + * @param[out] snode Found schema node corresponding to the input parameters. If NULL, parse as an opaque node. + * @param[out] ext Extension instance that provided @p snode, if any. + * @return LY_SUCCES on success. + * @return LY_ENOT if the whole object was parsed (skipped or as an extension). + * @return LY_ERR on error. + */ +static LY_ERR +lydjson_get_snode(struct lyd_json_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->jsonctx->ctx, prefix, prefix_len); + } else if (parent) { + if (parent->schema) { + mod = parent->schema->module; + } + } else if (!(lydctx->int_opts & LYD_INTOPT_ANY)) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Top-level JSON 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_JSON, NULL, name, name_len, snode, ext); + if (r != LY_ENOT) { + /* success or error */ + ret = r; + goto cleanup; + } + + /* unknown module */ + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "No module named \"%.*s\" in the context.", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + /* skip element with children */ + ret = lydjson_data_skip(lydctx->jsonctx); + LY_CHECK_GOTO(ret, cleanup); + + ret = LY_ENOT; + 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(parent ? parent->schema : NULL, 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_JSON, NULL, name, name_len, snode, ext); + if (r != LY_ENOT) { + /* success or error */ + ret = r; + goto cleanup; + } + + /* unknown data node */ + 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.", + (int)name_len, name, LYD_NAME(parent)); + } else if (lydctx->ext) { + if (lydctx->ext->argument) { + LOGVAL(lydctx->jsonctx->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->jsonctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the %s extension instance.", + (int)name_len, name, lydctx->ext->def->name); + } + } else { + LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.", + (int)name_len, name, mod->name); + } + ret = LY_EVALID; + goto cleanup; + } else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + /* skip element with children */ + ret = lydjson_data_skip(lydctx->jsonctx); + LY_CHECK_GOTO(ret, cleanup); + + ret = LY_ENOT; + 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 that the input data are parseable as the @p list. + * + * Checks for all the list's keys. Function does not revert the context state. + * + * @param[in] jsonctx JSON parser context. + * @param[in] list List schema node corresponding to the input data object. + * @return LY_SUCCESS in case the data are ok for the @p list + * @return LY_ENOT in case the input data are not sufficient to fully parse the list instance. + */ +static LY_ERR +lydjson_check_list(struct lyjson_ctx *jsonctx, const struct lysc_node *list) +{ + LY_ERR ret = LY_SUCCESS; + enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(jsonctx, 0); + struct ly_set key_set = {0}; + const struct lysc_node *snode; + uint32_t i, status_count; + + assert(list && (list->nodetype == LYS_LIST)); + + /* get all keys into a set (keys do not have if-features or anything) */ + snode = NULL; + while ((snode = lys_getnext(snode, list, NULL, 0)) && (snode->flags & LYS_KEY)) { + ret = ly_set_add(&key_set, (void *)snode, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + if (status == LYJSON_OBJECT) { + status_count = jsonctx->status.count; + + while (key_set.count && (status != LYJSON_OBJECT_CLOSED)) { + const char *name, *prefix; + size_t name_len, prefix_len; + ly_bool is_attr; + + /* match the key */ + snode = NULL; + lydjson_parse_name(jsonctx->value, jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr); + + if (!is_attr && !prefix) { + for (i = 0; i < key_set.count; ++i) { + snode = (const struct lysc_node *)key_set.objs[i]; + if (!ly_strncmp(snode->name, name, name_len)) { + break; + } + } + /* go into the item to a) process it as a key or b) start skipping it as another list child */ + ret = lyjson_ctx_next(jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + + if (snode) { + /* we have the key, validate the value */ + if (status < LYJSON_NUMBER) { + /* not a terminal */ + ret = LY_ENOT; + goto cleanup; + } + + ret = lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* key with a valid value, remove from the set */ + ly_set_rm_index(&key_set, i, NULL); + } + } else { + /* start skipping the member we are not interested in */ + ret = lyjson_ctx_next(jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + } + /* move to the next child */ + while (status_count < jsonctx->status.count) { + ret = lyjson_ctx_next(jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + } + } + } + + if (key_set.count) { + /* some keys are missing/did not validate */ + ret = LY_ENOT; + } + +cleanup: + ly_set_erase(&key_set, NULL); + return ret; +} + +/** + * @brief Get the hint for the data type parsers according to the current JSON parser context. + * + * @param[in] lydctx JSON data parser context. The context is supposed to be on a value. + * @param[in,out] status Pointer to the current context status, + * in some circumstances the function manipulates with the context so the status is updated. + * @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 context status not referring to a value. + */ +static LY_ERR +lydjson_value_type_hint(struct lyd_json_ctx *lydctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p) +{ + *type_hint_p = 0; + + if (*status_p == LYJSON_ARRAY) { + /* only [null] */ + LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_p)); + if (*status_p != LYJSON_NULL) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, + "Expected JSON name/value or special name/[null], but input data contains name/[%s].", + lyjson_token2str(*status_p)); + return LY_EINVAL; + } + + LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, NULL)); + if (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_ARRAY_CLOSED) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.", + lyjson_token2str(*status_p)); + return LY_EINVAL; + } + + *type_hint_p = LYD_VALHINT_EMPTY; + } else if (*status_p == LYJSON_STRING) { + *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64; + } else if (*status_p == LYJSON_NUMBER) { + *type_hint_p = LYD_VALHINT_DECNUM; + } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) { + *type_hint_p = LYD_VALHINT_BOOLEAN; + } else if (*status_p == LYJSON_NULL) { + *type_hint_p = 0; + } else { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p)); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @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 JSON data parser context. When the function returns, the context is in the same state + * as before calling, despite it is necessary to process input data for checking. + * @param[in] snode Schema node corresponding to the member currently being processed in the context. + * @param[out] type_hint_p Pointer to a variable to store detected value type hint in case of leaf or leaf-list. + * @return LY_SUCCESS in case the data are ok for the @p snode or the LYD_PARSE_OPAQ is not enabled. + * @return LY_ENOT in case the input data are not sufficient to fully parse the list instance + * @return LY_EINVAL in case of invalid leaf JSON encoding + * and they are expected to be parsed as opaq nodes. + */ +static LY_ERR +lydjson_data_check_opaq(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, uint32_t *type_hint_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyjson_ctx *jsonctx = lydctx->jsonctx; + enum LYJSON_PARSER_STATUS status; + + assert(snode); + + if (!(snode->nodetype & (LYD_NODE_TERM | LYS_LIST))) { + /* can always be parsed as a data node if we have the schema node */ + return LY_SUCCESS; + } + + /* backup parser */ + lyjson_ctx_backup(jsonctx); + status = lyjson_ctx_status(jsonctx, 0); + + if (lydctx->parse_opts & LYD_PARSE_OPAQ) { + /* check if the node is parseable. if not, NULL the snode to announce that it is supposed to be parsed + * as an opaq node */ + switch (snode->nodetype) { + case LYS_LEAFLIST: + case LYS_LEAF: + /* value may not be valid in which case we parse it as an opaque node */ + ret = lydjson_value_type_hint(lydctx, &status, type_hint_p); + if (ret) { + break; + } + + if (lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL)) { + ret = LY_ENOT; + } + break; + case LYS_LIST: + /* lists may not have all its keys */ + if (lydjson_check_list(jsonctx, snode)) { + /* invalid list, parse as opaque if it misses/has invalid some keys */ + ret = LY_ENOT; + } + break; + } + } else if (snode->nodetype & LYD_NODE_TERM) { + status = lyjson_ctx_status(jsonctx, 0); + ret = lydjson_value_type_hint(lydctx, &status, type_hint_p); + } + + /* restore parser */ + lyjson_ctx_restore(jsonctx); + + return ret; +} + +/** + * @brief Join the forward-referencing metadata with their target data nodes. + * + * Note that JSON encoding for YANG data allows forward-referencing metadata only for leafs/leaf-lists. + * + * @param[in] lydctx JSON data parser context. + * @param[in,out] first_p Pointer to the first sibling node variable (top-level or in a particular parent node) + * as a starting point to search for the metadata's target data node + * @return LY_SUCCESS on success + * @return LY_EVALID in case there are some metadata with unresolved target data node instance + */ +static LY_ERR +lydjson_metadata_finish(struct lyd_json_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] != '@')) { + /* not an opaq metadata node */ + continue; + } + + LOG_LOCSET(NULL, attr, NULL, NULL); + log_location_items++; + + if (prev != meta_container->name.name) { + /* metas' names are stored in dictionary, so checking pointers must works */ + lydict_remove(lydctx->jsonctx->ctx, prev); + LY_CHECK_GOTO(ret = lydict_insert(lydctx->jsonctx->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 - we are going to put into it just a generic attribute. */ + 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->jsonctx->ctx, LYVE_SYNTAX, "Metadata container references a sibling list node %s.", + ((struct lyd_node_opaq *)node)->name.name); + ret = LY_EVALID; + goto cleanup; + } + + /* match */ + match++; + if (match != instance) { + continue; + } + + LY_LIST_FOR(meta_container->child, meta_iter) { + /* convert opaq node to a attribute of the opaq node */ + struct lyd_node_opaq *meta = (struct lyd_node_opaq *)meta_iter; + + ret = lyd_create_attr(node, NULL, lydctx->jsonctx->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_JSON, + NULL, meta->hints); + LY_CHECK_GOTO(ret, cleanup); + } + + /* done */ + break; + } else { + /* this is the second time we are resolving the schema node, so it must succeed, + * but remember that snode can be still NULL */ + lydjson_parse_name(meta_container->name.name, strlen(meta_container->name.name), &name, &name_len, + &prefix, &prefix_len, &is_attr); + assert(is_attr); + lydjson_get_snode(lydctx, is_attr, prefix, prefix_len, name, name_len, lyd_parent(*first_p), &snode, &ext); + + if (snode != node->schema) { + continue; + } + + /* match */ + match++; + if (match != instance) { + continue; + } + + LY_LIST_FOR(meta_container->child, meta_iter) { + /* convert opaq node to a metadata of the node */ + struct lyd_node_opaq *meta = (struct lyd_node_opaq *)meta_iter; + struct lys_module *mod = NULL; + + mod = ly_ctx_get_module_implemented(lydctx->jsonctx->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_JSON, NULL, meta->hints, node->schema); + LY_CHECK_GOTO(ret, cleanup); + } else if (lydctx->parse_opts & LYD_PARSE_STRICT) { + if (meta->name.prefix) { + LOGVAL(lydctx->jsonctx->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->jsonctx->ctx, LYVE_REFERENCE, "Missing YANG module of metadata \"%s\".", + meta->name.name); + } + ret = LY_EVALID; + goto cleanup; + } + } + + /* add/correct flags */ + ret = lyd_parse_set_data_flags(node, &node->meta, (struct lyd_ctx *)lydctx, ext); + LY_CHECK_GOTO(ret, cleanup); + break; + } + } + + if (match != instance) { + /* there is no corresponding data node for the metadata */ + if (instance > 1) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, + "Missing JSON data instance #%" PRIu64 " to be coupled with %s metadata.", + instance, meta_container->name.name); + } else { + LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "Missing JSON data instance to be coupled with %s metadata.", + meta_container->name.name); + } + ret = LY_EVALID; + } else { + /* remove the opaq attr */ + if (attr == (*first_p)) { + *first_p = attr->next; + } + lyd_free_tree(attr); + } + + LOG_LOCBACK(0, log_location_items, 0, 0); + log_location_items = 0; + } + +cleanup: + lydict_remove(lydctx->jsonctx->ctx, prev); + + LOG_LOCBACK(0, log_location_items, 0, 0); + return ret; +} + +/** + * @brief Parse a metadata member. + * + * @param[in] lydctx JSON data parser context. + * @param[in] snode Schema node of the metadata parent. + * @param[in] node Parent node in case the metadata is not forward-referencing (only LYD_NODE_TERM) + * so the data node does not exists. In such a case the metadata is stored in the context for the later + * processing by lydjson_metadata_finish(). + * @return LY_SUCCESS on success + * @return Various LY_ERR values in case of failure. + */ +static LY_ERR +lydjson_metadata(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lyd_node *node) +{ + LY_ERR ret = LY_SUCCESS; + enum LYJSON_PARSER_STATUS status; + const char *expected; + ly_bool in_parent = 0; + const char *name, *prefix = NULL; + char *dynamic_prefname = NULL; + size_t name_len, prefix_len = 0; + struct lys_module *mod; + const struct ly_ctx *ctx = lydctx->jsonctx->ctx; + ly_bool is_attr = 0; + struct lyd_node *prev = node; + uint32_t instance = 0, val_hints; + uint16_t nodetype; + + assert(snode || node); + + nodetype = snode ? snode->nodetype : LYS_CONTAINER; + LOG_LOCSET(snode, NULL, NULL, NULL); + + /* move to the second item in the name/X pair */ + ret = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + + /* check attribute encoding */ + switch (nodetype) { + case LYS_LEAFLIST: + expected = "@name/array of objects/nulls"; + + LY_CHECK_GOTO(status != LYJSON_ARRAY, representation_error); + +next_entry: + instance++; + + /* move into array/next entry */ + ret = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + + if (status == LYJSON_ARRAY_CLOSED) { + /* no more metadata */ + goto cleanup; + } + LY_CHECK_GOTO((status != LYJSON_OBJECT) && (status != LYJSON_NULL), representation_error); + + if (!node || (node->schema != prev->schema)) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "Missing JSON data instance #%u of %s:%s to be coupled with metadata.", + instance, prev->schema->module->name, prev->schema->name); + ret = LY_EVALID; + goto cleanup; + } + + if (status == LYJSON_NULL) { + /* continue with the next entry in the leaf-list array */ + prev = node; + node = node->next; + goto next_entry; + } + break; + case LYS_LEAF: + case LYS_ANYXML: + expected = "@name/object"; + + LY_CHECK_GOTO(status != LYJSON_OBJECT, 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(status != LYJSON_OBJECT, representation_error); + break; + default: + LOGINT(ctx); + ret = LY_EINT; + goto cleanup; + } + + /* process all the members inside a single metadata object */ + assert(status == LYJSON_OBJECT); + while (status != LYJSON_OBJECT_CLOSED) { + LY_CHECK_GOTO(status != LYJSON_OBJECT, representation_error); + + lydjson_parse_name(lydctx->jsonctx->value, lydctx->jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr); + lyjson_ctx_give_dynamic_value(lydctx->jsonctx, &dynamic_prefname); + + if (!name_len) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Metadata in JSON found with an empty name, followed by: %.10s", name); + ret = LY_EVALID; + goto cleanup; + } else if (!prefix_len) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Metadata in JSON must be namespace-qualified, missing prefix for \"%.*s\".", + (int)lydctx->jsonctx->value_len, lydctx->jsonctx->value); + ret = LY_EVALID; + goto cleanup; + } else if (is_attr) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Invalid format of the Metadata identifier in JSON, unexpected '@' in \"%.*s\"", + (int)lydctx->jsonctx->value_len, lydctx->jsonctx->value); + ret = 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); + ret = LY_EVALID; + goto cleanup; + } + if (node->schema) { + /* skip element with children */ + ret = lydjson_data_skip(lydctx->jsonctx); + LY_CHECK_GOTO(ret, cleanup); + status = lyjson_ctx_status(lydctx->jsonctx, 0); + /* end of the item */ + continue; + } + assert(lydctx->parse_opts & LYD_PARSE_OPAQ); + } + + /* get the value */ + ret = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + + /* get value hints */ + ret = lydjson_value_type_hint(lydctx, &status, &val_hints); + LY_CHECK_GOTO(ret, cleanup); + + if (node->schema) { + /* create metadata */ + ret = lyd_parser_create_meta((struct lyd_ctx *)lydctx, node, NULL, mod, name, name_len, lydctx->jsonctx->value, + lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, val_hints, node->schema); + LY_CHECK_GOTO(ret, cleanup); + + /* add/correct flags */ + ret = lyd_parse_set_data_flags(node, &node->meta, (struct lyd_ctx *)lydctx, NULL); + LY_CHECK_GOTO(ret, cleanup); + } else { + /* create attribute */ + const char *module_name; + size_t module_name_len; + + lydjson_get_node_prefix(node, prefix, prefix_len, &module_name, &module_name_len); + + /* attr2 is always changed to the created attribute */ + ret = lyd_create_attr(node, NULL, lydctx->jsonctx->ctx, name, name_len, prefix, prefix_len, module_name, + module_name_len, lydctx->jsonctx->value, lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, + LY_VALUE_JSON, NULL, val_hints); + LY_CHECK_GOTO(ret, cleanup); + } + /* next member */ + ret = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_GOTO((status != LYJSON_OBJECT) && (status != LYJSON_OBJECT_CLOSED), representation_error); + } + + if (nodetype == LYS_LEAFLIST) { + /* continue by processing another metadata object for the following + * leaf-list instance since they are always instantiated in JSON array */ + prev = node; + node = node->next; + goto next_entry; + } + + /* success */ + goto cleanup; + +representation_error: + LOGVAL(ctx, LYVE_SYNTAX_JSON, + "The attribute(s) of %s \"%s\" is expected to be represented as JSON %s, but input data contains @%s/%s.", + lys_nodetype2str(nodetype), node ? LYD_NAME(node) : LYD_NAME(prev), expected, lyjson_token2str(status), + in_parent ? "" : "name"); + ret = LY_EVALID; + +cleanup: + free(dynamic_prefname); + LOG_LOCBACK(1, 0, 0, 0); + return ret; +} + +/** + * @brief Eat the node pointed by @p node_p by inserting it into @p parent and maintain the @p first_p pointing + * to the first child node. + * + * @param[in] parent Parent node to insert to, can be NULL in case of top-level (or provided first_p). + * @param[in,out] first_p Pointer to the first sibling node in case of top-level. + * @param[in,out] node_p pointer to the new node to insert, after the insert is done, pointer is set to NULL. + * @param[in] last If set, always insert at the end. + * @param[in] ext Extension instance of @p node_p, if any. + */ +static void +lydjson_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) { + /* insert, keep first pointer correct */ + 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 Wrapper for ::lyd_create_opaq(). + * + * @param[in] lydctx JSON data parser context. + * @param[in] name Name of the opaq node to create. + * @param[in] name_len Length of the @p name string. + * @param[in] prefix Prefix of the opaq node to create. + * @param[in] prefix_len Length of the @p prefx string. + * @param[in] parent Data parent of the opaq node to create, can be NULL in case of top level, + * but must be set if @p first is not. + * @param[in,out] status_inner_p In case of processing JSON array, this parameter points to a standalone + * context status of the array content. Otherwise, it is supposed to be the same as @p status_p. + * @param[out] node_p Pointer to the created opaq node. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_create_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_len, const char *prefix, size_t prefix_len, + struct lyd_node *parent, enum LYJSON_PARSER_STATUS *status_inner_p, 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; + + if ((*status_inner_p != LYJSON_OBJECT) && (*status_inner_p != LYJSON_OBJECT_EMPTY)) { + /* prepare for creating opaq node with a value */ + value = lydctx->jsonctx->value; + value_len = lydctx->jsonctx->value_len; + dynamic = lydctx->jsonctx->dynamic; + lydctx->jsonctx->dynamic = 0; + + LY_CHECK_RET(lydjson_value_type_hint(lydctx, status_inner_p, &type_hint)); + } + + /* create node */ + lydjson_get_node_prefix(parent, prefix, prefix_len, &module_name, &module_name_len); + ret = lyd_create_opaq(lydctx->jsonctx->ctx, name, name_len, prefix, prefix_len, module_name, module_name_len, value, + value_len, &dynamic, LY_VALUE_JSON, NULL, type_hint, node_p); + if (dynamic) { + free((char *)value); + } + + return ret; +} + +static LY_ERR lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_set *parsed); + +/** + * @brief Parse opaq node from the input. + * + * In case of processing array, the whole array is being processed and the resulting @p node_p is the last item of the array. + * + * @param[in] lydctx JSON data parser context. + * @param[in] name Name of the opaq node to create. + * @param[in] name_len Length of the @p name string. + * @param[in] prefix Prefix of the opaq node to create. + * @param[in] prefix_len Length of the @p prefx string. + * @param[in] parent Data parent of the opaq node to create, can be NULL in case of top level, + * but must be set if @p first is not. + * @param[in,out] status_p Pointer to the current status of the parser context, + * since the function manipulates with the context and process the input, the status can be updated. + * @param[in,out] status_inner_p In case of processing JSON array, this parameter points to a standalone + * context status of the array content. Otherwise, it is supposed to be the same as @p status_p. + * @param[in,out] first_p First top-level/parent sibling, must be set if @p parent is not. + * @param[out] node_p Pointer to the created opaq node. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_parse_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_len, const char *prefix, size_t prefix_len, + struct lyd_node *parent, enum LYJSON_PARSER_STATUS *status_p, enum LYJSON_PARSER_STATUS *status_inner_p, + struct lyd_node **first_p, struct lyd_node **node_p) +{ + LY_CHECK_RET(lydjson_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_inner_p, node_p)); + + if ((*status_p == LYJSON_ARRAY) && (*status_inner_p == LYJSON_NULL)) { + /* special array null value */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_VALHINT_EMPTY; + + /* must be the only item */ + LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_inner_p)); + if (*status_inner_p != LYJSON_ARRAY_CLOSED) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX, "Array \"null\" member with another member."); + return LY_EVALID; + } + + goto finish; + } + + while ((*status_p == LYJSON_ARRAY) || (*status_p == LYJSON_ARRAY_EMPTY)) { + /* process another instance of the same node */ + + if ((*status_inner_p == LYJSON_OBJECT) || (*status_inner_p == LYJSON_OBJECT_EMPTY)) { + /* array with objects, list */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LIST; + + /* but first process children of the object in the array */ + while ((*status_inner_p != LYJSON_OBJECT_CLOSED) && (*status_inner_p != LYJSON_OBJECT_EMPTY)) { + LY_CHECK_RET(lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL)); + *status_inner_p = lyjson_ctx_status(lydctx->jsonctx, 0); + } + } else { + /* array with values, leaf-list */ + ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LEAFLIST; + } + + LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_inner_p)); + if (*status_inner_p == LYJSON_ARRAY_CLOSED) { + goto finish; + } + + /* continue with the next instance */ + assert(node_p); + lydjson_maintain_children(parent, first_p, node_p, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, NULL); + LY_CHECK_RET(lydjson_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_inner_p, node_p)); + } + + if ((*status_p == LYJSON_OBJECT) || (*status_p == LYJSON_OBJECT_EMPTY)) { + /* process children */ + while (*status_p != LYJSON_OBJECT_CLOSED && *status_p != LYJSON_OBJECT_EMPTY) { + LY_CHECK_RET(lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL)); + *status_p = lyjson_ctx_status(lydctx->jsonctx, 0); + } + } + +finish: + /* finish linking metadata */ + return lydjson_metadata_finish(lydctx, lyd_node_child_p(*node_p)); +} + +/** + * @brief Move to the second item in the name/X pair and parse opaq node from the input. + * + * This function is basically the wrapper of the ::lydjson_parse_opaq(). + * In addition, it calls the ::json_ctx_next() and prepares the status_inner_p parameter + * for ::lydjson_parse_opaq(). + * + * @param[in] lydctx JSON data parser context. + * @param[in] name Name of the opaq node to create. + * @param[in] name_len Length of the @p name string. + * @param[in] prefix Prefix of the opaq node to create. + * @param[in] prefix_len Length of the @p prefx string. + * @param[in] parent Data parent of the opaq node to create, can be NULL in case of top level, + * but must be set if @p first is not. + * @param[in,out] status_p Pointer to the current status of the parser context, + * since the function manipulates with the context and process the input, the status can be updated. + * @param[in,out] first_p First top-level/parent sibling, must be set if @p parent is not. + * @param[out] node_p Pointer to the created opaq node. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_ctx_next_parse_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_len, + const char *prefix, size_t prefix_len, struct lyd_node *parent, enum LYJSON_PARSER_STATUS *status_p, + struct lyd_node **first_p, struct lyd_node **node_p) +{ + enum LYJSON_PARSER_STATUS status_inner = 0; + + /* move to the second item in the name/X pair */ + LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_p)); + + if (*status_p == LYJSON_ARRAY) { + /* move into the array */ + LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, &status_inner)); + } else { + /* just a flag to pass correct parameters into lydjson_parse_opaq() */ + status_inner = LYJSON_ERROR; + } + + if (status_inner == LYJSON_ERROR) { + status_inner = *status_p; + } + + /* parse opaq node from the input */ + LY_CHECK_RET(lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_p, &status_inner, + first_p, node_p)); + + return LY_SUCCESS; +} + +/** + * @brief Process the attribute container (starting by @) + * + * @param[in] lydctx JSON data parser context. + * @param[in] attr_node The data node referenced by the attribute container, if already known. + * @param[in] snode The schema node of the data node referenced by the attribute container, if known. + * @param[in] name Name of the opaq node to create. + * @param[in] name_len Length of the @p name string. + * @param[in] prefix Prefix of the opaq node to create. + * @param[in] prefix_len Length of the @p prefx string. + * @param[in] parent Data parent of the opaq node to create, can be NULL in case of top level, + * but must be set if @p first is not. + * @param[in,out] status_p Pointer to the current status of the parser context, + * since the function manipulates with the context and process the input, the status can be updated. + * @param[in,out] first_p First top-level/parent sibling, must be set if @p parent is not. + * @param[out] node_p Pointer to the created opaq node. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_parse_attribute(struct lyd_json_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, + enum LYJSON_PARSER_STATUS *status_p, struct lyd_node **first_p, struct lyd_node **node_p) +{ + LY_ERR r; + const char *opaq_name, *mod_name; + size_t opaq_name_len; + + if (!snode && !prefix) { + /* set the prefix */ + if (parent) { + lydjson_get_node_prefix(parent, NULL, 0, &prefix, &prefix_len); + } else { + prefix = ""; + prefix_len = 0; + } + } + + /* parse as an attribute to a (opaque) node */ + if (!attr_node) { + /* try to find the instance */ + LY_LIST_FOR(*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) { + if (!ly_strncmp(LYD_NAME(attr_node), name, name_len) && + !ly_strncmp(attr_node->schema->module->name, prefix, prefix_len)) { + break; + } + } else { + mod_name = ((struct lyd_node_opaq *)attr_node)->name.module_name; + if (!ly_strncmp(LYD_NAME(attr_node), name, name_len) && mod_name && + !ly_strncmp(mod_name, prefix, prefix_len)) { + break; + } + } + } + } + } + if (!attr_node) { + /* parse just as an opaq node with the name beginning with @, + * later we have to check that it belongs to a standard node + * and it is supposed to be converted to a metadata */ + uint32_t prev_opts; + + /* backup parser options to parse unknown metadata as opaq nodes and try to resolve them later */ + 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 = lydjson_ctx_next_parse_opaq(lydctx, opaq_name, opaq_name_len, NULL, 0, parent, status_p, first_p, node_p); + + /* restore the parser options */ + lydctx->parse_opts = prev_opts; + LY_CHECK_RET(r); + } else { + LY_CHECK_RET(lydjson_metadata(lydctx, snode, attr_node)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse a single anydata/anyxml node. + * + * @param[in] lydctx JSON data parser context. When the function returns, the context is in the same state + * as before calling, despite it is necessary to process input data for checking. + * @param[in] snode Schema node corresponding to the member currently being processed in the context. + * @param[in] ext Extension instance of @p snode, if any. + * @param[in,out] status JSON parser status, is updated. + * @param[out] node Parsed data (or opaque) node. + * @return LY_SUCCESS if a node was successfully parsed, + * @return LY_ENOT in case of invalid JSON encoding, + * @return LY_ERR on other errors. + */ +static LY_ERR +lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext, + enum LYJSON_PARSER_STATUS *status, struct lyd_node **node) +{ + LY_ERR rc = LY_SUCCESS; + uint32_t prev_parse_opts, prev_int_opts; + struct ly_in in_start; + char *val = NULL; + struct lyd_node *tree = NULL; + + assert(snode->nodetype & LYD_NODE_ANY); + + /* status check according to allowed JSON types */ + if (snode->nodetype == LYS_ANYXML) { + LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY) && (*status != LYJSON_ARRAY) && + (*status != LYJSON_ARRAY_EMPTY) && (*status != LYJSON_NUMBER) && (*status != LYJSON_STRING) && + (*status != LYJSON_FALSE) && (*status != LYJSON_TRUE) && (*status != LYJSON_NULL), LY_ENOT); + } else { + LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY), LY_ENOT); + } + + /* create any node */ + switch (*status) { + case LYJSON_OBJECT: + /* parse any data tree with correct options, first backup the current options and then make the parser + * process data as opaq nodes */ + prev_parse_opts = lydctx->parse_opts; + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); + prev_int_opts = lydctx->int_opts; + lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; + + /* process the anydata content */ + while (*status != LYJSON_OBJECT_CLOSED) { + LY_CHECK_RET(lydjson_subtree_r(lydctx, NULL, &tree, NULL)); + *status = lyjson_ctx_status(lydctx->jsonctx, 0); + } + + /* restore parser options */ + lydctx->parse_opts = prev_parse_opts; + lydctx->int_opts = prev_int_opts; + + /* finish linking metadata */ + LY_CHECK_RET(lydjson_metadata_finish(lydctx, &tree)); + + LY_CHECK_RET(lyd_create_any(snode, tree, LYD_ANYDATA_DATATREE, 1, node)); + break; + case LYJSON_ARRAY_EMPTY: + /* store the empty array */ + if (asprintf(&val, "[]") == -1) { + LOGMEM(lydctx->jsonctx->ctx); + return LY_EMEM; + } + LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err); + break; + case LYJSON_ARRAY: + /* skip until the array end */ + in_start = *lydctx->jsonctx->in; + LY_CHECK_RET(lydjson_data_skip(lydctx->jsonctx)); + + /* make a copy of the whole array and store it */ + if (asprintf(&val, "[%.*s", (int)(lydctx->jsonctx->in->current - in_start.current), in_start.current) == -1) { + LOGMEM(lydctx->jsonctx->ctx); + return LY_EMEM; + } + LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err); + break; + case LYJSON_STRING: + /* string value */ + if (lydctx->jsonctx->dynamic) { + LY_CHECK_RET(lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node)); + lydctx->jsonctx->dynamic = 0; + } else { + val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len); + LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM); + + LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node), val_err); + } + break; + case LYJSON_NUMBER: + case LYJSON_FALSE: + case LYJSON_TRUE: + /* JSON value */ + assert(!lydctx->jsonctx->dynamic); + val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len); + LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM); + + LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err); + break; + case LYJSON_NULL: + /* no value */ + LY_CHECK_RET(lyd_create_any(snode, NULL, LYD_ANYDATA_JSON, 1, node)); + break; + case LYJSON_OBJECT_EMPTY: + /* empty object */ + LY_CHECK_RET(lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node)); + break; + default: + LOGINT_RET(lydctx->jsonctx->ctx); + } + + return LY_SUCCESS; + +val_err: + free(val); + return rc; +} + +/** + * @brief Parse a single instance of an inner node. + * + * @param[in] lydctx JSON data parser context. + * @param[in] snode Schema node corresponding to the member currently being processed in the context. + * @param[in] ext Extension instance of @p snode, if any. + * @param[in,out] status JSON parser status, is updated. + * @param[out] node Parsed data (or opaque) node. + * @return LY_SUCCESS if a node was successfully parsed, + * @return LY_ENOT in case of invalid JSON encoding, + * @return LY_ERR on other errors. + */ +static LY_ERR +lydjson_parse_instance_inner(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext, + enum LYJSON_PARSER_STATUS *status, struct lyd_node **node) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t prev_parse_opts = lydctx->parse_opts; + + LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY), LY_ENOT); + + /* create inner node */ + LY_CHECK_RET(lyd_create_inner(snode, node)); + + /* use it for logging */ + LOG_LOCSET(NULL, *node, NULL, NULL); + + if (ext) { + /* only parse these extension data and validate afterwards */ + lydctx->parse_opts |= LYD_PARSE_ONLY; + } + + /* process children */ + while ((*status != LYJSON_OBJECT_CLOSED) && (*status != LYJSON_OBJECT_EMPTY)) { + ret = lydjson_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL); + LY_CHECK_GOTO(ret, cleanup); + *status = lyjson_ctx_status(lydctx->jsonctx, 0); + } + + /* finish linking metadata */ + ret = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node)); + LY_CHECK_GOTO(ret, cleanup); + + if (snode->nodetype == LYS_LIST) { + /* check all keys exist */ + ret = lyd_parse_check_keys(*node); + LY_CHECK_GOTO(ret, cleanup); + } + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { + /* new node validation, autodelete CANNOT occur, all nodes are new */ + ret = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* add any missing default children */ + ret = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when, + &lydctx->node_types, &lydctx->ext_node, + (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + lydctx->parse_opts = prev_parse_opts; + LOG_LOCBACK(0, 1, 0, 0); + return ret; +} + +/** + * @brief Parse a single instance of a node. + * + * @param[in] lydctx JSON data parser context. When the function returns, the context is in the same state + * as before calling, despite it is necessary to process input data for checking. + * @param[in] parent Data parent of the subtree, must be set if @p first is not. + * @param[in,out] first_p Pointer to the variable holding the first top-level sibling, must be set if @p parent is not. + * @param[in] snode Schema node corresponding to the member currently being processed in the context. + * @param[in] ext Extension instance of @p snode, if any. + * @param[in] name Parsed JSON node name. + * @param[in] name_len Lenght of @p name. + * @param[in] prefix Parsed JSON node prefix. + * @param[in] prefix_len Length of @p prefix. + * @param[in,out] status JSON parser status, is updated. + * @param[out] node Parsed data (or opaque) node. + * @return LY_SUCCESS if a node was successfully parsed, + * @return LY_ENOT in case of invalid JSON encoding, + * @return LY_ERR on other errors. + */ +static LY_ERR +lydjson_parse_instance(struct lyd_json_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, enum LYJSON_PARSER_STATUS *status, struct lyd_node **node) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t type_hints = 0; + + LOG_LOCSET(snode, NULL, NULL, NULL); + + ret = lydjson_data_check_opaq(lydctx, snode, &type_hints); + if (ret == LY_SUCCESS) { + assert(snode->nodetype & (LYD_NODE_TERM | LYD_NODE_INNER | LYD_NODE_ANY)); + if (snode->nodetype & LYD_NODE_TERM) { + if ((*status != LYJSON_ARRAY) && (*status != LYJSON_NUMBER) && (*status != LYJSON_STRING) && + (*status != LYJSON_FALSE) && (*status != LYJSON_TRUE) && (*status != LYJSON_NULL)) { + ret = LY_ENOT; + goto cleanup; + } + + /* create terminal node */ + LY_CHECK_GOTO(ret = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, lydctx->jsonctx->value, + lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, type_hints, node), cleanup); + + /* move JSON parser */ + if (*status == LYJSON_ARRAY) { + /* only [null], 2 more moves are needed */ + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status), cleanup); + assert(*status == LYJSON_NULL); + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status), cleanup); + assert(*status == LYJSON_ARRAY_CLOSED); + } + } else if (snode->nodetype & LYD_NODE_INNER) { + /* create inner node */ + LY_CHECK_GOTO(ret = lydjson_parse_instance_inner(lydctx, snode, ext, status, node), cleanup); + } else { + /* create any node */ + LY_CHECK_GOTO(ret = lydjson_parse_any(lydctx, snode, ext, status, node), cleanup); + } + + /* add/correct flags */ + LY_CHECK_GOTO(ret = lyd_parse_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext), cleanup); + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { + /* store for ext instance node validation, if needed */ + LY_CHECK_GOTO(ret = lyd_validate_node_ext(*node, &lydctx->ext_node), cleanup); + } + } else if (ret == LY_ENOT) { + /* parse it again as an opaq node */ + LY_CHECK_GOTO(ret = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status, status, + first_p, node), 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 */ + goto cleanup; + } + +cleanup: + LOG_LOCBACK(1, 0, 0, 0); + return ret; +} + +/** + * @brief Parse JSON subtree. All leaf-list and list instances of a node are considered one subtree. + * + * @param[in] lydctx JSON data parser context. + * @param[in] parent Data parent of the subtree, must be set if @p first is not. + * @param[in,out] first_p Pointer to the variable holding the first top-level sibling, must be set if @p parent is not. + * @param[in,out] parsed Optional set to add all the parsed siblings into. + * @return LY_ERR value. + */ +static LY_ERR +lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS, r; + enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(lydctx->jsonctx, 0); + const char *name, *prefix = NULL, *expected = NULL; + size_t name_len, prefix_len = 0; + ly_bool is_meta = 0, parse_subtree; + const struct lysc_node *snode = NULL; + struct lysc_ext_instance *ext; + struct lyd_node *node = NULL, *attr_node = NULL; + const struct ly_ctx *ctx = lydctx->jsonctx->ctx; + char *value = NULL; + + assert(parent || first_p); + assert(status == LYJSON_OBJECT); + + parse_subtree = lydctx->parse_opts & LYD_PARSE_SUBTREE ? 1 : 0; + /* all descendants should be parsed */ + lydctx->parse_opts &= ~LYD_PARSE_SUBTREE; + + /* process the node name */ + lydjson_parse_name(lydctx->jsonctx->value, lydctx->jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_meta); + lyjson_ctx_give_dynamic_value(lydctx->jsonctx, &value); + + if (!is_meta || name_len || prefix_len) { + /* get the schema node */ + r = lydjson_get_snode(lydctx, is_meta, prefix, prefix_len, name, name_len, parent, &snode, &ext); + if (r == LY_ENOT) { + /* data parsed */ + goto cleanup; + } + LY_CHECK_ERR_GOTO(r, ret = r, cleanup); + + if (!snode) { + /* we will not be parsing it as metadata */ + is_meta = 0; + } + } + + if (is_meta) { + /* parse as metadata */ + if (!name_len && !prefix_len) { + /* parent's metadata without a name - use the schema from the parent */ + if (!parent) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, + "Invalid metadata format - \"@\" can be used only inside anydata, container or list entries."); + ret = LY_EVALID; + goto cleanup; + } + attr_node = parent; + snode = attr_node->schema; + } + ret = lydjson_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, &status, + first_p, &node); + LY_CHECK_GOTO(ret, cleanup); + } else if (!snode) { + /* parse as an opaq node */ + assert((lydctx->parse_opts & LYD_PARSE_OPAQ) || (lydctx->int_opts)); + + /* opaq node cannot have an empty string as the name. */ + if (name_len == 0) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "A JSON object member name cannot be a zero-length string."); + ret = LY_EVALID; + goto cleanup; + } + + /* move to the second item in the name/X pair and parse opaq */ + ret = lydjson_ctx_next_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, &status, first_p, &node); + LY_CHECK_GOTO(ret, cleanup); + } else { + /* parse as a standard lyd_node but it can still turn out to be an opaque node */ + + /* move to the second item in the name/X pair */ + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + + /* 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: + if (status == LYJSON_ARRAY) { + expected = "name/[null]"; + } else { + 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: + if (status == LYJSON_ARRAY) { + expected = "name/array"; + } else { + expected = "name/value"; + } + break; + } + + /* check the representation according to the nodetype and then continue with the content */ + switch (snode->nodetype) { + case LYS_LEAFLIST: + case LYS_LIST: + if (status == LYJSON_ARRAY_EMPTY) { + /* no instances, skip */ + break; + } + LY_CHECK_GOTO(status != LYJSON_ARRAY, representation_error); + + /* move into array */ + ret = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_GOTO(ret, cleanup); + + /* process all the values/objects */ + do { + ret = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, + &status, &node); + if (ret == LY_ENOT) { + goto representation_error; + } else if (ret) { + goto cleanup; + } + lydjson_maintain_children(parent, first_p, &node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, ext); + + /* move after the item(s) */ + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + } while (status != LYJSON_ARRAY_CLOSED); + + 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 */ + ret = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, + &status, &node); + if (ret == LY_ENOT) { + goto representation_error; + } else if (ret) { + goto cleanup; + } + + if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { + /* rememeber the RPC/action/notification */ + lydctx->op_node = node; + } + break; + } + } + + /* finally connect the parsed node */ + lydjson_maintain_children(parent, first_p, &node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, ext); + + /* rememeber a successfully parsed node */ + if (parsed && node) { + ly_set_add(parsed, node, 1, NULL); + } + + if (!parse_subtree) { + /* move after the item(s) */ + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + } + + /* success */ + goto cleanup; + +representation_error: + LOGVAL(ctx, LYVE_SYNTAX_JSON, "The %s \"%s\" is expected to be represented as JSON %s, but input data contains name/%s.", + lys_nodetype2str(snode->nodetype), snode->name, expected, lyjson_token2str(status)); + ret = LY_EVALID; + +cleanup: + free(value); + lyd_free_tree(node); + return ret; +} + +/** + * @brief Common start of JSON parser processing different types of the input data. + * + * @param[in] ctx libyang context + * @param[in] in Input structure. + * @param[in] parse_opts Options for parser, see @ref dataparseroptions. + * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. + * @param[out] lydctx_p Data parser context to finish validation. + * @param[out] status Storage for the current context's status + * @return LY_ERR value. + */ +static LY_ERR +lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, + struct lyd_json_ctx **lydctx_p, enum LYJSON_PARSER_STATUS *status) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_json_ctx *lydctx; + size_t i; + ly_bool subtree; + + assert(lydctx_p); + assert(status); + + /* 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_json_ctx_free; + + /* starting top-level */ + for (i = 0; in->current[i] != '\0' && is_jsonws(in->current[i]); i++) { + if (in->current[i] == '\n') { + /* new line */ + LY_IN_NEW_LINE(in); + } + } + + subtree = (parse_opts & LYD_PARSE_SUBTREE) ? 1 : 0; + LY_CHECK_ERR_RET(ret = lyjson_ctx_new(ctx, in, subtree, &lydctx->jsonctx), free(lydctx), ret); + *status = lyjson_ctx_status(lydctx->jsonctx, 0); + + if ((*status == LYJSON_END) || (*status == LYJSON_OBJECT_EMPTY) || (*status == LYJSON_OBJECT)) { + *lydctx_p = lydctx; + return LY_SUCCESS; + } else { + /* expecting top-level object */ + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object, but %s found.", lyjson_token2str(*status)); + *lydctx_p = NULL; + lyd_json_ctx_free((struct lyd_ctx *)lydctx); + return LY_EVALID; + } +} + +LY_ERR +lyd_parse_json(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 rc = LY_SUCCESS; + struct lyd_json_ctx *lydctx = NULL; + enum LYJSON_PARSER_STATUS status; + + rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx, &status); + LY_CHECK_GOTO(rc || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup); + + assert(status == LYJSON_OBJECT); + + lydctx->int_opts = int_opts; + lydctx->ext = ext; + + /* 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) */ + while (lydctx->jsonctx->in->current[0] && (status != LYJSON_OBJECT_CLOSED)) { + rc = lydjson_subtree_r(lydctx, parent, first_p, parsed); + LY_CHECK_GOTO(rc, cleanup); + + status = lyjson_ctx_status(lydctx->jsonctx, 0); + + if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { + break; + } + } + + if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lydctx->jsonctx->in->current[0] && + (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_OBJECT_CLOSED)) { + LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); + rc = LY_EVALID; + goto 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."); + rc = LY_EVALID; + goto cleanup; + } + + /* finish linking metadata */ + rc = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p); + LY_CHECK_GOTO(rc, cleanup); + + if (parse_opts & LYD_PARSE_SUBTREE) { + /* check for a sibling object */ + assert(subtree_sibling); + if (lydctx->jsonctx->in->current[0] == ',') { + *subtree_sibling = 1; + + /* move to the next object */ + ly_in_skip(lydctx->jsonctx->in, 1); + } else { + *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) { + lyd_json_ctx_free((struct lyd_ctx *)lydctx); + } else { + *lydctx_p = (struct lyd_ctx *)lydctx; + + /* the JSON context is no more needed, freeing it also stops logging line numbers which would be confusing now */ + lyjson_ctx_free(lydctx->jsonctx); + lydctx->jsonctx = NULL; + } + return rc; +} diff --git a/src/parser_lyb.c b/src/parser_lyb.c new file mode 100644 index 0000000..f898085 --- /dev/null +++ b/src/parser_lyb.c @@ -0,0 +1,1792 @@ +/** + * @file parser_lyb.c + * @author Michal Vasko + * @brief LYB data parser for libyang + * + * Copyright (c) 2020 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "lyb.h" + +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "hash_table.h" +#include "in.h" +#include "in_internal.h" +#include "log.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "plugins_exts.h" +#include "plugins_exts/metadata.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "validation.h" +#include "xml.h" + +static LY_ERR lyb_parse_siblings(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_set *parsed); + +void +lylyb_ctx_free(struct lylyb_ctx *ctx) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FREE(ctx->siblings); + LY_ARRAY_FREE(ctx->models); + + LY_ARRAY_FOR(ctx->sib_hts, u) { + lyht_free(ctx->sib_hts[u].ht); + } + LY_ARRAY_FREE(ctx->sib_hts); + + free(ctx); +} + +void +lyd_lyb_ctx_free(struct lyd_ctx *lydctx) +{ + struct lyd_lyb_ctx *ctx = (struct lyd_lyb_ctx *)lydctx; + + lyd_ctx_free(lydctx); + lylyb_ctx_free(ctx->lybctx); + free(ctx); +} + +/** + * @brief Read metadata about siblings. + * + * @param[out] sib Structure in which the metadata will be stored. + * @param[in] lybctx LYB context. + */ +static void +lyb_read_sibling_meta(struct lyd_lyb_sibling *sib, struct lylyb_ctx *lybctx) +{ + uint8_t meta_buf[LYB_META_BYTES]; + uint64_t num = 0; + + ly_in_read(lybctx->in, meta_buf, LYB_META_BYTES); + + memcpy(&num, meta_buf, LYB_SIZE_BYTES); + sib->written = le64toh(num); + memcpy(&num, meta_buf + LYB_SIZE_BYTES, LYB_INCHUNK_BYTES); + sib->inner_chunks = le64toh(num); + + /* remember whether there is a following chunk or not */ + sib->position = (sib->written == LYB_SIZE_MAX ? 1 : 0); +} + +/** + * @brief Read YANG data from LYB input. Metadata are handled transparently and not returned. + * + * @param[in] buf Destination buffer. + * @param[in] count Number of bytes to read. + * @param[in] lybctx LYB context. + */ +static void +lyb_read(uint8_t *buf, size_t count, struct lylyb_ctx *lybctx) +{ + LY_ARRAY_COUNT_TYPE u; + struct lyd_lyb_sibling *empty; + size_t to_read; + + assert(lybctx); + + while (1) { + /* check for fully-read (empty) data chunks */ + to_read = count; + empty = NULL; + LY_ARRAY_FOR(lybctx->siblings, u) { + /* we want the innermost chunks resolved first, so replace previous empty chunks, + * also ignore chunks that are completely finished, there is nothing for us to do */ + if ((lybctx->siblings[u].written <= to_read) && lybctx->siblings[u].position) { + /* empty chunk, do not read more */ + to_read = lybctx->siblings[u].written; + empty = &lybctx->siblings[u]; + } + } + + if (!empty && !count) { + break; + } + + /* we are actually reading some data, not just finishing another chunk */ + if (to_read) { + if (buf) { + ly_in_read(lybctx->in, buf, to_read); + } else { + ly_in_skip(lybctx->in, to_read); + } + + LY_ARRAY_FOR(lybctx->siblings, u) { + /* decrease all written counters */ + lybctx->siblings[u].written -= to_read; + assert(lybctx->siblings[u].written <= LYB_SIZE_MAX); + } + /* decrease count/buf */ + count -= to_read; + if (buf) { + buf += to_read; + } + } + + if (empty) { + /* read the next chunk meta information */ + lyb_read_sibling_meta(empty, lybctx); + } + } +} + +/** + * @brief Read a number. + * + * @param[in] num Destination buffer. + * @param[in] num_size Size of @p num. + * @param[in] bytes Number of bytes to read. + * @param[in] lybctx LYB context. + */ +static void +lyb_read_number(void *num, size_t num_size, size_t bytes, struct lylyb_ctx *lybctx) +{ + uint64_t buf = 0; + + lyb_read((uint8_t *)&buf, bytes, lybctx); + + /* correct byte order */ + buf = le64toh(buf); + + switch (num_size) { + case sizeof(uint8_t): + *((uint8_t *)num) = buf; + break; + case sizeof(uint16_t): + *((uint16_t *)num) = buf; + break; + case sizeof(uint32_t): + *((uint32_t *)num) = buf; + break; + case sizeof(uint64_t): + *((uint64_t *)num) = buf; + break; + default: + LOGINT(lybctx->ctx); + } +} + +/** + * @brief Read a string. + * + * @param[in] str Destination buffer, is allocated. + * @param[in] len_size Number of bytes on which the length of the string is written. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_read_string(char **str, uint8_t len_size, struct lylyb_ctx *lybctx) +{ + uint64_t len = 0; + + assert((len_size == 1) || (len_size == 2) || (len_size == 4) || (len_size == 8)); + + *str = NULL; + + lyb_read_number(&len, sizeof len, len_size, lybctx); + + *str = malloc((len + 1) * sizeof **str); + LY_CHECK_ERR_RET(!*str, LOGMEM(lybctx->ctx), LY_EMEM); + + lyb_read((uint8_t *)*str, len, lybctx); + + (*str)[len] = '\0'; + return LY_SUCCESS; +} + +/** + * @brief Skip string. + * + * @param[in] len_size Number of bytes on which the length of the string is written. + * @param[in] lybctx LYB context. + */ +static void +lyb_skip_string(uint8_t len_size, struct lylyb_ctx *lybctx) +{ + size_t len = 0; + + lyb_read_number(&len, sizeof len, len_size, lybctx); + + lyb_read(NULL, len, lybctx); +} + +/** + * @brief Read value of term node. + * + * @param[in] term Compiled term node. + * @param[out] term_value Set to term node value in dynamically + * allocated memory. The caller must release it. + * @param[out] term_value_len Value length in bytes. The zero byte is + * always included and is not counted. + * @param[in,out] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_read_term_value(const struct lysc_node_leaf *term, uint8_t **term_value, uint64_t *term_value_len, + struct lylyb_ctx *lybctx) +{ + uint32_t allocated_size; + int32_t lyb_data_len; + struct lysc_type_leafref *type_lf; + + assert(term && term_value && term_value_len && lybctx); + + /* Find out the size from @ref howtoDataLYB. */ + if (term->type->basetype == LY_TYPE_LEAFREF) { + /* Leafref itself is ignored, the target is loaded directly. */ + type_lf = (struct lysc_type_leafref *)term->type; + lyb_data_len = type_lf->realtype->plugin->lyb_data_len; + } else { + lyb_data_len = term->type->plugin->lyb_data_len; + } + + if (lyb_data_len < 0) { + /* Parse value size. */ + lyb_read_number(term_value_len, sizeof *term_value_len, + sizeof *term_value_len, lybctx); + } else { + /* Data size is fixed. */ + *term_value_len = lyb_data_len; + } + + /* Allocate memory. */ + allocated_size = *term_value_len + 1; + *term_value = malloc(allocated_size * sizeof **term_value); + LY_CHECK_ERR_RET(!*term_value, LOGMEM(lybctx->ctx), LY_EMEM); + + if (*term_value_len > 0) { + /* Parse value. */ + lyb_read(*term_value, *term_value_len, lybctx); + } + + /* Add extra zero byte regardless of whether it is string or not. */ + (*term_value)[allocated_size - 1] = 0; + + return LY_SUCCESS; +} + +/** + * @brief Stop the current "siblings" - change LYB context state. + * + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_read_stop_siblings(struct lylyb_ctx *lybctx) +{ + if (LYB_LAST_SIBLING(lybctx).written) { + LOGINT_RET(lybctx->ctx); + } + + LY_ARRAY_DECREMENT(lybctx->siblings); + return LY_SUCCESS; +} + +/** + * @brief Start a new "siblings" - change LYB context state but also read the expected metadata. + * + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_read_start_siblings(struct lylyb_ctx *lybctx) +{ + LY_ARRAY_COUNT_TYPE u; + + u = LY_ARRAY_COUNT(lybctx->siblings); + if (u == lybctx->sibling_size) { + LY_ARRAY_CREATE_RET(lybctx->ctx, lybctx->siblings, u + LYB_SIBLING_STEP, LY_EMEM); + lybctx->sibling_size = u + LYB_SIBLING_STEP; + } + + LY_ARRAY_INCREMENT(lybctx->siblings); + lyb_read_sibling_meta(&LYB_LAST_SIBLING(lybctx), lybctx); + + return LY_SUCCESS; +} + +/** + * @brief Read YANG model info. + * + * @param[in] lybctx LYB context. + * @param[out] mod_name Module name, if any. + * @param[out] mod_rev Module revision, "" if none. + * @param[in,out] feat_set Set to add the names of enabled features to. If not set, enabled features are not parsed. + * @return LY_ERR value. + */ +static LY_ERR +lyb_read_model(struct lylyb_ctx *lybctx, char **mod_name, char mod_rev[], struct ly_set *feat_set) +{ + uint16_t i, rev, length; + char *str; + + *mod_name = NULL; + mod_rev[0] = '\0'; + + lyb_read_number(&length, 2, 2, lybctx); + if (!length) { + return LY_SUCCESS; + } + + /* module name */ + *mod_name = malloc(length + 1); + LY_CHECK_ERR_RET(!*mod_name, LOGMEM(lybctx->ctx), LY_EMEM); + lyb_read(((uint8_t *)*mod_name), length, lybctx); + (*mod_name)[length] = '\0'; + + /* module revision */ + lyb_read_number(&rev, sizeof rev, 2, lybctx); + + if (rev) { + sprintf(mod_rev, "%04u-%02u-%02u", ((rev & LYB_REV_YEAR_MASK) >> LYB_REV_YEAR_SHIFT) + LYB_REV_YEAR_OFFSET, + (rev & LYB_REV_MONTH_MASK) >> LYB_REV_MONTH_SHIFT, rev & LYB_REV_DAY_MASK); + } + + if (!feat_set) { + /* enabled features not printed */ + return LY_SUCCESS; + } + + /* enabled feature count */ + lyb_read_number(&length, sizeof length, sizeof length, lybctx); + if (!length) { + return LY_SUCCESS; + } + + /* enabled features */ + for (i = 0; i < length; ++i) { + LY_CHECK_RET(lyb_read_string(&str, sizeof length, lybctx)); + ly_set_add(feat_set, str, 1, NULL); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse YANG model info. + * + * @param[in] lybctx LYB context. + * @param[in] parse_options Flag with options for parsing. + * @param[in] with_features Whether the enabled features were also printed and should be read. + * @param[out] mod Parsed module. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_model(struct lylyb_ctx *lybctx, uint32_t parse_options, ly_bool with_features, const struct lys_module **mod) +{ + LY_ERR ret = LY_SUCCESS; + const struct lys_module *m = NULL; + char *mod_name = NULL, mod_rev[LY_REV_SIZE]; + struct ly_set feat_set = {0}; + struct lysp_feature *f = NULL; + uint32_t i, idx = 0; + ly_bool enabled; + + /* read module info */ + if ((ret = lyb_read_model(lybctx, &mod_name, mod_rev, with_features ? &feat_set : NULL))) { + goto cleanup; + } + + /* get the module */ + if (mod_rev[0]) { + m = ly_ctx_get_module(lybctx->ctx, mod_name, mod_rev); + if ((parse_options & LYD_PARSE_LYB_MOD_UPDATE) && !m) { + /* try to use an updated module */ + m = ly_ctx_get_module_implemented(lybctx->ctx, mod_name); + if (m && (!m->revision || (strcmp(m->revision, mod_rev) < 0))) { + /* not an implemented module in a newer revision */ + m = NULL; + } + } + } else { + m = ly_ctx_get_module_latest(lybctx->ctx, mod_name); + } + + if (!m || !m->implemented) { + if (parse_options & LYD_PARSE_STRICT) { + if (!m) { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid context for LYB data parsing, missing module \"%s%s%s\".", + mod_name, mod_rev[0] ? "@" : "", mod_rev[0] ? mod_rev : ""); + } else if (!m->implemented) { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid context for LYB data parsing, module \"%s%s%s\" not implemented.", + mod_name, mod_rev[0] ? "@" : "", mod_rev[0] ? mod_rev : ""); + } + ret = LY_EINVAL; + goto cleanup; + } + + goto cleanup; + } + + if (with_features) { + /* check features */ + while ((f = lysp_feature_next(f, m->parsed, &idx))) { + enabled = 0; + for (i = 0; i < feat_set.count; ++i) { + if (!strcmp(feat_set.objs[i], f->name)) { + enabled = 1; + break; + } + } + + if (enabled && !(f->flags & LYS_FENABLED)) { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid context for LYB data parsing, module \"%s\" has \"%s\" feature disabled.", + mod_name, f->name); + ret = LY_EINVAL; + goto cleanup; + } else if (!enabled && (f->flags & LYS_FENABLED)) { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid context for LYB data parsing, module \"%s\" has \"%s\" feature enabled.", + mod_name, f->name); + ret = LY_EINVAL; + goto cleanup; + } + } + } + + /* fill cached hashes, if not already */ + lyb_cache_module_hash(m); + +cleanup: + *mod = m; + free(mod_name); + ly_set_erase(&feat_set, free); + return ret; +} + +/** + * @brief Parse YANG node metadata. + * + * @param[in] lybctx LYB context. + * @param[in] sparent Schema parent node of the metadata. + * @param[out] meta Parsed metadata. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_metadata(struct lyd_lyb_ctx *lybctx, const struct lysc_node *sparent, struct lyd_meta **meta) +{ + LY_ERR ret = LY_SUCCESS; + ly_bool dynamic; + uint8_t i, count = 0; + char *meta_name = NULL, *meta_value; + const struct lys_module *mod; + + /* read number of attributes stored */ + lyb_read(&count, 1, lybctx->lybctx); + + /* read attributes */ + for (i = 0; i < count; ++i) { + /* find model */ + ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_opts, 0, &mod); + LY_CHECK_GOTO(ret, cleanup); + + if (!mod) { + /* skip meta name */ + lyb_skip_string(sizeof(uint16_t), lybctx->lybctx); + + /* skip meta value */ + lyb_skip_string(sizeof(uint16_t), lybctx->lybctx); + continue; + } + + /* meta name */ + ret = lyb_read_string(&meta_name, sizeof(uint16_t), lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* meta value */ + ret = lyb_read_string(&meta_value, sizeof(uint64_t), lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + dynamic = 1; + + /* create metadata */ + ret = lyd_parser_create_meta((struct lyd_ctx *)lybctx, NULL, meta, mod, meta_name, strlen(meta_name), meta_value, + ly_strlen(meta_value), &dynamic, LY_VALUE_JSON, NULL, LYD_HINT_DATA, sparent); + + /* free strings */ + free(meta_name); + meta_name = NULL; + if (dynamic) { + free(meta_value); + dynamic = 0; + } + + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + free(meta_name); + if (ret) { + lyd_free_meta_siblings(*meta); + *meta = NULL; + } + return ret; +} + +/** + * @brief Parse format-specific prefix data. + * + * @param[in] lybctx LYB context. + * @param[in] format Prefix data format. + * @param[out] prefix_data Parsed prefix data. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_prefix_data(struct lylyb_ctx *lybctx, LY_VALUE_FORMAT format, void **prefix_data) +{ + LY_ERR ret = LY_SUCCESS; + uint8_t count, i; + struct ly_set *set = NULL; + struct lyxml_ns *ns = NULL; + + switch (format) { + case LY_VALUE_XML: + /* read count */ + lyb_read(&count, 1, lybctx); + + /* read all NS elements */ + LY_CHECK_GOTO(ret = ly_set_new(&set), cleanup); + + for (i = 0; i < count; ++i) { + ns = calloc(1, sizeof *ns); + + /* prefix */ + LY_CHECK_GOTO(ret = lyb_read_string(&ns->prefix, sizeof(uint16_t), lybctx), cleanup); + if (!strlen(ns->prefix)) { + free(ns->prefix); + ns->prefix = NULL; + } + + /* namespace */ + LY_CHECK_GOTO(ret = lyb_read_string(&ns->uri, sizeof(uint16_t), lybctx), cleanup); + + LY_CHECK_GOTO(ret = ly_set_add(set, ns, 1, NULL), cleanup); + ns = NULL; + } + + *prefix_data = set; + break; + case LY_VALUE_JSON: + case LY_VALUE_LYB: + /* nothing stored */ + break; + default: + LOGINT(lybctx->ctx); + ret = LY_EINT; + break; + } + +cleanup: + if (ret) { + ly_free_prefix_data(format, set); + if (ns) { + free(ns->prefix); + free(ns->uri); + free(ns); + } + } + return ret; +} + +/** + * @brief Parse opaque attributes. + * + * @param[in] lybctx LYB context. + * @param[out] attr Parsed attributes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_attributes(struct lylyb_ctx *lybctx, struct lyd_attr **attr) +{ + LY_ERR ret = LY_SUCCESS; + uint8_t count, i; + struct lyd_attr *attr2 = NULL; + char *prefix = NULL, *module_name = NULL, *name = NULL, *value = NULL; + ly_bool dynamic = 0; + LY_VALUE_FORMAT format = 0; + void *val_prefix_data = NULL; + + /* read count */ + lyb_read(&count, 1, lybctx); + + /* read attributes */ + for (i = 0; i < count; ++i) { + /* prefix, may be empty */ + ret = lyb_read_string(&prefix, sizeof(uint16_t), lybctx); + LY_CHECK_GOTO(ret, cleanup); + if (!prefix[0]) { + free(prefix); + prefix = NULL; + } + + /* namespace, may be empty */ + ret = lyb_read_string(&module_name, sizeof(uint16_t), lybctx); + LY_CHECK_GOTO(ret, cleanup); + if (!module_name[0]) { + free(module_name); + module_name = NULL; + } + + /* name */ + ret = lyb_read_string(&name, sizeof(uint16_t), lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* format */ + lyb_read_number(&format, sizeof format, 1, lybctx); + + /* value prefixes */ + ret = lyb_parse_prefix_data(lybctx, format, &val_prefix_data); + LY_CHECK_GOTO(ret, cleanup); + + /* value */ + ret = lyb_read_string(&value, sizeof(uint64_t), lybctx); + LY_CHECK_ERR_GOTO(ret, ly_free_prefix_data(format, val_prefix_data), cleanup); + dynamic = 1; + + /* attr2 is always changed to the created attribute */ + ret = lyd_create_attr(NULL, &attr2, lybctx->ctx, name, strlen(name), prefix, ly_strlen(prefix), module_name, + ly_strlen(module_name), value, ly_strlen(value), &dynamic, format, val_prefix_data, LYD_HINT_DATA); + LY_CHECK_GOTO(ret, cleanup); + + free(prefix); + prefix = NULL; + free(module_name); + module_name = NULL; + free(name); + name = NULL; + assert(!dynamic); + value = NULL; + + if (!*attr) { + *attr = attr2; + } + + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + free(prefix); + free(module_name); + free(name); + if (dynamic) { + free(value); + } + if (ret) { + lyd_free_attr_siblings(lybctx->ctx, *attr); + *attr = NULL; + } + return ret; +} + +/** + * @brief Fill @p hash with hash values. + * + * @param[in] lybctx LYB context. + * @param[in,out] hash Pointer to the array in which the hash values are to be written. + * @param[out] hash_count Number of hashes in @p hash. + * @return LY_ERR value. + */ +static LY_ERR +lyb_read_hashes(struct lylyb_ctx *lybctx, LYB_HASH *hash, uint8_t *hash_count) +{ + uint8_t i = 0, j; + + /* read the first hash */ + lyb_read(&hash[0], sizeof *hash, lybctx); + + if (!hash[0]) { + *hash_count = i + 1; + return LY_SUCCESS; + } + + /* based on the first hash read all the other ones, if any */ + for (i = 0; !(hash[0] & (LYB_HASH_COLLISION_ID >> i)); ++i) { + if (i > LYB_HASH_BITS) { + LOGINT_RET(lybctx->ctx); + } + } + + /* move the first hash on its accurate position */ + hash[i] = hash[0]; + + /* read the rest of hashes */ + for (j = i; j; --j) { + lyb_read(&hash[j - 1], sizeof *hash, lybctx); + + /* correct collision ID */ + assert(hash[j - 1] & (LYB_HASH_COLLISION_ID >> (j - 1))); + /* preceded with zeros */ + assert(!(hash[j - 1] & (LYB_HASH_MASK << (LYB_HASH_BITS - (j - 1))))); + } + + *hash_count = i + 1; + + return LY_SUCCESS; +} + +/** + * @brief Check whether a schema node matches a hash(es). + * + * @param[in] sibling Schema node to check. + * @param[in] hash Hash array to check. + * @param[in] hash_count Number of hashes in @p hash. + * @return non-zero if matches, + * @return 0 if not. + */ +static int +lyb_is_schema_hash_match(struct lysc_node *sibling, LYB_HASH *hash, uint8_t hash_count) +{ + LYB_HASH sibling_hash; + uint8_t i; + + /* compare all the hashes starting from collision ID 0 */ + for (i = 0; i < hash_count; ++i) { + sibling_hash = lyb_get_hash(sibling, i); + if (sibling_hash != hash[i]) { + return 0; + } + } + + return 1; +} + +/** + * @brief Parse schema node hash. + * + * @param[in] lybctx LYB context. + * @param[in] sparent Schema parent, must be set if @p mod is not. + * @param[in] mod Module of the top-level node, must be set if @p sparent is not. + * @param[out] snode Parsed found schema node, may be NULL if opaque. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_schema_hash(struct lyd_lyb_ctx *lybctx, const struct lysc_node *sparent, const struct lys_module *mod, + const struct lysc_node **snode) +{ + LY_ERR ret; + const struct lysc_node *sibling; + LYB_HASH hash[LYB_HASH_BITS - 1]; + uint32_t getnext_opts; + uint8_t hash_count; + + *snode = NULL; + + ret = lyb_read_hashes(lybctx->lybctx, hash, &hash_count); + LY_CHECK_RET(ret); + + if (!hash[0]) { + /* opaque node */ + return LY_SUCCESS; + } + + getnext_opts = lybctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; + + /* find our node with matching hashes */ + sibling = NULL; + while (1) { + if (!sparent && lybctx->ext) { + sibling = lys_getnext_ext(sibling, sparent, lybctx->ext, getnext_opts); + } else { + sibling = lys_getnext(sibling, sparent, mod ? mod->compiled : NULL, getnext_opts); + } + if (!sibling) { + break; + } + /* skip schema nodes from models not present during printing */ + if (((sibling->module->ctx != lybctx->lybctx->ctx) || lyb_has_schema_model(sibling, lybctx->lybctx->models)) && + lyb_is_schema_hash_match((struct lysc_node *)sibling, hash, hash_count)) { + /* match found */ + break; + } + } + + if (!sibling && (lybctx->parse_opts & LYD_PARSE_STRICT)) { + if (lybctx->ext) { + LOGVAL(lybctx->lybctx->ctx, LYVE_REFERENCE, "Failed to find matching hash for a node from \"%s\" extension instance node.", + lybctx->ext->def->name); + } else if (mod) { + LOGVAL(lybctx->lybctx->ctx, LYVE_REFERENCE, "Failed to find matching hash for a top-level node" + " from \"%s\".", mod->name); + } else { + LOGVAL(lybctx->lybctx->ctx, LYVE_REFERENCE, "Failed to find matching hash for a child node" + " of \"%s\".", sparent->name); + } + return LY_EVALID; + } else if (sibling && (ret = lyd_parser_check_schema((struct lyd_ctx *)lybctx, sibling))) { + return ret; + } + + *snode = sibling; + return LY_SUCCESS; +} + +/** + * @brief Parse schema node name of a nested extension data node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent. + * @param[in] mod_name Module name of the node. + * @param[out] snode Parsed found schema node of a nested extension. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_schema_nested_ext(struct lyd_lyb_ctx *lybctx, const struct lyd_node *parent, const char *mod_name, + const struct lysc_node **snode) +{ + LY_ERR rc = LY_SUCCESS, r; + char *name = NULL; + struct lysc_ext_instance *ext; + + assert(parent); + + /* read schema node name */ + LY_CHECK_GOTO(rc = lyb_read_string(&name, sizeof(uint16_t), lybctx->lybctx), cleanup); + + /* check for extension data */ + r = ly_nested_ext_schema(parent, NULL, mod_name, mod_name ? strlen(mod_name) : 0, LY_VALUE_JSON, NULL, name, + strlen(name), snode, &ext); + if (r == LY_ENOT) { + /* failed to parse */ + LOGERR(lybctx->lybctx->ctx, LY_EINVAL, "Failed to parse node \"%s\" as nested extension instance data.", name); + rc = LY_EINVAL; + goto cleanup; + } else if (r) { + /* error */ + rc = r; + goto cleanup; + } + + /* fill cached hashes in the module, it may be from a different context */ + lyb_cache_module_hash((*snode)->module); + +cleanup: + free(name); + return rc; +} + +/** + * @brief Read until the end of the current siblings. + * + * @param[in] lybctx LYB context. + */ +static void +lyb_skip_siblings(struct lylyb_ctx *lybctx) +{ + do { + /* first skip any meta information inside */ + ly_in_skip(lybctx->in, LYB_LAST_SIBLING(lybctx).inner_chunks * LYB_META_BYTES); + + /* then read data */ + lyb_read(NULL, LYB_LAST_SIBLING(lybctx).written, lybctx); + } while (LYB_LAST_SIBLING(lybctx).written); +} + +/** + * @brief Insert new node to @p parsed set. + * + * Also if needed, correct @p first_p. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling, must be set if @p first_p is not. + * @param[in,out] node Parsed node to insertion. + * @param[in,out] first_p First top-level sibling, must be set if @p parent is not. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static void +lyb_insert_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node *node, struct lyd_node **first_p, + struct ly_set *parsed) +{ + /* insert, keep first pointer correct */ + if (parent && (LYD_CTX(parent) != LYD_CTX(node))) { + lyplg_ext_insert(parent, node); + } else { + lyd_insert_node(parent, first_p, node, lybctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0); + } + while (!parent && (*first_p)->prev->next) { + *first_p = (*first_p)->prev; + } + + /* rememeber a successfully parsed node */ + if (parsed) { + ly_set_add(parsed, node, 1, NULL); + } +} + +/** + * @brief Finish parsing the opaq node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling, must be set if @p first_p is not. + * @param[in] flags Node flags to set. + * @param[in,out] attr Attributes to be attached. Finally set to NULL. + * @param[in,out] node Parsed opaq node to finish. + * @param[in,out] first_p First top-level sibling, must be set if @p parent is not. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static void +lyb_finish_opaq(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, uint32_t flags, struct lyd_attr **attr, + struct lyd_node **node, struct lyd_node **first_p, struct ly_set *parsed) +{ + struct lyd_attr *iter; + + /* set flags */ + (*node)->flags = flags; + + /* add attributes */ + assert(!(*node)->schema); + LY_LIST_FOR(*attr, iter) { + iter->parent = (struct lyd_node_opaq *)*node; + } + ((struct lyd_node_opaq *)*node)->attr = *attr; + *attr = NULL; + + lyb_insert_node(lybctx, parent, *node, first_p, parsed); + *node = NULL; +} + +/** + * @brief Finish parsing the node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling, must be set if @p first_p is not. + * @param[in] flags Node flags to set. + * @param[in,out] meta Metadata to be attached. Finally set to NULL. + * @param[in,out] node Parsed node to finish. + * @param[in,out] first_p First top-level sibling, must be set if @p parent is not. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static void +lyb_finish_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, uint32_t flags, struct lyd_meta **meta, + struct lyd_node **node, struct lyd_node **first_p, struct ly_set *parsed) +{ + struct lyd_meta *m; + + /* set flags */ + (*node)->flags = flags; + + /* add metadata */ + LY_LIST_FOR(*meta, m) { + m->parent = *node; + } + (*node)->meta = *meta; + *meta = NULL; + + /* insert into parent */ + lyb_insert_node(lybctx, parent, *node, first_p, parsed); + + if (!(lybctx->parse_opts & LYD_PARSE_ONLY)) { + /* store for ext instance node validation, if needed */ + (void)lyd_validate_node_ext(*node, &lybctx->ext_node); + } + + *node = NULL; +} + +/** + * @brief Parse header for non-opaq node. + * + * @param[in] lybctx LYB context. + * @param[in] sparent Schema parent node of the metadata. + * @param[out] flags Parsed node flags. + * @param[out] meta Parsed metadata of the node. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_header(struct lyd_lyb_ctx *lybctx, const struct lysc_node *sparent, uint32_t *flags, struct lyd_meta **meta) +{ + LY_ERR ret; + + /* create and read metadata */ + ret = lyb_parse_metadata(lybctx, sparent, meta); + LY_CHECK_RET(ret); + + /* read flags */ + lyb_read_number(flags, sizeof *flags, sizeof *flags, lybctx->lybctx); + + return ret; +} + +/** + * @brief Create term node and fill it with value. + * + * @param[in] lybctx LYB context. + * @param[in] snode Schema of the term node. + * @param[out] node Created term node. + * @return LY_ERR value. + */ +static LY_ERR +lyb_create_term(struct lyd_lyb_ctx *lybctx, const struct lysc_node *snode, struct lyd_node **node) +{ + LY_ERR ret; + ly_bool dynamic; + uint8_t *term_value; + uint64_t term_value_len; + + ret = lyb_read_term_value((struct lysc_node_leaf *)snode, &term_value, &term_value_len, lybctx->lybctx); + LY_CHECK_RET(ret); + + dynamic = 1; + /* create node */ + ret = lyd_parser_create_term((struct lyd_ctx *)lybctx, snode, + term_value, term_value_len, &dynamic, LY_VALUE_LYB, + NULL, LYD_HINT_DATA, node); + if (dynamic) { + free(term_value); + } + if (ret) { + lyd_free_tree(*node); + *node = NULL; + } + + return ret; +} + +/** + * @brief Validate inner node, autodelete default values nad create implicit nodes. + * + * @param[in,out] lybctx LYB context. + * @param[in] snode Schema of the inner node. + * @param[in] node Parsed inner node. + * @return LY_ERR value. + */ +static LY_ERR +lyb_validate_node_inner(struct lyd_lyb_ctx *lybctx, const struct lysc_node *snode, struct lyd_node *node) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t impl_opts; + + if (!(lybctx->parse_opts & LYD_PARSE_ONLY)) { + /* new node validation, autodelete CANNOT occur, all nodes are new */ + ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL); + LY_CHECK_RET(ret); + + /* add any missing default children */ + impl_opts = (lybctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0; + ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &lybctx->node_when, &lybctx->node_types, + &lybctx->ext_node, impl_opts, NULL); + LY_CHECK_RET(ret); + } + + return ret; +} + +/** + * @brief Parse opaq node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling. + * @param[in,out] first_p First top-level sibling. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_opaq(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL; + struct lyd_attr *attr = NULL; + char *value = NULL, *name = NULL, *prefix = NULL, *module_key = NULL; + ly_bool dynamic = 0; + LY_VALUE_FORMAT format = 0; + void *val_prefix_data = NULL; + const struct ly_ctx *ctx = lybctx->lybctx->ctx; + uint32_t flags; + + /* parse opaq node attributes */ + ret = lyb_parse_attributes(lybctx->lybctx, &attr); + LY_CHECK_GOTO(ret, cleanup); + + /* read flags */ + lyb_read_number(&flags, sizeof flags, sizeof flags, lybctx->lybctx); + + /* parse prefix */ + ret = lyb_read_string(&prefix, sizeof(uint16_t), lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* parse module key */ + ret = lyb_read_string(&module_key, sizeof(uint16_t), lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* parse name */ + ret = lyb_read_string(&name, sizeof(uint16_t), lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* parse value */ + ret = lyb_read_string(&value, sizeof(uint64_t), lybctx->lybctx); + LY_CHECK_ERR_GOTO(ret, ly_free_prefix_data(format, val_prefix_data), cleanup); + dynamic = 1; + + /* parse format */ + lyb_read_number(&format, sizeof format, 1, lybctx->lybctx); + + /* parse value prefixes */ + ret = lyb_parse_prefix_data(lybctx->lybctx, format, &val_prefix_data); + LY_CHECK_GOTO(ret, cleanup); + + if (!(lybctx->parse_opts & LYD_PARSE_OPAQ)) { + /* skip children */ + ret = lyb_read_start_siblings(lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + lyb_skip_siblings(lybctx->lybctx); + ret = lyb_read_stop_siblings(lybctx->lybctx); + LY_CHECK_GOTO(ret, cleanup); + goto cleanup; + } + + /* create node */ + ret = lyd_create_opaq(ctx, name, strlen(name), prefix, ly_strlen(prefix), module_key, ly_strlen(module_key), + value, strlen(value), &dynamic, format, val_prefix_data, 0, &node); + LY_CHECK_GOTO(ret, cleanup); + + /* process children */ + ret = lyb_parse_siblings(lybctx, node, NULL, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* register parsed opaq node */ + lyb_finish_opaq(lybctx, parent, flags, &attr, &node, first_p, parsed); + assert(!attr && !node); + +cleanup: + free(prefix); + free(module_key); + free(name); + if (dynamic) { + free(value); + } + lyd_free_attr_siblings(ctx, attr); + lyd_free_tree(node); + + return ret; +} + +/** + * @brief Parse anydata or anyxml node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling. + * @param[in] snode Schema of the node to be parsed. + * @param[in,out] first_p First top-level sibling. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_any(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node *snode, + struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL, *tree = NULL; + struct lyd_meta *meta = NULL; + LYD_ANYDATA_VALUETYPE value_type; + struct ly_in *in; + struct lyd_ctx *lydctx = NULL; + char *value = NULL; + uint32_t flags; + const struct ly_ctx *ctx = lybctx->lybctx->ctx; + + /* read necessary basic data */ + ret = lyb_parse_node_header(lybctx, snode, &flags, &meta); + LY_CHECK_GOTO(ret, error); + + /* parse value type */ + lyb_read_number(&value_type, sizeof value_type, sizeof value_type, lybctx->lybctx); + if ((value_type == LYD_ANYDATA_DATATREE) || ((snode->nodetype == LYS_ANYDATA) && (value_type != LYD_ANYDATA_LYB))) { + LOGINT(ctx); + ret = LY_EINT; + goto error; + } + + /* read anydata content */ + ret = lyb_read_string(&value, sizeof(uint64_t), lybctx->lybctx); + LY_CHECK_GOTO(ret, error); + + if (value_type == LYD_ANYDATA_LYB) { + /* parse LYB into a data tree */ + LY_CHECK_RET(ly_in_new_memory(value, &in)); + ret = lyd_parse_lyb(ctx, NULL, NULL, &tree, in, LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_STRICT, 0, + LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS, NULL, NULL, &lydctx); + ly_in_free(in, 0); + if (lydctx) { + lydctx->free(lydctx); + } + LY_CHECK_ERR_GOTO(ret, lyd_free_siblings(tree), error); + + /* use the parsed tree as the value */ + free(value); + value = (char *)tree; + value_type = LYD_ANYDATA_DATATREE; + } + + /* create the node */ + switch (value_type) { + case LYD_ANYDATA_DATATREE: + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + /* use the value directly */ + ret = lyd_create_any(snode, value, value_type, 1, &node); + LY_CHECK_GOTO(ret, error); + break; + default: + LOGINT(ctx); + ret = LY_EINT; + goto error; + } + + /* register parsed anydata node */ + lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + + return LY_SUCCESS; + +error: + free(value); + lyd_free_meta_siblings(meta); + lyd_free_tree(node); + return ret; +} + +/** + * @brief Parse inner node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling, must be set if @p first is not. + * @param[in] snode Schema of the node to be parsed. + * @param[in,out] first_p First top-level sibling, must be set if @p parent is not. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_inner(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node *snode, + struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *node = NULL; + struct lyd_meta *meta = NULL; + uint32_t flags; + + /* read necessary basic data */ + ret = lyb_parse_node_header(lybctx, snode, &flags, &meta); + LY_CHECK_GOTO(ret, error); + + /* create node */ + ret = lyd_create_inner(snode, &node); + LY_CHECK_GOTO(ret, error); + + /* process children */ + ret = lyb_parse_siblings(lybctx, node, NULL, NULL); + LY_CHECK_GOTO(ret, error); + + /* additional procedure for inner node */ + ret = lyb_validate_node_inner(lybctx, snode, node); + LY_CHECK_GOTO(ret, error); + + if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { + /* rememeber the RPC/action/notification */ + lybctx->op_node = node; + } + + /* register parsed node */ + lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + + return LY_SUCCESS; + +error: + lyd_free_meta_siblings(meta); + lyd_free_tree(node); + return ret; +} + +/** + * @brief Parse leaf node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling. + * @param[in] snode Schema of the node to be parsed. + * @param[in,out] first_p First top-level sibling. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_leaf(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node *snode, + struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL; + struct lyd_meta *meta = NULL; + uint32_t flags; + + /* read necessary basic data */ + ret = lyb_parse_node_header(lybctx, snode, &flags, &meta); + LY_CHECK_GOTO(ret, error); + + /* read value of term node and create it */ + ret = lyb_create_term(lybctx, snode, &node); + LY_CHECK_GOTO(ret, error); + + lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + + return LY_SUCCESS; + +error: + lyd_free_meta_siblings(meta); + lyd_free_tree(node); + return ret; +} + +/** + * @brief Parse all leaflist nodes which belong to same schema. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling. + * @param[in] snode Schema of the nodes to be parsed. + * @param[in,out] first_p First top-level sibling. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_leaflist(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node *snode, + struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + + /* register a new sibling */ + ret = lyb_read_start_siblings(lybctx->lybctx); + LY_CHECK_RET(ret); + + /* process all siblings */ + while (LYB_LAST_SIBLING(lybctx->lybctx).written) { + ret = lyb_parse_node_leaf(lybctx, parent, snode, first_p, parsed); + LY_CHECK_RET(ret); + } + + /* end the sibling */ + ret = lyb_read_stop_siblings(lybctx->lybctx); + LY_CHECK_RET(ret); + + return ret; +} + +/** + * @brief Parse all list nodes which belong to same schema. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling. + * @param[in] snode Schema of the nodes to be parsed. + * @param[in,out] first_p First top-level sibling. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node_list(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node *snode, + struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret; + struct lyd_node *node = NULL; + struct lyd_meta *meta = NULL; + uint32_t flags; + + /* register a new sibling */ + ret = lyb_read_start_siblings(lybctx->lybctx); + LY_CHECK_RET(ret); + + while (LYB_LAST_SIBLING(lybctx->lybctx).written) { + /* read necessary basic data */ + ret = lyb_parse_node_header(lybctx, snode, &flags, &meta); + LY_CHECK_GOTO(ret, error); + + /* create list node */ + ret = lyd_create_inner(snode, &node); + LY_CHECK_GOTO(ret, error); + + /* process children */ + ret = lyb_parse_siblings(lybctx, node, NULL, NULL); + LY_CHECK_GOTO(ret, error); + + /* additional procedure for inner node */ + ret = lyb_validate_node_inner(lybctx, snode, node); + LY_CHECK_GOTO(ret, error); + + if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { + /* rememeber the RPC/action/notification */ + lybctx->op_node = node; + } + + /* register parsed list node */ + lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + } + + /* end the sibling */ + ret = lyb_read_stop_siblings(lybctx->lybctx); + LY_CHECK_RET(ret); + + return LY_SUCCESS; + +error: + lyd_free_meta_siblings(meta); + lyd_free_tree(node); + return ret; +} + +/** + * @brief Parse a node. + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling, must be set if @p first_p is not. + * @param[in,out] first_p First top-level sibling, must be set if @p parent is not. + * @param[in,out] parsed Set of all successfully parsed nodes to add to. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_set *parsed) +{ + LY_ERR ret; + const struct lysc_node *snode; + const struct lys_module *mod; + enum lylyb_node_type lyb_type; + char *mod_name = NULL, mod_rev[LY_REV_SIZE]; + + /* read node type */ + lyb_read_number(&lyb_type, sizeof lyb_type, 1, lybctx->lybctx); + + switch (lyb_type) { + case LYB_NODE_TOP: + /* top-level, read module name */ + LY_CHECK_GOTO(ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_opts, 0, &mod), cleanup); + + /* read hash, find the schema node starting from mod */ + LY_CHECK_GOTO(ret = lyb_parse_schema_hash(lybctx, NULL, mod, &snode), cleanup); + break; + case LYB_NODE_CHILD: + case LYB_NODE_OPAQ: + /* read hash, find the schema node starting from parent schema, if any */ + LY_CHECK_GOTO(ret = lyb_parse_schema_hash(lybctx, parent ? parent->schema : NULL, NULL, &snode), cleanup); + break; + case LYB_NODE_EXT: + /* ext, read module name */ + LY_CHECK_GOTO(ret = lyb_read_model(lybctx->lybctx, &mod_name, mod_rev, NULL), cleanup); + + /* read schema node name, find the nexted ext schema node */ + LY_CHECK_GOTO(ret = lyb_parse_schema_nested_ext(lybctx, parent, mod_name, &snode), cleanup); + break; + } + + if (!snode) { + ret = lyb_parse_node_opaq(lybctx, parent, first_p, parsed); + } else if (snode->nodetype & LYS_LEAFLIST) { + ret = lyb_parse_node_leaflist(lybctx, parent, snode, first_p, parsed); + } else if (snode->nodetype == LYS_LIST) { + ret = lyb_parse_node_list(lybctx, parent, snode, first_p, parsed); + } else if (snode->nodetype & LYD_NODE_ANY) { + ret = lyb_parse_node_any(lybctx, parent, snode, first_p, parsed); + } else if (snode->nodetype & LYD_NODE_INNER) { + ret = lyb_parse_node_inner(lybctx, parent, snode, first_p, parsed); + } else { + ret = lyb_parse_node_leaf(lybctx, parent, snode, first_p, parsed); + } + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + free(mod_name); + return ret; +} + +/** + * @brief Parse siblings (@ref lyb_print_siblings()). + * + * @param[in] lybctx LYB context. + * @param[in] parent Data parent of the sibling, must be set if @p first_p is not. + * @param[in,out] first_p First top-level sibling, must be set if @p parent is not. + * @param[out] parsed Set of all successfully parsed nodes. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_siblings(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_set *parsed) +{ + ly_bool top_level; + + top_level = !LY_ARRAY_COUNT(lybctx->lybctx->siblings); + + /* register a new siblings */ + LY_CHECK_RET(lyb_read_start_siblings(lybctx->lybctx)); + + while (LYB_LAST_SIBLING(lybctx->lybctx).written) { + LY_CHECK_RET(lyb_parse_node(lybctx, parent, first_p, parsed)); + + if (top_level && !(lybctx->int_opts & LYD_INTOPT_WITH_SIBLINGS)) { + break; + } + } + + /* end the siblings */ + LY_CHECK_RET(lyb_read_stop_siblings(lybctx->lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Parse used YANG data models. + * + * @param[in] lybctx LYB context. + * @param[in] parse_options Flag with options for parsing. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_data_models(struct lylyb_ctx *lybctx, uint32_t parse_options) +{ + LY_ERR ret; + uint32_t count; + LY_ARRAY_COUNT_TYPE u; + + /* read model count */ + lyb_read_number(&count, sizeof count, 2, lybctx); + + if (count) { + LY_ARRAY_CREATE_RET(lybctx->ctx, lybctx->models, count, LY_EMEM); + + /* read modules */ + for (u = 0; u < count; ++u) { + ret = lyb_parse_model(lybctx, parse_options, 1, &lybctx->models[u]); + LY_CHECK_RET(ret); + LY_ARRAY_INCREMENT(lybctx->models); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse LYB magic number. + * + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_magic_number(struct lylyb_ctx *lybctx) +{ + char magic_byte = 0; + + lyb_read((uint8_t *)&magic_byte, 1, lybctx); + if (magic_byte != 'l') { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid first magic number byte \"0x%02x\".", magic_byte); + return LY_EINVAL; + } + + lyb_read((uint8_t *)&magic_byte, 1, lybctx); + if (magic_byte != 'y') { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid second magic number byte \"0x%02x\".", magic_byte); + return LY_EINVAL; + } + + lyb_read((uint8_t *)&magic_byte, 1, lybctx); + if (magic_byte != 'b') { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid third magic number byte \"0x%02x\".", magic_byte); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse LYB header. + * + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_parse_header(struct lylyb_ctx *lybctx) +{ + uint8_t byte = 0; + + /* version, future flags */ + lyb_read((uint8_t *)&byte, sizeof byte, lybctx); + + if ((byte & LYB_VERSION_MASK) != LYB_VERSION_NUM) { + LOGERR(lybctx->ctx, LY_EINVAL, "Invalid LYB format version \"0x%02x\", expected \"0x%02x\".", + byte & LYB_VERSION_MASK, LYB_VERSION_NUM); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_parse_lyb(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 rc = LY_SUCCESS; + struct lyd_lyb_ctx *lybctx; + + assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK)); + assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK)); + + LY_CHECK_ARG_RET(ctx, !(parse_opts & LYD_PARSE_SUBTREE), LY_EINVAL); + + if (subtree_sibling) { + *subtree_sibling = 0; + } + + lybctx = calloc(1, sizeof *lybctx); + LY_CHECK_ERR_RET(!lybctx, LOGMEM(ctx), LY_EMEM); + lybctx->lybctx = calloc(1, sizeof *lybctx->lybctx); + LY_CHECK_ERR_GOTO(!lybctx->lybctx, LOGMEM(ctx); rc = LY_EMEM, cleanup); + + lybctx->lybctx->in = in; + lybctx->lybctx->ctx = ctx; + lybctx->parse_opts = parse_opts; + lybctx->val_opts = val_opts; + lybctx->int_opts = int_opts; + lybctx->free = lyd_lyb_ctx_free; + lybctx->ext = ext; + + /* find the operation node if it exists already */ + LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lybctx->op_node), cleanup); + + /* read magic number */ + rc = lyb_parse_magic_number(lybctx->lybctx); + LY_CHECK_GOTO(rc, cleanup); + + /* read header */ + rc = lyb_parse_header(lybctx->lybctx); + LY_CHECK_GOTO(rc, cleanup); + + /* read used models */ + rc = lyb_parse_data_models(lybctx->lybctx, lybctx->parse_opts); + LY_CHECK_GOTO(rc, cleanup); + + /* read sibling(s) */ + rc = lyb_parse_siblings(lybctx, parent, first_p, parsed); + LY_CHECK_GOTO(rc, cleanup); + + if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lybctx->lybctx->in->current[0]) { + LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); + rc = LY_EVALID; + goto cleanup; + } + if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lybctx->op_node) { + LOGVAL(ctx, LYVE_DATA, "Missing the operation node."); + rc = LY_EVALID; + goto cleanup; + } + + /* read the last zero, parsing finished */ + ly_in_skip(lybctx->lybctx->in, 1); + +cleanup: + /* there should be no unres stored if validation should be skipped */ + assert(!(parse_opts & LYD_PARSE_ONLY) || (!lybctx->node_types.count && !lybctx->meta_types.count && + !lybctx->node_when.count)); + + if (rc) { + lyd_lyb_ctx_free((struct lyd_ctx *)lybctx); + } else { + *lydctx_p = (struct lyd_ctx *)lybctx; + } + return rc; +} + +LIBYANG_API_DEF int +lyd_lyb_data_length(const char *data) +{ + LY_ERR ret = LY_SUCCESS; + struct lylyb_ctx *lybctx; + uint32_t count, feat_count, len = 0, i, j; + uint8_t buf[LYB_SIZE_MAX]; + uint8_t zero[LYB_SIZE_BYTES] = {0}; + + if (!data) { + return -1; + } + + lybctx = calloc(1, sizeof *lybctx); + LY_CHECK_ERR_RET(!lybctx, LOGMEM(NULL), LY_EMEM); + ret = ly_in_new_memory(data, &lybctx->in); + LY_CHECK_GOTO(ret, cleanup); + + /* read magic number */ + ret = lyb_parse_magic_number(lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* read header */ + ret = lyb_parse_header(lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* read model count */ + lyb_read_number(&count, sizeof count, 2, lybctx); + + /* read all models */ + for (i = 0; i < count; ++i) { + /* module name length */ + lyb_read_number(&len, sizeof len, 2, lybctx); + + /* model name */ + lyb_read(buf, len, lybctx); + + /* revision */ + lyb_read(buf, 2, lybctx); + + /* enabled feature count */ + lyb_read_number(&feat_count, sizeof feat_count, 2, lybctx); + + /* enabled features */ + for (j = 0; j < feat_count; ++j) { + /* feature name length */ + lyb_read_number(&len, sizeof len, 2, lybctx); + + /* feature name */ + lyb_read(buf, len, lybctx); + } + } + + if (memcmp(zero, lybctx->in->current, LYB_SIZE_BYTES)) { + /* register a new sibling */ + ret = lyb_read_start_siblings(lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* skip it */ + lyb_skip_siblings(lybctx); + + /* sibling finished */ + ret = lyb_read_stop_siblings(lybctx); + LY_CHECK_GOTO(ret, cleanup); + } else { + lyb_read(NULL, LYB_SIZE_BYTES, lybctx); + } + + /* read the last zero, parsing finished */ + ly_in_skip(lybctx->in, 1); + +cleanup: + count = lybctx->in->current - lybctx->in->start; + + ly_in_free(lybctx->in, 0); + lylyb_ctx_free(lybctx); + + return ret ? -1 : (int)count; +} diff --git a/src/parser_schema.h b/src/parser_schema.h new file mode 100644 index 0000000..f8df16d --- /dev/null +++ b/src/parser_schema.h @@ -0,0 +1,179 @@ +/** + * @file parser_schema.h + * @author Radek Krejci + * @brief Schema parsers for libyang + * + * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef LY_PARSER_SCHEMA_H_ +#define LY_PARSER_SCHEMA_H_ + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_in; +struct lys_module; + +/** + * @page howtoSchemaParsers Parsing YANG Modules + * + * YANG module parsers allow to read YANG module from a specific format. libyang supports the following module formats: + * + * - YANG + * + * Basic YANG schemas format described in [RFC 6020](http://tools.ietf.org/html/rfc6020) and + * [RFC 7951](http://tools.ietf.org/html/rfc7951) (so both YANG 1.0 and YANG 1.1 versions are supported). + * + * - YIN + * + * Alternative XML-based format to YANG - YANG Independent Notation. The details can be found in + * [RFC 6020](http://tools.ietf.org/html/rfc6020#section-11) and + * [RFC 7951](http://tools.ietf.org/html/rfc7951#section-13). + * + * When the [context](@ref howtoContext) is created, it already contains the following YANG modules, which + * are implemented internally by libyang: + * - ietf-yang-metadata@2016-08-05 + * - yang@2020-06-17 + * - ietf-inet-types@2013-07-15 + * - ietf-yang-types@2013-07-15 + * - ietf-datastores@2018-02-14 + * - ietf-yang-library@2019-01-04 + * + * The `yang` module is the libyang's internal module to provide namespace and definitions of for various YANG + * attributes described in [RFC 7951](https://tools.ietf.org/html/rfc6243) (such as `insert` attribute for + * edit-config's data). + * + * Other modules can be added to the context manually with the functions listed below. Besides them, + * it is also possible to use ::ly_ctx_load_module() which tries to find the required module automatically - using + * ::ly_module_imp_clb or automatic search in working directory and in the context's search directories. For details, see + * [how the context works](@ref howtoContext). + * + * YANG modules are loaded in two steps. First, the input YANG/YIN data are parsed into \b lysp_* structures that reflect + * the structure of the input module and submodule(s). Mostly just syntax checks are done, no reference or type checking is + * performed in this step. If the module is supposed to be implemented, not just imported by another module, the second step + * is to compile it. The compiled tree may significantly differ from the source (parsed) tree structure. All the references + * are resolved, groupings are instantiated, types are resolved (and compiled by joining all the relevant restrictions + * when derived from another types) and many other syntactical checks are done. + * + * There is the main parsing function ::lys_parse() working with the libyang [input handler](@ref howtoInput). However, + * to simplify some of the use-cases, it is also possible to use other functions accepting input data from various sources. + * + * Functions List + * -------------- + * - ::lys_parse() + * - ::lys_parse_mem() + * - ::lys_parse_fd() + * - ::lys_parse_path() + * + * - ::lys_search_localfile() + * - ::ly_ctx_set_module_imp_clb() + * - ::ly_ctx_load_module() + */ + +/** + * @addtogroup schematree + * @{ + */ + +/** + * @brief Schema input formats accepted by libyang [parser functions](@ref howtoSchemaParsers). + */ +typedef enum { + LYS_IN_UNKNOWN = 0, /**< unknown format, used as return value in case of error */ + LYS_IN_YANG = 1, /**< YANG schema input format */ + LYS_IN_YIN = 3 /**< YIN schema input format */ +} LYS_INFORMAT; + +/** + * @brief Load a schema into the specified context. + * + * @param[in] ctx libyang context where to process the data model. + * @param[in] in The input handle to provide the dumped data model in the specified format. + * @param[in] format Format of the schema to parse. Can be 0 to try to detect format from the input handler. + * @param[in] features Array of features to enable ended with NULL. If NULL, no features are enabled. + * @param[out] module Optional parsed module. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_parse(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, const char **features, + struct lys_module **module); + +/** + * @brief Load a schema into the specified context. + * + * This function is considered for a simple use, if you have a complex use-case, + * consider use of ::lys_parse() with a standalone input handler. + * + * @param[in] ctx libyang context where to process the data model. + * @param[in] data The string containing the dumped data model in the specified format. + * @param[in] format Format of the schema to parse. + * @param[out] module Optional parsed module. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_parse_mem(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, struct lys_module **module); + +/** + * @brief Read a schema from file descriptor into the specified context. + * + * \note Current implementation supports only reading data from standard (disk) file, not from sockets, pipes, etc. + * + * This function is considered for a simple use, if you have a complex use-case, + * consider use of ::lys_parse() with a standalone input handler. + * + * @param[in] ctx libyang context where to process the data model. + * @param[in] fd File descriptor of a regular file (e.g. sockets are not supported) containing the schema + * in the specified format. + * @param[in] format Format of the schema to parse. + * @param[out] module Optional parsed module. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, struct lys_module **module); + +/** + * @brief Load a schema into the specified context from a file. + * + * This function is considered for a simple use, if you have a complex use-case, + * consider use of ::lys_parse() with a standalone input handler. + * + * @param[in] ctx libyang context where to process the data model. + * @param[in] path Path to the file with the model in the specified format. + * @param[in] format Format of the schema to parse. + * @param[out] module Optional parsed module. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format, struct lys_module **module); + +/** + * @brief Search for the schema file in the specified searchpaths. + * + * @param[in] searchpaths NULL-terminated array of paths to be searched (recursively). Current working + * directory is searched automatically (but non-recursively if not in the provided list). Caller can use + * result of the ::ly_ctx_get_searchdirs(). + * @param[in] cwd Flag to implicitly search also in the current working directory (non-recursively). + * @param[in] name Name of the schema to find. + * @param[in] revision Revision of the schema to find. If NULL, the newest found schema filepath is returned. + * @param[out] localfile Mandatory output variable containing absolute path of the found schema. If no schema + * complying the provided restriction is found, NULL is set. + * @param[out] format Optional output variable containing expected format of the schema document according to the + * file suffix. + * @return LY_ERR value (LY_SUCCESS is returned even if the file is not found, then the *localfile is NULL). + */ +LIBYANG_API_DECL LY_ERR lys_search_localfile(const char * const *searchpaths, ly_bool cwd, const char *name, const char *revision, + char **localfile, LYS_INFORMAT *format); + +/** @} schematree */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PARSER_SCHEMA_H_ */ diff --git a/src/parser_xml.c b/src/parser_xml.c new file mode 100644 index 0000000..5a929da --- /dev/null +++ b/src/parser_xml.c @@ -0,0 +1,1816 @@ +/** + * @file parser_xml.c + * @author Radek Krejci + * @author Michal Vasko + * @brief XML data parser for libyang + * + * Copyright (c) 2019 - 2022 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 + +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "in_internal.h" +#include "log.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "plugins_exts.h" +#include "plugins_internal.h" +#include "schema_compile_node.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "validation.h" +#include "xml.h" + +void +lyd_xml_ctx_free(struct lyd_ctx *lydctx) +{ + struct lyd_xml_ctx *ctx = (struct lyd_xml_ctx *)lydctx; + + lyd_ctx_free(lydctx); + lyxml_ctx_free(ctx->xmlctx); + free(ctx); +} + +/** + * @brief Parse and create XML metadata. + * + * @param[in] lydctx XML data parser context. + * @param[in] sparent Schema node of the parent. + * @param[out] meta List of created metadata instances. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_metadata(struct lyd_xml_ctx *lydctx, const struct lysc_node *sparent, struct lyd_meta **meta) +{ + LY_ERR ret = LY_SUCCESS; + const struct lyxml_ns *ns; + struct lys_module *mod; + const char *name; + size_t name_len; + LY_ARRAY_COUNT_TYPE u; + ly_bool filter_attrs = 0; + struct lyxml_ctx *xmlctx = lydctx->xmlctx; + + *meta = NULL; + + /* check for NETCONF filter unqualified attributes */ + if (!strcmp(sparent->module->name, "notifications")) { + /* ancient module that does not even use the extension */ + filter_attrs = 1; + } else { + LY_ARRAY_FOR(sparent->exts, u) { + if (!strcmp(sparent->exts[u].def->name, "get-filter-element-attributes") && + !strcmp(sparent->exts[u].def->module->name, "ietf-netconf")) { + filter_attrs = 1; + break; + } + } + } + + while (xmlctx->status == LYXML_ATTRIBUTE) { + if (!xmlctx->prefix_len) { + /* in XML all attributes must be prefixed except NETCONF filter ones marked by an extension */ + if (filter_attrs && (!ly_strncmp("type", xmlctx->name, xmlctx->name_len) || + !ly_strncmp("select", xmlctx->name, xmlctx->name_len))) { + mod = ly_ctx_get_module_implemented(xmlctx->ctx, "ietf-netconf"); + if (!mod) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, + "Missing (or not implemented) YANG module \"ietf-netconf\" for special filter attributes."); + ret = LY_ENOTFOUND; + goto cleanup; + } + goto create_meta; + } + + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Missing mandatory prefix for XML metadata \"%.*s\".", + (int)xmlctx->name_len, xmlctx->name); + ret = LY_EVALID; + goto cleanup; + } + + /* skip attr */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + assert(xmlctx->status == LYXML_ATTR_CONTENT); + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + continue; + } + + /* get namespace of the attribute to find its annotation definition */ + ns = lyxml_ns_get(&xmlctx->ns, xmlctx->prefix, xmlctx->prefix_len); + if (!ns) { + /* unknown namespace, XML error */ + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", (int)xmlctx->prefix_len, xmlctx->prefix); + ret = LY_ENOTFOUND; + goto cleanup; + } + + /* get the module with metadata definition */ + mod = ly_ctx_get_module_implemented_ns(xmlctx->ctx, ns->uri); + if (!mod) { + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, + "Unknown (or not implemented) YANG module with namespace \"%s\" for metadata \"%.*s%s%.*s\".", + ns->uri, (int)xmlctx->prefix_len, xmlctx->prefix, xmlctx->prefix_len ? ":" : "", + (int)xmlctx->name_len, xmlctx->name); + ret = LY_ENOTFOUND; + goto cleanup; + } + + /* skip attr */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + assert(xmlctx->status == LYXML_ATTR_CONTENT); + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + continue; + } + +create_meta: + /* remember meta name and get its content */ + name = xmlctx->name; + name_len = xmlctx->name_len; + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + assert(xmlctx->status == LYXML_ATTR_CONTENT); + + /* create metadata */ + ret = lyd_parser_create_meta((struct lyd_ctx *)lydctx, NULL, meta, mod, name, name_len, xmlctx->value, + xmlctx->value_len, &xmlctx->dynamic, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA, sparent); + LY_CHECK_GOTO(ret, cleanup); + + /* next attribute */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + } + +cleanup: + if (ret) { + lyd_free_meta_siblings(*meta); + *meta = NULL; + } + return ret; +} + +static LY_ERR +lydxml_attrs(struct lyxml_ctx *xmlctx, struct lyd_attr **attr) +{ + LY_ERR ret = LY_SUCCESS; + const struct lyxml_ns *ns; + void *val_prefix_data; + LY_VALUE_FORMAT format; + struct lyd_attr *attr2; + const char *name, *prefix; + size_t name_len, prefix_len; + + assert(attr); + *attr = NULL; + + while (xmlctx->status == LYXML_ATTRIBUTE) { + if (*attr) { + attr2 = *attr; + } else { + attr2 = NULL; + } + + /* remember attr prefix, name, and get its content */ + prefix = xmlctx->prefix; + prefix_len = xmlctx->prefix_len; + name = xmlctx->name; + name_len = xmlctx->name_len; + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + assert(xmlctx->status == LYXML_ATTR_CONTENT); + + /* handle special "xml" attribute prefix */ + if ((prefix_len == 3) && !strncmp(prefix, "xml", 3)) { + name = prefix; + name_len += 1 + prefix_len; + prefix = NULL; + prefix_len = 0; + } + + /* find namespace of the attribute, if any */ + ns = NULL; + if (prefix_len) { + ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); + if (!ns) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", (int)prefix_len, prefix); + ret = LY_EVALID; + goto cleanup; + } + } + + /* get value prefixes */ + val_prefix_data = NULL; + LY_CHECK_GOTO(ret = ly_store_prefix_data(xmlctx->ctx, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, + &xmlctx->ns, &format, &val_prefix_data), cleanup); + + /* attr2 is always changed to the created attribute */ + ret = lyd_create_attr(NULL, &attr2, xmlctx->ctx, name, name_len, prefix, prefix_len, ns ? ns->uri : NULL, + ns ? strlen(ns->uri) : 0, xmlctx->value, xmlctx->value_len, &xmlctx->dynamic, format, val_prefix_data, + LYD_HINT_DATA); + LY_CHECK_GOTO(ret, cleanup); + + if (!*attr) { + *attr = attr2; + } + + /* next attribute */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + } + +cleanup: + if (ret) { + lyd_free_attr_siblings(xmlctx->ctx, *attr); + *attr = NULL; + } + return ret; +} + +static LY_ERR +lydxml_check_list(struct lyxml_ctx *xmlctx, const struct lysc_node *list) +{ + LY_ERR ret = LY_SUCCESS, r; + enum LYXML_PARSER_STATUS next; + struct ly_set key_set = {0}; + const struct lysc_node *snode; + uint32_t i, parents_count; + + assert(list && (list->nodetype == LYS_LIST)); + + /* get all keys into a set (keys do not have if-features or anything) */ + snode = NULL; + while ((snode = lys_getnext(snode, list, NULL, 0)) && (snode->flags & LYS_KEY)) { + ret = ly_set_add(&key_set, (void *)snode, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + /* remember parent count */ + parents_count = xmlctx->elements.count; + + while (xmlctx->status == LYXML_ELEMENT) { + /* find key definition */ + for (i = 0; i < key_set.count; ++i) { + snode = (const struct lysc_node *)key_set.objs[i]; + if (!ly_strncmp(snode->name, xmlctx->name, xmlctx->name_len)) { + break; + } + } + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + + /* skip attributes */ + while (xmlctx->status == LYXML_ATTRIBUTE) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + assert(xmlctx->status == LYXML_ATTR_CONTENT); + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + } + + assert(xmlctx->status == LYXML_ELEM_CONTENT); + if (i < key_set.count) { + /* validate the value */ + r = lys_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns); + if (!r) { + /* key with a valid value, remove from the set */ + ly_set_rm_index(&key_set, i, NULL); + } + } + + /* parser next */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + + /* skip any children, resursively */ + while (xmlctx->status == LYXML_ELEMENT) { + while (parents_count < xmlctx->elements.count) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + } + assert(xmlctx->status == LYXML_ELEM_CLOSE); + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + } + + /* parser next, but do not parse closing element of the list because it would remove its namespaces */ + assert(xmlctx->status == LYXML_ELEM_CLOSE); + LY_CHECK_GOTO(ret = lyxml_ctx_peek(xmlctx, &next), cleanup); + if (next != LYXML_ELEM_CLOSE) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), cleanup); + } + } + + if (key_set.count) { + /* some keys are missing/did not validate */ + ret = LY_ENOT; + } + +cleanup: + ly_set_erase(&key_set, NULL); + return ret; +} + +/** + * @brief Skip an element with all its descendants. + * + * @param[in] xmlctx XML parser context. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_data_skip(struct lyxml_ctx *xmlctx) +{ + uint32_t parents_count; + + /* remember current number of parents */ + parents_count = xmlctx->elements.count; + assert(parents_count); + + /* skip after the content */ + while (xmlctx->status != LYXML_ELEM_CONTENT) { + LY_CHECK_RET(lyxml_ctx_next(xmlctx)); + } + LY_CHECK_RET(lyxml_ctx_next(xmlctx)); + + /* skip all children elements, recursively, if any */ + while (parents_count <= xmlctx->elements.count) { + LY_CHECK_RET(lyxml_ctx_next(xmlctx)); + } + + /* close element */ + assert(xmlctx->status == LYXML_ELEM_CLOSE); + LY_CHECK_RET(lyxml_ctx_next(xmlctx)); + + return LY_SUCCESS; +} + +/** + * @brief Check that the current element can be parsed as a data node. + * + * @param[in] lydctx XML data parser context. + * @param[in,out] snode Found schema node, set to NULL if data node cannot be created. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_data_check_opaq(struct lyd_xml_ctx *lydctx, const struct lysc_node **snode) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxml_ctx *xmlctx = lydctx->xmlctx, pxmlctx; + + if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + /* only checks specific to opaque nodes */ + return LY_SUCCESS; + } + + if (!((*snode)->nodetype & (LYD_NODE_TERM | LYD_NODE_INNER))) { + /* nothing to check */ + return LY_SUCCESS; + } + + assert(xmlctx->elements.count); + + /* backup parser */ + LY_CHECK_RET(lyxml_ctx_backup(xmlctx, &pxmlctx)); + + /* skip attributes */ + while (xmlctx->status == LYXML_ATTRIBUTE) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), restore); + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), restore); + } + + if ((*snode)->nodetype & LYD_NODE_TERM) { + /* value may not be valid in which case we parse it as an opaque node */ + if (lys_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns)) { + LOGVRB("Parsing opaque term node \"%s\" with invalid value \"%.*s\".", (*snode)->name, xmlctx->value_len, + xmlctx->value); + *snode = NULL; + } + } else if ((*snode)->nodetype == LYS_LIST) { + /* skip content */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), restore); + + if (lydxml_check_list(xmlctx, *snode)) { + /* invalid list, parse as opaque if it missing/has invalid some keys */ + LOGVRB("Parsing opaque list node \"%s\" with missing/invalid keys.", (*snode)->name); + *snode = NULL; + } + } else { + /* if there is a non-WS value, it cannot be parsed as an inner node */ + assert(xmlctx->status == LYXML_ELEM_CONTENT); + if (!xmlctx->ws_only) { + *snode = NULL; + } + } + +restore: + /* restore parser */ + lyxml_ctx_restore(xmlctx, &pxmlctx); + return ret; +} + +/** + * @brief Get sensible data hints for an opaque node. + * + * @param[in] name Node name. + * @param[in] name_len Length of @p name. + * @param[in] value Node value. + * @param[in] value_len Length of @p value. + * @param[in] first Node first sibling. + * @param[in] ns Node module namespace, NULL for no namespace. + * @param[out] hints Data hints to use. + * @param[out] anchor Anchor to insert after in case of a list. + */ +static void +lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size_t value_len, struct lyd_node *first, + const char *ns, uint32_t *hints, struct lyd_node **anchor) +{ + struct lyd_node_opaq *opaq; + char *ptr; + long num; + + *hints = 0; + *anchor = NULL; + + if (!value_len) { + /* no value */ + *hints |= LYD_VALHINT_EMPTY; + } else if (!strncmp(value, "true", value_len) || !strncmp(value, "false", value_len)) { + /* boolean value */ + *hints |= LYD_VALHINT_BOOLEAN; + } else { + num = strtol(value, &ptr, 10); + if ((unsigned)(ptr - value) == value_len) { + /* number value */ + *hints |= LYD_VALHINT_DECNUM; + if ((num < INT32_MIN) || (num > UINT32_MAX)) { + /* large number */ + *hints |= LYD_VALHINT_NUM64; + } + } else { + /* string value */ + *hints |= LYD_VALHINT_STRING; + } + } + + if (!first) { + return; + } + + /* search backwards to find the last instance */ + do { + first = first->prev; + if (first->schema) { + continue; + } + + opaq = (struct lyd_node_opaq *)first; + assert(opaq->format == LY_VALUE_XML); + if (!ly_strncmp(opaq->name.name, name, name_len) && + ((ns && !strcmp(opaq->name.module_ns, ns)) || (!ns && !opaq->name.module_ns))) { + if (opaq->value && opaq->value[0]) { + /* leaf-list nodes */ + opaq->hints |= LYD_NODEHINT_LEAFLIST; + *hints |= LYD_NODEHINT_LEAFLIST; + } else { + /* list nodes */ + opaq->hints |= LYD_NODEHINT_LIST; + *hints |= LYD_NODEHINT_LIST; + } + *anchor = first; + break; + } + } while (first->prev->next); +} + +/** + * @brief Get schema node for the current element. + * + * @param[in] lydctx XML data parser context. + * @param[in] parent Parsed parent data node, if any. + * @param[in] prefix Element prefix, if any. + * @param[in] prefix_len Length of @p prefix. + * @param[in] name Element name. + * @param[in] name_len Length of @p name. + * @param[out] snode Found schema node, NULL if no suitable was found. + * @param[out] ext Extension instance that provided @p snode, if any. + * @return LY_SUCCESS on success; + * @return LY_ERR on error. + */ +static LY_ERR +lydxml_subtree_snode(struct lyd_xml_ctx *lydctx, const struct lyd_node *parent, const char *prefix, size_t prefix_len, + const char *name, size_t name_len, const struct lysc_node **snode, struct lysc_ext_instance **ext) +{ + LY_ERR r; + struct lyxml_ctx *xmlctx; + const struct ly_ctx *ctx; + const struct lyxml_ns *ns; + struct lys_module *mod; + uint32_t getnext_opts; + + xmlctx = lydctx->xmlctx; + ctx = xmlctx->ctx; + getnext_opts = lydctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0; + + *snode = NULL; + *ext = NULL; + + /* get current namespace */ + ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); + if (!ns) { + if (lydctx->int_opts & LYD_INTOPT_ANY) { + goto unknown_module; + } + + if (prefix_len) { + LOGVAL(ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", (int)prefix_len, prefix); + } else { + LOGVAL(ctx, LYVE_REFERENCE, "Missing XML namespace."); + } + return LY_EVALID; + } + + /* get the element module, use parent context if possible because of extensions */ + mod = ly_ctx_get_module_implemented_ns(parent ? LYD_CTX(parent) : ctx, ns->uri); + if (!mod) { + /* check for extension data */ + r = ly_nested_ext_schema(parent, NULL, prefix, prefix_len, LY_VALUE_XML, &lydctx->xmlctx->ns, name, name_len, + snode, ext); + if (r != LY_ENOT) { + /* success or error */ + return r; + } + +unknown_module: + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(ctx, LYVE_REFERENCE, "No module with namespace \"%s\" in the context.", ns->uri); + return LY_EVALID; + } + return LY_SUCCESS; + } + + /* get the schema node */ + if (mod) { + if (!parent && lydctx->ext) { + *snode = lysc_ext_find_node(lydctx->ext, mod, name, name_len, 0, getnext_opts); + } else { + *snode = lys_find_child(parent ? parent->schema : NULL, 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_XML, &lydctx->xmlctx->ns, name, + name_len, snode, ext); + if (r != LY_ENOT) { + /* success or error */ + return r; + } + + /* unknown data node */ + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + if (parent) { + LOGVAL(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(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(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the %s extension instance.", + (int)name_len, name, lydctx->ext->def->name); + } + } else { + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.", + (int)name_len, name, mod->name); + } + return LY_EVALID; + } + return LY_SUCCESS; + } else { + /* check that schema node is valid and can be used */ + LY_CHECK_RET(lyd_parser_check_schema((struct lyd_ctx *)lydctx, *snode)); + LY_CHECK_RET(lydxml_data_check_opaq(lydctx, snode)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse XML subtree. + * + * @param[in] lydctx XML YANG data parser context. + * @param[in,out] parent Parent node where the children are inserted. NULL in case of parsing top-level elements. + * @param[in,out] first_p Pointer to the first (@p parent or top-level) child. In case there were already some siblings, + * this may point to a previously existing node. + * @param[in,out] parsed Optional set to add all the parsed siblings into. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR ret = LY_SUCCESS; + const char *prefix, *name, *ns_uri; + size_t prefix_len, name_len; + struct lyxml_ctx *xmlctx; + const struct ly_ctx *ctx; + const struct lyxml_ns *ns; + struct lyd_meta *meta = NULL; + struct lyd_attr *attr = NULL; + const struct lysc_node *snode; + struct lysc_ext_instance *ext; + uint32_t prev_parse_opts, orig_parse_opts, prev_int_opts, hints; + struct lyd_node *node = NULL, *anchor, *insert_anchor = NULL; + void *val_prefix_data = NULL; + LY_VALUE_FORMAT format; + ly_bool parse_subtree; + char *val; + + assert(parent || first_p); + + xmlctx = lydctx->xmlctx; + ctx = xmlctx->ctx; + + parse_subtree = lydctx->parse_opts & LYD_PARSE_SUBTREE ? 1 : 0; + /* all descendants should be parsed */ + lydctx->parse_opts &= ~LYD_PARSE_SUBTREE; + orig_parse_opts = lydctx->parse_opts; + + assert(xmlctx->status == LYXML_ELEMENT); + + /* remember element prefix and name */ + prefix = xmlctx->prefix; + prefix_len = xmlctx->prefix_len; + name = xmlctx->name; + name_len = xmlctx->name_len; + + /* parser next */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + + /* get the schema node */ + LY_CHECK_GOTO(ret = lydxml_subtree_snode(lydctx, parent, prefix, prefix_len, name, name_len, &snode, &ext), error); + + if (!snode && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + LOGVRB("Skipping parsing of unknown node \"%.*s\".", name_len, name); + + /* skip element with children */ + LY_CHECK_GOTO(ret = lydxml_data_skip(xmlctx), error); + return LY_SUCCESS; + } + + /* create metadata/attributes */ + if (xmlctx->status == LYXML_ATTRIBUTE) { + if (snode) { + ret = lydxml_metadata(lydctx, snode, &meta); + LY_CHECK_GOTO(ret, error); + } else { + assert(lydctx->parse_opts & LYD_PARSE_OPAQ); + ret = lydxml_attrs(xmlctx, &attr); + LY_CHECK_GOTO(ret, error); + } + } + + assert(xmlctx->status == LYXML_ELEM_CONTENT); + if (!snode) { + assert(lydctx->parse_opts & LYD_PARSE_OPAQ); + + if (xmlctx->ws_only) { + /* ignore WS-only value */ + if (xmlctx->dynamic) { + free((char *)xmlctx->value); + } + xmlctx->dynamic = 0; + xmlctx->value = ""; + xmlctx->value_len = 0; + format = LY_VALUE_XML; + } else { + /* get value prefixes */ + ret = ly_store_prefix_data(xmlctx->ctx, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, + &xmlctx->ns, &format, &val_prefix_data); + LY_CHECK_GOTO(ret, error); + } + + /* get NS again, it may have been backed up and restored */ + ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); + ns_uri = ns ? ns->uri : NULL; + + /* get best-effort node hints */ + lydxml_get_hints_opaq(name, name_len, xmlctx->value, xmlctx->value_len, parent ? lyd_child(parent) : *first_p, + ns_uri, &hints, &insert_anchor); + + /* create node */ + ret = lyd_create_opaq(ctx, name, name_len, prefix, prefix_len, ns_uri, ns_uri ? strlen(ns_uri) : 0, + xmlctx->value, xmlctx->value_len, &xmlctx->dynamic, format, val_prefix_data, hints, &node); + LY_CHECK_GOTO(ret, error); + + /* parser next */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + + /* process children */ + while (xmlctx->status == LYXML_ELEMENT) { + ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL); + LY_CHECK_GOTO(ret, error); + } + } else if (snode->nodetype & LYD_NODE_TERM) { + /* create node */ + LY_CHECK_GOTO(ret = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, xmlctx->value, xmlctx->value_len, + &xmlctx->dynamic, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA, &node), error); + LOG_LOCSET(snode, node, NULL, NULL); + + if (parent && (node->schema->flags & LYS_KEY)) { + /* check the key order, the anchor must never be a key */ + anchor = lyd_insert_get_next_anchor(lyd_child(parent), node); + if (anchor && anchor->schema && (anchor->schema->flags & LYS_KEY)) { + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(ctx, LYVE_DATA, "Invalid position of the key \"%s\" in a list.", node->schema->name); + ret = LY_EVALID; + goto error; + } else { + LOGWRN(ctx, "Invalid position of the key \"%s\" in a list.", node->schema->name); + } + } + } + + /* parser next */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + + /* no children expected */ + if (xmlctx->status == LYXML_ELEMENT) { + LOGVAL(ctx, LYVE_SYNTAX, "Child element \"%.*s\" inside a terminal node \"%s\" found.", + (int)xmlctx->name_len, xmlctx->name, snode->name); + ret = LY_EVALID; + goto error; + } + } else if (snode->nodetype & LYD_NODE_INNER) { + if (!xmlctx->ws_only) { + /* value in inner node */ + LOGVAL(ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an inner node \"%s\" found.", + (int)xmlctx->value_len, xmlctx->value, snode->name); + ret = LY_EVALID; + goto error; + } + + /* create node */ + ret = lyd_create_inner(snode, &node); + LY_CHECK_GOTO(ret, error); + + LOG_LOCSET(snode, node, NULL, NULL); + + /* parser next */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + + prev_parse_opts = lydctx->parse_opts; + if (ext) { + /* only parse these extension data and validate afterwards */ + lydctx->parse_opts |= LYD_PARSE_ONLY; + } + + /* process children */ + while (xmlctx->status == LYXML_ELEMENT) { + ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL); + LY_CHECK_GOTO(ret, error); + } + + /* restore options */ + lydctx->parse_opts = prev_parse_opts; + + if (snode->nodetype == LYS_LIST) { + /* check all keys exist */ + LY_CHECK_GOTO(ret = lyd_parse_check_keys(node), error); + } + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { + /* new node validation, autodelete CANNOT occur, all nodes are new */ + ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL); + LY_CHECK_GOTO(ret, error); + + /* add any missing default children */ + ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &lydctx->node_when, &lydctx->node_types, + &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL); + LY_CHECK_GOTO(ret, error); + } + + if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { + /* rememeber the RPC/action/notification */ + lydctx->op_node = node; + } + } else if (snode->nodetype & LYD_NODE_ANY) { + if ((snode->nodetype == LYS_ANYDATA) && !xmlctx->ws_only) { + /* value in anydata node, we expect a tree */ + LOGVAL(ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an anydata node \"%s\" found.", + (int)xmlctx->value_len < 20 ? xmlctx->value_len : 20, xmlctx->value, snode->name); + ret = LY_EVALID; + goto error; + } + + if (!xmlctx->ws_only) { + /* use an arbitrary text value for anyxml */ + val = strndup(xmlctx->value, xmlctx->value_len); + LY_CHECK_ERR_GOTO(!val, LOGMEM(xmlctx->ctx); ret = LY_EMEM, error); + + /* parser next */ + LY_CHECK_ERR_GOTO(ret = lyxml_ctx_next(xmlctx), free(val), error); + + /* create node */ + ret = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, &node); + LY_CHECK_ERR_GOTO(ret, free(val), error); + } else { + /* parser next */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + + /* update options so that generic data can be parsed */ + prev_parse_opts = lydctx->parse_opts; + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); + prev_int_opts = lydctx->int_opts; + lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; + + /* parse any data tree */ + anchor = NULL; + while (xmlctx->status == LYXML_ELEMENT) { + ret = lydxml_subtree_r(lydctx, NULL, &anchor, NULL); + if (ret) { + lyd_free_siblings(anchor); + break; + } + } + + /* restore options */ + lydctx->parse_opts = prev_parse_opts; + lydctx->int_opts = prev_int_opts; + + LY_CHECK_GOTO(ret, error); + + /* create node */ + ret = lyd_create_any(snode, anchor, LYD_ANYDATA_DATATREE, 1, &node); + LY_CHECK_GOTO(ret, error); + } + } + assert(node); + + if (snode) { + /* add/correct flags */ + LY_CHECK_GOTO(ret = lyd_parse_set_data_flags(node, &meta, (struct lyd_ctx *)lydctx, ext), error); + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { + /* store for ext instance node validation, if needed */ + LY_CHECK_GOTO(ret = lyd_validate_node_ext(node, &lydctx->ext_node), error); + } + } + + /* parser next */ + assert(xmlctx->status == LYXML_ELEM_CLOSE); + if (!parse_subtree) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + } + + /* add metadata/attributes */ + if (snode) { + lyd_insert_meta(node, meta, 0); + } else { + lyd_insert_attr(node, attr); + } + + /* insert, keep first pointer correct */ + if (insert_anchor) { + lyd_insert_after(insert_anchor, node); + } else if (ext) { + LY_CHECK_GOTO(ret = lyplg_ext_insert(parent, node), error); + } else { + lyd_insert_node(parent, first_p, node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0); + } + while (!parent && (*first_p)->prev->next) { + *first_p = (*first_p)->prev; + } + + /* rememeber a successfully parsed node */ + if (parsed) { + ly_set_add(parsed, node, 1, NULL); + } + + lydctx->parse_opts = orig_parse_opts; + LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0); + return LY_SUCCESS; + +error: + lydctx->parse_opts = orig_parse_opts; + LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0); + lyd_free_meta_siblings(meta); + lyd_free_attr_siblings(ctx, attr); + lyd_free_tree(node); + return ret; +} + +/** + * @brief Parse a specific XML element into an opaque node. + * + * @param[in] xmlctx XML parser context. + * @param[in] name Name of the element. + * @param[in] uri URI of the element. + * @param[in] value Whether a value is expected in the element. + * @param[out] evnp Parsed envelope (opaque node). + * @return LY_SUCCESS on success. + * @return LY_ENOT if the specified element did not match. + * @return LY_ERR value on error. + */ +static LY_ERR +lydxml_envelope(struct lyxml_ctx *xmlctx, const char *name, const char *uri, ly_bool value, struct lyd_node **envp) +{ + LY_ERR rc = LY_SUCCESS; + const struct lyxml_ns *ns; + struct lyd_attr *attr = NULL; + const char *prefix; + size_t prefix_len; + + assert(xmlctx->status == LYXML_ELEMENT); + if (ly_strncmp(name, xmlctx->name, xmlctx->name_len)) { + /* not the expected element */ + return LY_ENOT; + } + + prefix = xmlctx->prefix; + prefix_len = xmlctx->prefix_len; + ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); + if (!ns) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", (int)prefix_len, prefix); + return LY_EVALID; + } else if (strcmp(ns->uri, uri)) { + /* different namespace */ + return LY_ENOT; + } + + LY_CHECK_RET(lyxml_ctx_next(xmlctx)); + + /* create attributes */ + if (xmlctx->status == LYXML_ATTRIBUTE) { + LY_CHECK_RET(lydxml_attrs(xmlctx, &attr)); + } + + assert(xmlctx->status == LYXML_ELEM_CONTENT); + if (!value && !xmlctx->ws_only) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected value \"%.*s\" in the \"%s\" element.", + (int)xmlctx->value_len, xmlctx->value, name); + rc = LY_EVALID; + goto cleanup; + } + + /* create node */ + rc = lyd_create_opaq(xmlctx->ctx, name, strlen(name), prefix, prefix_len, uri, strlen(uri), xmlctx->value, + xmlctx->ws_only ? 0 : xmlctx->value_len, NULL, LY_VALUE_XML, NULL, 0, envp); + LY_CHECK_GOTO(rc, cleanup); + + /* assign atributes */ + ((struct lyd_node_opaq *)(*envp))->attr = attr; + attr = NULL; + + /* parser next element */ + LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup); + +cleanup: + lyd_free_attr_siblings(xmlctx->ctx, attr); + if (rc) { + lyd_free_tree(*envp); + *envp = NULL; + } + return rc; +} + +LY_ERR +lyd_parse_xml(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 rc = LY_SUCCESS; + struct lyd_xml_ctx *lydctx; + ly_bool parsed_data_nodes = 0; + enum LYXML_PARSER_STATUS status; + + assert(ctx && in && lydctx_p); + assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK)); + assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK)); + + /* init context */ + lydctx = calloc(1, sizeof *lydctx); + LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM); + LY_CHECK_GOTO(rc = lyxml_ctx_new(ctx, in, &lydctx->xmlctx), cleanup); + lydctx->parse_opts = parse_opts; + lydctx->val_opts = val_opts; + lydctx->int_opts = int_opts; + lydctx->free = lyd_xml_ctx_free; + lydctx->ext = ext; + + /* find the operation node if it exists already */ + LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); + + /* parse XML data */ + while (lydctx->xmlctx->status == LYXML_ELEMENT) { + LY_CHECK_GOTO(rc = lydxml_subtree_r(lydctx, parent, first_p, parsed), cleanup); + parsed_data_nodes = 1; + + if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { + break; + } + } + + /* check final state */ + if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) { + LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); + rc = LY_EVALID; + goto 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."); + rc = LY_EVALID; + goto cleanup; + } + + if (!parsed_data_nodes) { + /* no data nodes were parsed */ + lydctx->op_node = NULL; + } + + if (parse_opts & LYD_PARSE_SUBTREE) { + /* check for a sibling element */ + assert(subtree_sibling); + if (!lyxml_ctx_peek(lydctx->xmlctx, &status) && (status == LYXML_ELEMENT)) { + *subtree_sibling = 1; + } else { + *subtree_sibling = 0; + } + } + +cleanup: + /* there should be no unres stored if validation should be skipped */ + assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count && + !lydctx->node_when.count)); + + if (rc) { + lyd_xml_ctx_free((struct lyd_ctx *)lydctx); + } else { + *lydctx_p = (struct lyd_ctx *)lydctx; + + /* the XML context is no more needed, freeing it also stops logging line numbers which would be confusing now */ + lyxml_ctx_free(lydctx->xmlctx); + lydctx->xmlctx = NULL; + } + return rc; +} + +/** + * @brief Parse all expected non-data XML elements of a NETCONF rpc message. + * + * @param[in] xmlctx XML parser context. + * @param[out] evnp Parsed envelope(s) (opaque node). + * @param[out] int_opts Internal options for parsing the rest of YANG data. + * @param[out] close_elem Number of parsed opened elements that need to be closed. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +static LY_ERR +lydxml_env_netconf_rpc(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem) +{ + LY_ERR rc = LY_SUCCESS, r; + struct lyd_node *child; + + assert(envp && !*envp); + + if (xmlctx->status != LYXML_ELEMENT) { + /* nothing to parse */ + assert(xmlctx->status == LYXML_END); + goto cleanup; + } + + /* parse "rpc" */ + r = lydxml_envelope(xmlctx, "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* parse "action", if any */ + r = lydxml_envelope(xmlctx, "action", "urn:ietf:params:xml:ns:yang:1", 0, &child); + if (r == LY_SUCCESS) { + /* insert */ + lyd_insert_node(*envp, NULL, child, 0); + + /* NETCONF action */ + *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_ACTION; + *close_elem = 2; + } else if (r == LY_ENOT) { + /* NETCONF RPC */ + *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_RPC; + *close_elem = 1; + } else { + rc = r; + goto cleanup; + } + +cleanup: + if (rc) { + lyd_free_tree(*envp); + *envp = NULL; + } + return rc; +} + +/** + * @brief Validate eventTime date-and-time value. + * + * @param[in] node Opaque eventTime node. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +static LY_ERR +lydxml_env_netconf_eventtime_validate(const struct lyd_node *node) +{ + LY_ERR rc = LY_SUCCESS; + struct ly_ctx *ctx = (struct ly_ctx *)LYD_CTX(node); + struct lysc_ctx cctx; + const struct lys_module *mod; + LY_ARRAY_COUNT_TYPE u; + struct ly_err_item *err = NULL; + struct lysp_type *type_p = NULL; + struct lysc_pattern **patterns = NULL; + const char *value; + + LYSC_CTX_INIT_CTX(cctx, ctx); + + /* get date-and-time parsed type */ + mod = ly_ctx_get_module_latest(ctx, "ietf-yang-types"); + assert(mod); + LY_ARRAY_FOR(mod->parsed->typedefs, u) { + if (!strcmp(mod->parsed->typedefs[u].name, "date-and-time")) { + type_p = &mod->parsed->typedefs[u].type; + break; + } + } + assert(type_p); + + /* compile patterns */ + assert(type_p->patterns); + LY_CHECK_GOTO(rc = lys_compile_type_patterns(&cctx, type_p->patterns, NULL, &patterns), cleanup); + + /* validate */ + value = lyd_get_value(node); + rc = lyplg_type_validate_patterns(patterns, value, strlen(value), &err); + +cleanup: + FREE_ARRAY(&cctx.free_ctx, patterns, lysc_pattern_free); + if (rc && err) { + LOGVAL_ERRITEM(ctx, err); + ly_err_free(err); + LOGVAL(ctx, LYVE_DATA, "Invalid \"eventTime\" in the notification."); + } + return rc; +} + +/** + * @brief Parse all expected non-data XML elements of a NETCONF notification message. + * + * @param[in] xmlctx XML parser context. + * @param[out] evnp Parsed envelope(s) (opaque node). + * @param[out] int_opts Internal options for parsing the rest of YANG data. + * @param[out] close_elem Number of parsed opened elements that need to be closed. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +static LY_ERR +lydxml_env_netconf_notif(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem) +{ + LY_ERR rc = LY_SUCCESS, r; + struct lyd_node *child; + + assert(envp && !*envp); + + if (xmlctx->status != LYXML_ELEMENT) { + /* nothing to parse */ + assert(xmlctx->status == LYXML_END); + goto cleanup; + } + + /* parse "notification" */ + r = lydxml_envelope(xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0", 0, envp); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* parse "eventTime" */ + r = lydxml_envelope(xmlctx, "eventTime", "urn:ietf:params:xml:ns:netconf:notification:1.0", 1, &child); + if (r == LY_ENOT) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"eventTime\".", + (int)xmlctx->name_len, xmlctx->name); + r = LY_EVALID; + } + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* insert */ + lyd_insert_node(*envp, NULL, child, 0); + + /* validate value */ + r = lydxml_env_netconf_eventtime_validate(child); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* finish child parsing */ + if (xmlctx->status != LYXML_ELEM_CLOSE) { + assert(xmlctx->status == LYXML_ELEMENT); + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"eventTime\".", + (int)xmlctx->name_len, xmlctx->name); + rc = LY_EVALID; + goto cleanup; + } + LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup); + + /* NETCONF notification */ + *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_NOTIF; + *close_elem = 1; + +cleanup: + if (rc) { + lyd_free_tree(*envp); + *envp = NULL; + } + return rc; +} + +/** + * @brief Parse an XML element as an opaque node subtree. + * + * @param[in] xmlctx XML parser context. + * @param[in] parent Parent to append nodes to. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_opaq_r(struct lyxml_ctx *xmlctx, struct lyd_node *parent) +{ + LY_ERR rc = LY_SUCCESS; + const struct lyxml_ns *ns; + struct lyd_attr *attr = NULL; + struct lyd_node *child = NULL; + const char *name, *prefix; + size_t name_len, prefix_len; + + assert(xmlctx->status == LYXML_ELEMENT); + + name = xmlctx->name; + name_len = xmlctx->name_len; + prefix = xmlctx->prefix; + prefix_len = xmlctx->prefix_len; + ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); + if (!ns) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unknown XML prefix \"%.*s\".", (int)prefix_len, prefix); + return LY_EVALID; + } + + LY_CHECK_RET(lyxml_ctx_next(xmlctx)); + + /* create attributes */ + if (xmlctx->status == LYXML_ATTRIBUTE) { + LY_CHECK_RET(lydxml_attrs(xmlctx, &attr)); + } + + /* create node */ + assert(xmlctx->status == LYXML_ELEM_CONTENT); + rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, ns->uri, strlen(ns->uri), xmlctx->value, + xmlctx->ws_only ? 0 : xmlctx->value_len, NULL, LY_VALUE_XML, NULL, 0, &child); + LY_CHECK_GOTO(rc, cleanup); + + /* assign atributes */ + ((struct lyd_node_opaq *)child)->attr = attr; + attr = NULL; + + /* parser next element */ + LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup); + + /* parse all the descendants */ + while (xmlctx->status == LYXML_ELEMENT) { + rc = lydxml_opaq_r(xmlctx, child); + LY_CHECK_GOTO(rc, cleanup); + } + + /* insert */ + lyd_insert_node(parent, NULL, child, 1); + +cleanup: + lyd_free_attr_siblings(xmlctx->ctx, attr); + if (rc) { + lyd_free_tree(child); + } + return rc; +} + +/** + * @brief Parse all expected non-data XML elements of the error-info element in NETCONF rpc-reply message. + * + * @param[in] xmlctx XML parser context. + * @param[in] parent Parent to append nodes to. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_env_netconf_rpc_reply_error_info(struct lyxml_ctx *xmlctx, struct lyd_node *parent) +{ + LY_ERR r; + struct lyd_node *child, *iter; + ly_bool no_dup; + + /* there must be some child */ + if (xmlctx->status == LYXML_ELEM_CLOSE) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Missing child elements of \"error-info\"."); + return LY_EVALID; + } + + while (xmlctx->status == LYXML_ELEMENT) { + child = NULL; + + /* + * session-id + */ + r = lydxml_envelope(xmlctx, "session-id", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * bad-attribute + */ + r = lydxml_envelope(xmlctx, "bad-attribute", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * bad-element + */ + r = lydxml_envelope(xmlctx, "bad-element", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * bad-namespace + */ + r = lydxml_envelope(xmlctx, "bad-namespace", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + if (r == LY_ENOT) { + assert(xmlctx->status == LYXML_ELEMENT); + + /* custom elements, parse all the siblings */ + while (xmlctx->status == LYXML_ELEMENT) { + LY_CHECK_GOTO(r = lydxml_opaq_r(xmlctx, parent), error); + LY_CHECK_GOTO(r = lyxml_ctx_next(xmlctx), error); + } + continue; + } + +check_child: + /* check for duplicates */ + if (no_dup) { + LY_LIST_FOR(lyd_child(parent), iter) { + if ((((struct lyd_node_opaq *)iter)->name.name == ((struct lyd_node_opaq *)child)->name.name) && + (((struct lyd_node_opaq *)iter)->name.module_ns == ((struct lyd_node_opaq *)child)->name.module_ns)) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Duplicate element \"%s\" in \"error-info\".", + ((struct lyd_node_opaq *)child)->name.name); + r = LY_EVALID; + goto error; + } + } + } + + /* finish child parsing */ + if (xmlctx->status != LYXML_ELEM_CLOSE) { + assert(xmlctx->status == LYXML_ELEMENT); + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"error-info\".", + (int)xmlctx->name_len, xmlctx->name); + r = LY_EVALID; + goto error; + } + LY_CHECK_GOTO(r = lyxml_ctx_next(xmlctx), error); + + /* insert */ + lyd_insert_node(parent, NULL, child, 1); + } + + return LY_SUCCESS; + +error: + lyd_free_tree(child); + return r; +} + +/** + * @brief Parse all expected non-data XML elements of the rpc-error element in NETCONF rpc-reply message. + * + * @param[in] xmlctx XML parser context. + * @param[in] parent Parent to append nodes to. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_env_netconf_rpc_reply_error(struct lyxml_ctx *xmlctx, struct lyd_node *parent) +{ + LY_ERR r; + struct lyd_node *child, *iter; + const char *val; + ly_bool no_dup; + + /* there must be some child */ + if (xmlctx->status == LYXML_ELEM_CLOSE) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Missing child elements of \"rpc-error\"."); + return LY_EVALID; + } + + while (xmlctx->status == LYXML_ELEMENT) { + child = NULL; + + /* + * error-type + */ + r = lydxml_envelope(xmlctx, "error-type", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + val = ((struct lyd_node_opaq *)child)->value; + if (strcmp(val, "transport") && strcmp(val, "rpc") && strcmp(val, "protocol") && strcmp(val, "application")) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid value \"%s\" of element \"%s\".", val, + ((struct lyd_node_opaq *)child)->name.name); + r = LY_EVALID; + goto error; + } + + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * error-tag + */ + r = lydxml_envelope(xmlctx, "error-tag", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + val = ((struct lyd_node_opaq *)child)->value; + if (strcmp(val, "in-use") && strcmp(val, "invalid-value") && strcmp(val, "too-big") && + strcmp(val, "missing-attribute") && strcmp(val, "bad-attribute") && + strcmp(val, "unknown-attribute") && strcmp(val, "missing-element") && strcmp(val, "bad-element") && + strcmp(val, "unknown-element") && strcmp(val, "unknown-namespace") && strcmp(val, "access-denied") && + strcmp(val, "lock-denied") && strcmp(val, "resource-denied") && strcmp(val, "rollback-failed") && + strcmp(val, "data-exists") && strcmp(val, "data-missing") && strcmp(val, "operation-not-supported") && + strcmp(val, "operation-failed") && strcmp(val, "malformed-message")) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid value \"%s\" of element \"%s\".", val, + ((struct lyd_node_opaq *)child)->name.name); + r = LY_EVALID; + goto error; + } + + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * error-severity + */ + r = lydxml_envelope(xmlctx, "error-severity", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + val = ((struct lyd_node_opaq *)child)->value; + if (strcmp(val, "error") && strcmp(val, "warning")) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Invalid value \"%s\" of element \"%s\".", val, + ((struct lyd_node_opaq *)child)->name.name); + r = LY_EVALID; + goto error; + } + + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * error-app-tag + */ + r = lydxml_envelope(xmlctx, "error-app-tag", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * error-path + */ + r = lydxml_envelope(xmlctx, "error-path", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* + * error-message + */ + r = lydxml_envelope(xmlctx, "error-message", "urn:ietf:params:xml:ns:netconf:base:1.0", 1, &child); + if (r == LY_SUCCESS) { + no_dup = 1; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + /* error-info */ + r = lydxml_envelope(xmlctx, "error-info", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, &child); + if (r == LY_SUCCESS) { + /* parse all the descendants */ + LY_CHECK_GOTO(r = lydxml_env_netconf_rpc_reply_error_info(xmlctx, child), error); + + no_dup = 0; + goto check_child; + } else if (r != LY_ENOT) { + goto error; + } + + if (r == LY_ENOT) { + assert(xmlctx->status == LYXML_ELEMENT); + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"rpc-error\".", + (int)xmlctx->name_len, xmlctx->name); + r = LY_EVALID; + goto error; + } + +check_child: + /* check for duplicates */ + if (no_dup) { + LY_LIST_FOR(lyd_child(parent), iter) { + if ((((struct lyd_node_opaq *)iter)->name.name == ((struct lyd_node_opaq *)child)->name.name) && + (((struct lyd_node_opaq *)iter)->name.module_ns == ((struct lyd_node_opaq *)child)->name.module_ns)) { + LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Duplicate element \"%s\" in \"rpc-error\".", + ((struct lyd_node_opaq *)child)->name.name); + r = LY_EVALID; + goto error; + } + } + } + + /* finish child parsing */ + if (xmlctx->status != LYXML_ELEM_CLOSE) { + assert(xmlctx->status == LYXML_ELEMENT); + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"rpc-error\".", + (int)xmlctx->name_len, xmlctx->name); + r = LY_EVALID; + goto error; + } + LY_CHECK_GOTO(r = lyxml_ctx_next(xmlctx), error); + + /* insert */ + lyd_insert_node(parent, NULL, child, 1); + } + + return LY_SUCCESS; + +error: + lyd_free_tree(child); + return r; +} + +/** + * @brief Parse all expected non-data XML elements of a NETCONF rpc-reply message. + * + * @param[in] xmlctx XML parser context. + * @param[out] evnp Parsed envelope(s) (opaque node). + * @param[out] int_opts Internal options for parsing the rest of YANG data. + * @param[out] close_elem Number of parsed opened elements that need to be closed. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +static LY_ERR +lydxml_env_netconf_reply(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem) +{ + LY_ERR rc = LY_SUCCESS, r; + struct lyd_node *child = NULL; + const char *parsed_elem = NULL; + + assert(envp && !*envp); + + if (xmlctx->status != LYXML_ELEMENT) { + /* nothing to parse */ + assert(xmlctx->status == LYXML_END); + goto cleanup; + } + + /* parse "rpc-reply" */ + r = lydxml_envelope(xmlctx, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* there must be some child */ + if (xmlctx->status == LYXML_ELEM_CLOSE) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Missing child elements of \"rpc-reply\"."); + rc = LY_EVALID; + goto cleanup; + } + + /* try to parse "ok" */ + r = lydxml_envelope(xmlctx, "ok", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, &child); + if (r == LY_SUCCESS) { + /* insert */ + lyd_insert_node(*envp, NULL, child, 1); + + /* finish child parsing */ + if (xmlctx->status != LYXML_ELEM_CLOSE) { + assert(xmlctx->status == LYXML_ELEMENT); + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"ok\".", + (int)xmlctx->name_len, xmlctx->name); + rc = LY_EVALID; + goto cleanup; + } + LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup); + + /* success */ + parsed_elem = "ok"; + goto finish; + } else if (r != LY_ENOT) { + rc = r; + goto cleanup; + } + + /* try to parse all "rpc-error" elements */ + while (xmlctx->status == LYXML_ELEMENT) { + r = lydxml_envelope(xmlctx, "rpc-error", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, &child); + if (r == LY_ENOT) { + break; + } else if (r) { + rc = r; + goto cleanup; + } + + /* insert */ + lyd_insert_node(*envp, NULL, child, 1); + + /* parse all children of "rpc-error" */ + LY_CHECK_GOTO(rc = lydxml_env_netconf_rpc_reply_error(xmlctx, child), cleanup); + + /* finish child parsing */ + assert(xmlctx->status == LYXML_ELEM_CLOSE); + LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup); + + parsed_elem = "rpc-error"; + } + +finish: + if (parsed_elem) { + /* NETCONF rpc-reply with no data */ + if (xmlctx->status != LYXML_ELEM_CLOSE) { + assert(xmlctx->status == LYXML_ELEMENT); + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected sibling element \"%.*s\" of \"%s\".", + (int)xmlctx->name_len, xmlctx->name, parsed_elem); + rc = LY_EVALID; + goto cleanup; + } + } + + /* NETCONF rpc-reply */ + *int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_REPLY; + *close_elem = 1; + +cleanup: + if (rc) { + lyd_free_tree(*envp); + *envp = NULL; + } + return rc; +} + +LY_ERR +lyd_parse_xml_netconf(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, enum lyd_type data_type, + struct lyd_node **envp, struct ly_set *parsed, struct lyd_ctx **lydctx_p) +{ + LY_ERR rc = LY_SUCCESS; + struct lyd_xml_ctx *lydctx; + uint32_t i, int_opts = 0, close_elem = 0; + ly_bool parsed_data_nodes = 0; + + assert(ctx && in && lydctx_p); + assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK)); + assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK)); + + assert((data_type == LYD_TYPE_RPC_NETCONF) || (data_type == LYD_TYPE_NOTIF_NETCONF) || + (data_type == LYD_TYPE_REPLY_NETCONF)); + assert(!(parse_opts & LYD_PARSE_SUBTREE)); + + /* init context */ + lydctx = calloc(1, sizeof *lydctx); + LY_CHECK_ERR_RET(!lydctx, LOGMEM(ctx), LY_EMEM); + LY_CHECK_GOTO(rc = lyxml_ctx_new(ctx, in, &lydctx->xmlctx), cleanup); + lydctx->parse_opts = parse_opts; + lydctx->val_opts = val_opts; + lydctx->free = lyd_xml_ctx_free; + lydctx->ext = ext; + + switch (data_type) { + case LYD_TYPE_RPC_NETCONF: + assert(!parent); + rc = lydxml_env_netconf_rpc(lydctx->xmlctx, envp, &int_opts, &close_elem); + if (rc == LY_ENOT) { + LOGVAL(ctx, LYVE_DATA, "Missing NETCONF envelope or in incorrect namespace."); + } + LY_CHECK_GOTO(rc, cleanup); + break; + case LYD_TYPE_NOTIF_NETCONF: + assert(!parent); + rc = lydxml_env_netconf_notif(lydctx->xmlctx, envp, &int_opts, &close_elem); + if (rc == LY_ENOT) { + LOGVAL(ctx, LYVE_DATA, "Missing NETCONF envelope or in incorrect namespace."); + } + LY_CHECK_GOTO(rc, cleanup); + break; + case LYD_TYPE_REPLY_NETCONF: + assert(parent); + rc = lydxml_env_netconf_reply(lydctx->xmlctx, envp, &int_opts, &close_elem); + if (rc == LY_ENOT) { + LOGVAL(ctx, LYVE_DATA, "Missing NETCONF envelope or in incorrect namespace."); + } + LY_CHECK_GOTO(rc, cleanup); + break; + default: + LOGINT(ctx); + rc = LY_EINT; + goto cleanup; + } + + lydctx->int_opts = int_opts; + + /* find the operation node if it exists already */ + LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); + + /* parse XML data */ + while (lydctx->xmlctx->status == LYXML_ELEMENT) { + LY_CHECK_GOTO(rc = lydxml_subtree_r(lydctx, parent, first_p, parsed), cleanup); + parsed_data_nodes = 1; + + if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { + break; + } + } + + /* close all opened elements */ + for (i = 0; i < close_elem; ++i) { + if (lydctx->xmlctx->status != LYXML_ELEM_CLOSE) { + assert(lydctx->xmlctx->status == LYXML_ELEMENT); + LOGVAL(lydctx->xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\".", + (int)lydctx->xmlctx->name_len, lydctx->xmlctx->name); + rc = LY_EVALID; + goto cleanup; + } + + LY_CHECK_GOTO(rc = lyxml_ctx_next(lydctx->xmlctx), cleanup); + } + + /* check final state */ + if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) { + LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); + rc = LY_EVALID; + goto 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."); + rc = LY_EVALID; + goto cleanup; + } + + if (!parsed_data_nodes) { + /* no data nodes were parsed */ + lydctx->op_node = NULL; + } + +cleanup: + /* there should be no unres stored if validation should be skipped */ + assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count && + !lydctx->node_when.count)); + + if (rc) { + lyd_xml_ctx_free((struct lyd_ctx *)lydctx); + } else { + *lydctx_p = (struct lyd_ctx *)lydctx; + + /* the XML context is no more needed, freeing it also stops logging line numbers which would be confusing now */ + lyxml_ctx_free(lydctx->xmlctx); + lydctx->xmlctx = NULL; + } + return rc; +} diff --git a/src/parser_yang.c b/src/parser_yang.c new file mode 100644 index 0000000..dd84480 --- /dev/null +++ b/src/parser_yang.c @@ -0,0 +1,4827 @@ +/** + * @file parser_yang.c + * @author Michal Vasko + * @brief YANG parser + * + * Copyright (c) 2018 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ +#include "parser_internal.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "context.h" +#include "dict.h" +#include "in_internal.h" +#include "log.h" +#include "parser_schema.h" +#include "path.h" +#include "set.h" +#include "tree.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_free.h" +#include "tree_schema_internal.h" + +struct lys_glob_unres; + +/** + * @brief Insert WORD into the libyang context's dictionary and store as TARGET. + * + * @param[in] CTX yang parser context to access libyang context. + * @param[in] BUF buffer in case the word is not a constant and can be inserted directly (zero-copy) + * @param[out] TARGET variable where to store the pointer to the inserted value. + * @param[in] WORD string to store. + * @param[in] LEN length of the string in WORD to store. + */ +#define INSERT_WORD_GOTO(CTX, BUF, TARGET, WORD, LEN, RET, LABEL) \ + if (BUF) {LY_CHECK_GOTO(RET = lydict_insert_zc(PARSER_CTX(CTX), WORD, &(TARGET)), LABEL);}\ + else {LY_CHECK_GOTO(RET = lydict_insert(PARSER_CTX(CTX), LEN ? WORD : "", LEN, &(TARGET)), LABEL);} + +/** + * @brief Read from the IN structure COUNT items. Also updates the indent value in yang parser context + * + * @param[in] CTX yang parser context to update its indent value. + * @param[in] COUNT number of items for which the DATA pointer is supposed to move on. + */ +#define MOVE_INPUT(CTX, COUNT) ly_in_skip((CTX)->in, COUNT);(CTX)->indent+=COUNT + +/** + * @brief Loop through all substatements. Starts a for loop and ::YANG_READ_SUBSTMT_NEXT_ITER must be used at its end. + * + * @param[in] CTX yang parser context. + * @param[out] KW YANG keyword read. + * @param[out] WORD Pointer to the keyword itself. + * @param[out] WORD_LEN Length of the keyword. + * @param[out] RET Variable for error storing. + * @param[in] ERR_LABEL Label to go to on error. + */ +#define YANG_READ_SUBSTMT_FOR_GOTO(CTX, KW, WORD, WORD_LEN, RET, ERR_LABEL) \ + ly_bool __loop_end = 0; \ + if ((RET = get_keyword(CTX, &KW, &WORD, &WORD_LEN))) { \ + goto ERR_LABEL; \ + } \ + if (KW == LY_STMT_SYNTAX_SEMICOLON) { \ + __loop_end = 1; \ + } else if (KW != LY_STMT_SYNTAX_LEFT_BRACE) { \ + LOGVAL_PARSER(CTX, LYVE_SYNTAX_YANG, "Invalid keyword \"%s\", expected \";\" or \"{\".", lyplg_ext_stmt2str(KW)); \ + RET = LY_EVALID; \ + goto ERR_LABEL; \ + } else { \ + YANG_READ_SUBSTMT_NEXT_ITER(CTX, KW, WORD, WORD_LEN, NULL, RET, ERR_LABEL); \ + } \ + while (!__loop_end) + +/** + * @brief Next iteration of ::YANG_READ_SUBSTMT_FOR_GOTO loop. + * + * @param[in] CTX yang parser context. + * @param[out] KW YANG keyword read. + * @param[out] WORD Pointer to the keyword itself. + * @param[out] WORD_LEN Length of the keyword. + * @param[in] EXTS Final extension instance array to store. + * @param[out] RET Variable for error storing. + * @param[in] ERR_LABEL Label to go to on error. + */ +#define YANG_READ_SUBSTMT_NEXT_ITER(CTX, KW, WORD, WORD_LEN, EXTS, RET, ERR_LABEL) \ + if ((RET = get_keyword(CTX, &KW, &WORD, &WORD_LEN))) { \ + goto ERR_LABEL; \ + } \ + if (KW == LY_STMT_SYNTAX_RIGHT_BRACE) { \ + if (EXTS && (RET = ly_set_add(&(CTX)->main_ctx->ext_inst, (EXTS), 1, NULL))) { \ + goto ERR_LABEL; \ + } \ + __loop_end = 1; \ + } + +LY_ERR parse_container(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings); +LY_ERR parse_uses(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings); +LY_ERR parse_choice(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings); +LY_ERR parse_case(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings); +LY_ERR parse_list(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings); +LY_ERR parse_grouping(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node_grp **groupings); + +/** + * @brief Add another character to dynamic buffer, a low-level function. + * + * Enlarge if needed. Updates \p input as well as \p buf_used. + * + * @param[in] ctx libyang context for logging. + * @param[in,out] in Input structure. + * @param[in] len Number of bytes to get from the input string and copy into the buffer. + * @param[in,out] buf Buffer to use, can be moved by realloc(). + * @param[in,out] buf_len Current size of the buffer. + * @param[in,out] buf_used Currently used characters of the buffer. + * @return LY_ERR values. + */ +LY_ERR +buf_add_char(struct ly_ctx *ctx, struct ly_in *in, size_t len, char **buf, size_t *buf_len, size_t *buf_used) +{ +#define BUF_STEP 16; + if (*buf_len <= (*buf_used) + len) { + *buf_len += BUF_STEP; + *buf = ly_realloc(*buf, *buf_len); + LY_CHECK_ERR_RET(!*buf, LOGMEM(ctx), LY_EMEM); + } + if (*buf_used) { + ly_in_read(in, &(*buf)[*buf_used], len); + } else { + ly_in_read(in, *buf, len); + } + + (*buf_used) += len; + return LY_SUCCESS; +#undef BUF_STEP +} + +/** + * @brief Store a single UTF8 character. It depends whether in a dynamically-allocated buffer or just as a pointer to the data. + * + * @param[in] ctx yang parser context for logging. + * @param[in] arg Type of the input string to select method of checking character validity. + * @param[in,out] word_p Word pointer. If buffer (\p word_b) was not yet needed, it is just a pointer to the first + * stored character. If buffer was needed (\p word_b is non-NULL or \p need_buf is set), it is pointing to the buffer. + * @param[in,out] word_len Current length of the word pointed to by \p word_p. + * @param[in,out] word_b Word buffer. Is kept NULL as long as it is not requested (word is a substring of the data). + * @param[in,out] buf_len Current length of \p word_b. + * @param[in] need_buf Flag if the dynamically allocated buffer is required. + * @param[in,out] prefix Storage for internally used flag in case of possible prefixed identifiers: + * 0 - colon not yet found (no prefix) + * 1 - \p c is the colon character + * 2 - prefix already processed, now processing the identifier + * @return LY_ERR values. + */ +LY_ERR +buf_store_char(struct lysp_yang_ctx *ctx, enum yang_arg arg, char **word_p, size_t *word_len, + char **word_b, size_t *buf_len, ly_bool need_buf, uint8_t *prefix) +{ + uint32_t c; + size_t len; + + /* check valid combination of input paremeters - if need_buf specified, word_b must be provided */ + assert(!need_buf || (need_buf && word_b)); + + /* get UTF8 code point (and number of bytes coding the character) */ + LY_CHECK_ERR_RET(ly_getutf8(&ctx->in->current, &c, &len), + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[-len]), LY_EVALID); + ctx->in->current -= len; + if (c == '\n') { + ctx->indent = 0; + LY_IN_NEW_LINE(ctx->in); + } else { + /* note - even the multibyte character is count as 1 */ + ++ctx->indent; + } + + /* check character validity */ + switch (arg) { + case Y_IDENTIF_ARG: + LY_CHECK_RET(lysp_check_identifierchar((struct lysp_ctx *)ctx, c, !(*word_len), NULL)); + break; + case Y_PREF_IDENTIF_ARG: + LY_CHECK_RET(lysp_check_identifierchar((struct lysp_ctx *)ctx, c, !(*word_len), prefix)); + break; + case Y_STR_ARG: + case Y_MAYBE_STR_ARG: + LY_CHECK_RET(lysp_check_stringchar((struct lysp_ctx *)ctx, c)); + break; + } + + if (word_b && *word_b) { + /* add another character into buffer */ + if (buf_add_char(PARSER_CTX(ctx), ctx->in, len, word_b, buf_len, word_len)) { + return LY_EMEM; + } + + /* in case of realloc */ + *word_p = *word_b; + } else if (word_b && need_buf) { + /* first time we need a buffer, copy everything read up to now */ + if (*word_len) { + *word_b = malloc(*word_len); + LY_CHECK_ERR_RET(!*word_b, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + *buf_len = *word_len; + memcpy(*word_b, *word_p, *word_len); + } + + /* add this new character into buffer */ + if (buf_add_char(PARSER_CTX(ctx), ctx->in, len, word_b, buf_len, word_len)) { + return LY_EMEM; + } + + /* in case of realloc */ + *word_p = *word_b; + } else { + /* just remember the first character pointer */ + if (!*word_p) { + *word_p = (char *)ctx->in->current; + } + /* ... and update the word's length */ + (*word_len) += len; + ly_in_skip(ctx->in, len); + } + + return LY_SUCCESS; +} + +/** + * @brief Skip YANG comment in data. + * + * @param[in] ctx yang parser context for logging. + * @param[in] comment Type of the comment to process: + * 1 for a one-line comment, + * 2 for a block comment. + * @return LY_ERR values. + */ +LY_ERR +skip_comment(struct lysp_yang_ctx *ctx, uint8_t comment) +{ + /* internal statuses: */ +#define COMMENT_NO 0 /* comment ended */ +#define COMMENT_LINE 1 /* in line comment */ +#define COMMENT_BLOCK 2 /* in block comment */ +#define COMMENT_BLOCK_END 3 /* in block comment with last read character '*' */ + + while (ctx->in->current[0] && comment) { + switch (comment) { + case COMMENT_LINE: + if (ctx->in->current[0] == '\n') { + comment = COMMENT_NO; + LY_IN_NEW_LINE(ctx->in); + } + break; + case COMMENT_BLOCK: + if (ctx->in->current[0] == '*') { + comment = COMMENT_BLOCK_END; + } else if (ctx->in->current[0] == '\n') { + LY_IN_NEW_LINE(ctx->in); + } + break; + case COMMENT_BLOCK_END: + if (ctx->in->current[0] == '/') { + comment = COMMENT_NO; + } else if (ctx->in->current[0] != '*') { + if (ctx->in->current[0] == '\n') { + LY_IN_NEW_LINE(ctx->in); + } + comment = COMMENT_BLOCK; + } + break; + default: + LOGINT_RET(PARSER_CTX(ctx)); + } + + if (ctx->in->current[0] == '\n') { + ctx->indent = 0; + } else { + ++ctx->indent; + } + ++ctx->in->current; + } + + if (!ctx->in->current[0] && (comment >= COMMENT_BLOCK)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Unexpected end-of-input, non-terminated comment."); + return LY_EVALID; + } + + return LY_SUCCESS; + +#undef COMMENT_NO +#undef COMMENT_LINE +#undef COMMENT_BLOCK +#undef COMMENT_BLOCK_END +} + +/** + * @brief Read a quoted string from data. + * + * @param[in] ctx yang parser context for logging. + * @param[in] arg Type of YANG keyword argument expected. + * @param[out] word_p Pointer to the read quoted string. + * @param[out] word_b Pointer to a dynamically-allocated buffer holding the read quoted string. If not needed, + * set to NULL. Otherwise equal to \p word_p. + * @param[out] word_len Length of the read quoted string. + * @param[out] buf_len Length of the dynamically-allocated buffer \p word_b. + * @param[in] indent Current indent (number of YANG spaces). Needed for correct multi-line string + * indenation in the final quoted string. + * @return LY_ERR values. + */ +static LY_ERR +read_qstring(struct lysp_yang_ctx *ctx, enum yang_arg arg, char **word_p, char **word_b, + size_t *word_len, size_t *buf_len) +{ + /* string parsing status: */ +#define STRING_ENDED 0 /* string ended */ +#define STRING_SINGLE_QUOTED 1 /* string with ' */ +#define STRING_DOUBLE_QUOTED 2 /* string with " */ +#define STRING_DOUBLE_QUOTED_ESCAPED 3 /* string with " with last character \ */ +#define STRING_PAUSED_NEXTSTRING 4 /* string finished, now skipping whitespaces looking for + */ +#define STRING_PAUSED_CONTINUE 5 /* string continues after +, skipping whitespaces */ + + uint8_t string; + uint64_t block_indent = 0, current_indent = 0; + ly_bool need_buf = 0; + uint8_t prefix = 0; + const char *c; + uint64_t trailing_ws = 0; /* current number of stored trailing whitespace characters */ + + if (ctx->in->current[0] == '\"') { + string = STRING_DOUBLE_QUOTED; + current_indent = block_indent = ctx->indent + 1; + } else { + assert(ctx->in->current[0] == '\''); + string = STRING_SINGLE_QUOTED; + } + MOVE_INPUT(ctx, 1); + + while (ctx->in->current[0] && string) { + switch (string) { + case STRING_SINGLE_QUOTED: + if (ctx->in->current[0] == '\'') { + /* string may be finished, but check for + */ + string = STRING_PAUSED_NEXTSTRING; + MOVE_INPUT(ctx, 1); + } else { + /* check and store character */ + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + } + break; + case STRING_DOUBLE_QUOTED: + switch (ctx->in->current[0]) { + case '\"': + /* string may be finished, but check for + */ + string = STRING_PAUSED_NEXTSTRING; + MOVE_INPUT(ctx, 1); + trailing_ws = 0; + break; + case '\\': + /* special character following */ + string = STRING_DOUBLE_QUOTED_ESCAPED; + + /* the backslash sequence is substituted, so we will need a buffer to store the result */ + need_buf = 1; + + /* move forward to the escaped character */ + ++ctx->in->current; + + /* note that the trailing whitespaces are supposed to be trimmed before substitution of + * backslash-escaped characters (RFC 7950, 6.1.3), so we have to zero the trailing whitespaces counter */ + trailing_ws = 0; + + /* since the backslash-escaped character is handled as first non-whitespace character, stop eating indentation */ + current_indent = block_indent; + break; + case ' ': + if (current_indent < block_indent) { + ++current_indent; + MOVE_INPUT(ctx, 1); + } else { + /* check and store whitespace character */ + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + trailing_ws++; + } + break; + case '\t': + if (current_indent < block_indent) { + assert(need_buf); + current_indent += Y_TAB_SPACES; + ctx->indent += Y_TAB_SPACES; + for ( ; current_indent > block_indent; --current_indent, --ctx->indent) { + /* store leftover spaces from the tab */ + c = ctx->in->current; + ctx->in->current = " "; + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + ctx->in->current = c; + trailing_ws++; + } + ++ctx->in->current; + } else { + /* check and store whitespace character */ + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + trailing_ws++; + /* additional characters for indentation - only 1 was count in buf_store_char */ + ctx->indent += Y_TAB_SPACES - 1; + } + break; + case '\r': + if (ctx->in->current[1] != '\n') { + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[0]); + return LY_EVALID; + } + /* fallthrough */ + case '\n': + if (block_indent) { + /* we will be removing the indents so we need our own buffer */ + need_buf = 1; + + /* remove trailing tabs and spaces */ + (*word_len) = *word_len - trailing_ws; + + /* restart indentation */ + current_indent = 0; + } + + /* check and store character */ + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + + /* reset context indentation counter for possible string after this one */ + ctx->indent = 0; + trailing_ws = 0; + break; + default: + /* first non-whitespace character, stop eating indentation */ + current_indent = block_indent; + + /* check and store character */ + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + trailing_ws = 0; + break; + } + break; + case STRING_DOUBLE_QUOTED_ESCAPED: + /* string encoded characters */ + c = ctx->in->current; + switch (ctx->in->current[0]) { + case 'n': + ctx->in->current = "\n"; + /* fix false newline count in buf_store_char() */ + ctx->in->line--; + break; + case 't': + ctx->in->current = "\t"; + break; + case '\"': + case '\\': + /* ok as is */ + break; + default: + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Double-quoted string unknown special character '\\%c'.", + ctx->in->current[0]); + return LY_EVALID; + } + + /* check and store character */ + LY_CHECK_RET(buf_store_char(ctx, arg, word_p, word_len, word_b, buf_len, need_buf, &prefix)); + + string = STRING_DOUBLE_QUOTED; + ctx->in->current = c + 1; + break; + case STRING_PAUSED_NEXTSTRING: + switch (ctx->in->current[0]) { + case '+': + /* string continues */ + string = STRING_PAUSED_CONTINUE; + need_buf = 1; + break; + case '\r': + if (ctx->in->current[1] != '\n') { + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[0]); + return LY_EVALID; + } + MOVE_INPUT(ctx, 1); + /* fallthrough */ + case '\n': + LY_IN_NEW_LINE(ctx->in); + /* fall through */ + case ' ': + case '\t': + /* just skip */ + break; + default: + /* string is finished */ + goto string_end; + } + MOVE_INPUT(ctx, 1); + break; + case STRING_PAUSED_CONTINUE: + switch (ctx->in->current[0]) { + case '\r': + if (ctx->in->current[1] != '\n') { + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[0]); + return LY_EVALID; + } + MOVE_INPUT(ctx, 1); + /* fallthrough */ + case '\n': + LY_IN_NEW_LINE(ctx->in); + /* fall through */ + case ' ': + case '\t': + /* skip */ + break; + case '\'': + string = STRING_SINGLE_QUOTED; + break; + case '\"': + string = STRING_DOUBLE_QUOTED; + break; + default: + /* it must be quoted again */ + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Both string parts divided by '+' must be quoted."); + return LY_EVALID; + } + MOVE_INPUT(ctx, 1); + break; + default: + return LY_EINT; + } + } + +string_end: + if ((arg <= Y_PREF_IDENTIF_ARG) && !(*word_len)) { + /* empty identifier */ + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Statement argument is required."); + return LY_EVALID; + } + return LY_SUCCESS; + +#undef STRING_ENDED +#undef STRING_SINGLE_QUOTED +#undef STRING_DOUBLE_QUOTED +#undef STRING_DOUBLE_QUOTED_ESCAPED +#undef STRING_PAUSED_NEXTSTRING +#undef STRING_PAUSED_CONTINUE +} + +/** + * @brief Get another YANG string from the raw data. + * + * @param[in] ctx yang parser context for logging. + * @param[in] arg Type of YANG keyword argument expected. + * @param[out] flags optional output argument to get flag of the argument's quoting (LYS_*QUOTED - see + * [schema node flags](@ref snodeflags)) + * @param[out] word_p Pointer to the read string. Can return NULL if \p arg is #Y_MAYBE_STR_ARG. + * @param[out] word_b Pointer to a dynamically-allocated buffer holding the read string. If not needed, + * set to NULL. Otherwise equal to \p word_p. + * @param[out] word_len Length of the read string. + * @return LY_ERR values. + */ +LY_ERR +get_argument(struct lysp_yang_ctx *ctx, enum yang_arg arg, uint16_t *flags, char **word_p, + char **word_b, size_t *word_len) +{ + LY_ERR ret; + size_t buf_len = 0; + uint8_t prefix = 0; + + /* word buffer - dynamically allocated */ + *word_b = NULL; + + /* word pointer - just a pointer to data */ + *word_p = NULL; + + *word_len = 0; + while (ctx->in->current[0]) { + switch (ctx->in->current[0]) { + case '\'': + case '\"': + if (*word_len) { + /* invalid - quotes cannot be in unquoted string and only optsep, ; or { can follow it */ + LOGVAL_PARSER(ctx, LY_VCODE_INSTREXP, 1, ctx->in->current, + "unquoted string character, optsep, semicolon or opening brace"); + ret = LY_EVALID; + goto error; + } + if (flags) { + (*flags) |= ctx->in->current[0] == '\'' ? LYS_SINGLEQUOTED : LYS_DOUBLEQUOTED; + } + LY_CHECK_GOTO(ret = read_qstring(ctx, arg, word_p, word_b, word_len, &buf_len), error); + if (!*word_p) { + /* do not return NULL word */ + *word_p = ""; + } + goto str_end; + case '/': + if (ctx->in->current[1] == '/') { + /* one-line comment */ + MOVE_INPUT(ctx, 2); + LY_CHECK_GOTO(ret = skip_comment(ctx, 1), error); + } else if (ctx->in->current[1] == '*') { + /* block comment */ + MOVE_INPUT(ctx, 2); + LY_CHECK_GOTO(ret = skip_comment(ctx, 2), error); + } else { + /* not a comment after all */ + LY_CHECK_GOTO(ret = buf_store_char(ctx, arg, word_p, word_len, word_b, &buf_len, 0, &prefix), error); + } + break; + case ' ': + if (*word_len) { + /* word is finished */ + goto str_end; + } + MOVE_INPUT(ctx, 1); + break; + case '\t': + if (*word_len) { + /* word is finished */ + goto str_end; + } + /* tabs count for 8 spaces */ + ctx->indent += Y_TAB_SPACES; + + ++ctx->in->current; + break; + case '\r': + if (ctx->in->current[1] != '\n') { + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[0]); + ret = LY_EVALID; + goto error; + } + MOVE_INPUT(ctx, 1); + /* fallthrough */ + case '\n': + if (*word_len) { + /* word is finished */ + goto str_end; + } + LY_IN_NEW_LINE(ctx->in); + MOVE_INPUT(ctx, 1); + + /* reset indent */ + ctx->indent = 0; + break; + case ';': + case '{': + if (*word_len || (arg == Y_MAYBE_STR_ARG)) { + /* word is finished */ + goto str_end; + } + + LOGVAL_PARSER(ctx, LY_VCODE_INSTREXP, 1, ctx->in->current, "an argument"); + ret = LY_EVALID; + goto error; + case '}': + /* invalid - braces cannot be in unquoted string (opening braces terminates the string and can follow it) */ + LOGVAL_PARSER(ctx, LY_VCODE_INSTREXP, 1, ctx->in->current, + "unquoted string character, optsep, semicolon or opening brace"); + ret = LY_EVALID; + goto error; + default: + LY_CHECK_GOTO(ret = buf_store_char(ctx, arg, word_p, word_len, word_b, &buf_len, 0, &prefix), error); + break; + } + } + + /* unexpected end of loop */ + LOGVAL_PARSER(ctx, LY_VCODE_EOF); + ret = LY_EVALID; + goto error; + +str_end: + /* terminating NULL byte for buf */ + if (*word_b) { + (*word_b) = ly_realloc(*word_b, (*word_len) + 1); + LY_CHECK_ERR_RET(!(*word_b), LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + (*word_b)[*word_len] = '\0'; + *word_p = *word_b; + } + + return LY_SUCCESS; + +error: + free(*word_b); + *word_b = NULL; + return ret; +} + +/** + * @brief Get another YANG keyword from the raw data. + * + * @param[in] ctx yang parser context for logging. + * @param[out] kw YANG keyword read. + * @param[out] word_p Pointer to the keyword in the data. Useful for extension instances. + * @param[out] word_len Length of the keyword in the data. Useful for extension instances. + * @return LY_ERR values. + */ +LY_ERR +get_keyword(struct lysp_yang_ctx *ctx, enum ly_stmt *kw, char **word_p, size_t *word_len) +{ + uint8_t prefix; + const char *word_start; + size_t len; + + if (word_p) { + *word_p = NULL; + *word_len = 0; + } + + /* first skip "optsep", comments */ + while (ctx->in->current[0]) { + switch (ctx->in->current[0]) { + case '/': + if (ctx->in->current[1] == '/') { + /* one-line comment */ + MOVE_INPUT(ctx, 2); + LY_CHECK_RET(skip_comment(ctx, 1)); + } else if (ctx->in->current[1] == '*') { + /* block comment */ + MOVE_INPUT(ctx, 2); + LY_CHECK_RET(skip_comment(ctx, 2)); + } else { + /* error - not a comment after all, keyword cannot start with slash */ + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Invalid identifier first character '/'."); + return LY_EVALID; + } + continue; + case '\n': + /* skip whitespaces (optsep) */ + LY_IN_NEW_LINE(ctx->in); + ctx->indent = 0; + break; + case ' ': + /* skip whitespaces (optsep) */ + ++ctx->indent; + break; + case '\t': + /* skip whitespaces (optsep) */ + ctx->indent += Y_TAB_SPACES; + break; + case '\r': + /* possible CRLF endline */ + if (ctx->in->current[1] == '\n') { + break; + } + /* fallthrough */ + default: + /* either a keyword start or an invalid character */ + goto keyword_start; + } + + ly_in_skip(ctx->in, 1); + } + +keyword_start: + word_start = ctx->in->current; + *kw = lysp_match_kw(ctx->in, &ctx->indent); + + if (*kw == LY_STMT_SYNTAX_SEMICOLON) { + goto success; + } else if (*kw == LY_STMT_SYNTAX_LEFT_BRACE) { + ctx->depth++; + if (ctx->depth > LY_MAX_BLOCK_DEPTH) { + LOGERR(PARSER_CTX(ctx), LY_EINVAL, "The maximum number of block nestings has been exceeded."); + return LY_EINVAL; + } + goto success; + } else if (*kw == LY_STMT_SYNTAX_RIGHT_BRACE) { + ctx->depth--; + goto success; + } + + if (*kw != LY_STMT_NONE) { + /* make sure we have the whole keyword */ + switch (ctx->in->current[0]) { + case '\r': + if (ctx->in->current[1] != '\n') { + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[0]); + return LY_EVALID; + } + MOVE_INPUT(ctx, 1); + /* fallthrough */ + case '\n': + case '\t': + case ' ': + /* mandatory "sep" is just checked, not eaten so nothing in the context is updated */ + break; + case ':': + /* keyword is not actually a keyword, but prefix of an extension. + * To avoid repeated check of the prefix syntax, move to the point where the colon was read + * and we will be checking the keyword (extension instance) itself */ + prefix = 1; + MOVE_INPUT(ctx, 1); + goto extension; + case '{': + /* allowed only for input and output statements which can be without arguments */ + if ((*kw == LY_STMT_INPUT) || (*kw == LY_STMT_OUTPUT)) { + break; + } + /* fall through */ + default: + MOVE_INPUT(ctx, 1); + LOGVAL_PARSER(ctx, LY_VCODE_INSTREXP, (int)(ctx->in->current - word_start), word_start, + "a keyword followed by a separator"); + return LY_EVALID; + } + } else { + /* still can be an extension */ + prefix = 0; + +extension: + while (ctx->in->current[0] && (ctx->in->current[0] != ' ') && (ctx->in->current[0] != '\t') && + (ctx->in->current[0] != '\n') && (ctx->in->current[0] != '\r') && (ctx->in->current[0] != '{') && + (ctx->in->current[0] != ';')) { + uint32_t c = 0; + + LY_CHECK_ERR_RET(ly_getutf8(&ctx->in->current, &c, &len), + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[-len]), LY_EVALID); + ++ctx->indent; + /* check character validity */ + LY_CHECK_RET(lysp_check_identifierchar((struct lysp_ctx *)ctx, c, + ctx->in->current - len == word_start ? 1 : 0, &prefix)); + } + if (!ctx->in->current[0]) { + LOGVAL_PARSER(ctx, LY_VCODE_EOF); + return LY_EVALID; + } + + /* prefix is mandatory for extension instances */ + if (prefix != 2) { + LOGVAL_PARSER(ctx, LY_VCODE_INSTREXP, (int)(ctx->in->current - word_start), word_start, "a keyword"); + return LY_EVALID; + } + + *kw = LY_STMT_EXTENSION_INSTANCE; + } + +success: + if (word_p) { + *word_p = (char *)word_start; + *word_len = ctx->in->current - word_start; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse extension instance substatements. + * + * @param[in] ctx yang parser context for logging. + * @param[in] kw Statement keyword value matching @p word value. + * @param[in] word Extension instance substatement name (keyword). + * @param[in] word_len Extension instance substatement name length. + * @param[in,out] child Children of this extension instance to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_ext_substmt(struct lysp_yang_ctx *ctx, enum ly_stmt kw, char *word, size_t word_len, + struct lysp_stmt **child) +{ + char *buf; + LY_ERR ret = LY_SUCCESS; + enum ly_stmt child_kw; + struct lysp_stmt *stmt, *par_child; + + stmt = calloc(1, sizeof *stmt); + LY_CHECK_ERR_RET(!stmt, LOGMEM(NULL), LY_EMEM); + + /* insert into parent statements */ + if (!*child) { + *child = stmt; + } else { + for (par_child = *child; par_child->next; par_child = par_child->next) {} + par_child->next = stmt; + } + + /* statement */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), word, word_len, &stmt->stmt)); + + /* get optional argument */ + LY_CHECK_RET(get_argument(ctx, Y_MAYBE_STR_ARG, &stmt->flags, &word, &buf, &word_len)); + if (word) { + INSERT_WORD_GOTO(ctx, buf, stmt->arg, word, word_len, ret, cleanup); + } + + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = PARSER_CUR_PMOD(ctx); + stmt->kw = kw; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, child_kw, word, word_len, ret, cleanup) { + LY_CHECK_GOTO(ret = parse_ext_substmt(ctx, child_kw, word, word_len, &stmt->child), cleanup) + YANG_READ_SUBSTMT_NEXT_ITER(ctx, child_kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse extension instance. + * + * @param[in] ctx yang parser context for logging. + * @param[in] ext_name Extension instance substatement name (keyword). + * @param[in] ext_name_len Extension instance substatement name length. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[in] parent_stmt_index In case of several @p parent_stmt, index of the relevant @p parent statement. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_ext(struct lysp_yang_ctx *ctx, const char *ext_name, size_t ext_name_len, const void *parent, + enum ly_stmt parent_stmt, LY_ARRAY_COUNT_TYPE parent_stmt_index, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + struct lysp_ext_instance *e; + enum ly_stmt kw; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *exts, e, LY_EMEM); + + if (!ly_strnchr(ext_name, ':', ext_name_len)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%*.s\" without the mandatory prefix.", ext_name_len, ext_name); + return LY_EVALID; + } + + /* store name */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), ext_name, ext_name_len, &e->name)); + + /* get optional argument */ + LY_CHECK_RET(get_argument(ctx, Y_MAYBE_STR_ARG, NULL, &word, &buf, &word_len)); + if (word) { + INSERT_WORD_GOTO(ctx, buf, e->argument, word, word_len, ret, cleanup); + } + + /* store the rest of information */ + e->format = LY_VALUE_SCHEMA; + e->parsed = NULL; + e->prefix_data = PARSER_CUR_PMOD(ctx); + e->parent = (void *)parent; + e->parent_stmt = parent_stmt; + e->parent_stmt_index = parent_stmt_index; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + LY_CHECK_GOTO(ret = parse_ext_substmt(ctx, kw, word, word_len, &e->child), cleanup) + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse a generic text field without specific constraints. Those are contact, organization, + * description, etc... + * + * @param[in] ctx yang parser context for logging. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of statement in @p value. + * @param[in] parent_stmt_index In case of several @p parent_stmt, index of the relevant @p parent statement. + * @param[in,out] value Place to store the parsed value. + * @param[in] arg Type of the YANG keyword argument (of the value). + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_text_field(struct lysp_yang_ctx *ctx, const void *parent, enum ly_stmt parent_stmt, uint32_t parent_stmt_index, + const char **value, enum yang_arg arg, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (*value) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, arg, NULL, &word, &buf, &word_len)); + + /* store value and spend buf if allocated */ + INSERT_WORD_GOTO(ctx, buf, *value, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, parent, parent_stmt, parent_stmt_index, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the yang-version statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] mod Module to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_yangversion(struct lysp_yang_ctx *ctx, struct lysp_module *mod) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (mod->version) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "yang-version"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + if ((word_len == 1) && !strncmp(word, "1", word_len)) { + mod->version = LYS_VERSION_1_0; + } else if ((word_len == ly_strlen_const("1.1")) && !strncmp(word, "1.1", word_len)) { + mod->version = LYS_VERSION_1_1; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "yang-version"); + free(buf); + return LY_EVALID; + } + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, mod, LY_STMT_YANG_VERSION, 0, &mod->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "yang-version"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the belongs-to statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] submod Submodule to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_belongsto(struct lysp_yang_ctx *ctx, struct lysp_submodule *submod) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (submod->prefix) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "belongs-to"); + return LY_EVALID; + } + + /* get value, it must match the main module */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + if (ly_strncmp(PARSER_CUR_PMOD(ctx)->mod->name, word, word_len)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Submodule \"belongs-to\" value \"%.*s\" does not match its module name \"%s\".", + (int)word_len, word, PARSER_CUR_PMOD(ctx)->mod->name); + free(buf); + return LY_EVALID; + } + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_PREFIX: + LY_CHECK_RET(parse_text_field(ctx, submod->prefix, LY_STMT_PREFIX, 0, &submod->prefix, Y_IDENTIF_ARG, &submod->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, submod, LY_STMT_BELONGS_TO, 0, &submod->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "belongs-to"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + + /* mandatory substatements */ + if (!submod->prefix) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "prefix", "belongs-to"); + return LY_EVALID; + } + +cleanup: + return ret; +} + +/** + * @brief Parse the revision-date statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] rev Buffer to store the parsed value in. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_revisiondate(struct lysp_yang_ctx *ctx, char *rev, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (rev[0]) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "revision-date"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + /* check value */ + if (lysp_check_date((struct lysp_ctx *)ctx, word, word_len, "revision-date")) { + free(buf); + return LY_EVALID; + } + + /* store value and spend buf if allocated */ + strncpy(rev, word, word_len); + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, rev, LY_STMT_REVISION_DATE, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "revision-date"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the include statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in] module_name Name of the module to check name collisions. + * @param[in,out] includes Parsed includes to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_include(struct lysp_yang_ctx *ctx, const char *module_name, struct lysp_include **includes) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_include *inc; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *includes, inc, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + + INSERT_WORD_GOTO(ctx, buf, inc->name, word, word_len, ret, cleanup); + + /* submodules share the namespace with the module names, so there must not be + * a module of the same name in the context, no need for revision matching */ + if (!strcmp(module_name, inc->name) || ly_ctx_get_module_latest(PARSER_CTX(ctx), inc->name)) { + LOGVAL_PARSER(ctx, LY_VCODE_NAME2_COL, "module", "submodule", inc->name); + return LY_EVALID; + } + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + PARSER_CHECK_STMTVER2_RET(ctx, "description", "include"); + LY_CHECK_RET(parse_text_field(ctx, inc->dsc, LY_STMT_DESCRIPTION, 0, &inc->dsc, Y_STR_ARG, &inc->exts)); + break; + case LY_STMT_REFERENCE: + PARSER_CHECK_STMTVER2_RET(ctx, "reference", "include"); + LY_CHECK_RET(parse_text_field(ctx, inc->ref, LY_STMT_REFERENCE, 0, &inc->ref, Y_STR_ARG, &inc->exts)); + break; + case LY_STMT_REVISION_DATE: + LY_CHECK_RET(parse_revisiondate(ctx, inc->rev, &inc->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, inc, LY_STMT_INCLUDE, 0, &inc->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "include"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, inc->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the import statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in] module_prefix Prefix of the module to check prefix collisions. + * @param[in,out] imports Parsed imports to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_import(struct lysp_yang_ctx *ctx, const char *module_prefix, struct lysp_import **imports) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_import *imp; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *imports, imp, LY_EVALID); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, imp->name, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_PREFIX: + LY_CHECK_RET(parse_text_field(ctx, imp->prefix, LY_STMT_PREFIX, 0, &imp->prefix, Y_IDENTIF_ARG, &imp->exts)); + LY_CHECK_RET(lysp_check_prefix((struct lysp_ctx *)ctx, *imports, module_prefix, &imp->prefix), LY_EVALID); + break; + case LY_STMT_DESCRIPTION: + PARSER_CHECK_STMTVER2_RET(ctx, "description", "import"); + LY_CHECK_RET(parse_text_field(ctx, imp->dsc, LY_STMT_DESCRIPTION, 0, &imp->dsc, Y_STR_ARG, &imp->exts)); + break; + case LY_STMT_REFERENCE: + PARSER_CHECK_STMTVER2_RET(ctx, "reference", "import"); + LY_CHECK_RET(parse_text_field(ctx, imp->ref, LY_STMT_REFERENCE, 0, &imp->ref, Y_STR_ARG, &imp->exts)); + break; + case LY_STMT_REVISION_DATE: + LY_CHECK_RET(parse_revisiondate(ctx, imp->rev, &imp->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, imp, LY_STMT_IMPORT, 0, &imp->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "import"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, imp->exts, ret, cleanup); + } + + /* mandatory substatements */ + LY_CHECK_ERR_RET(!imp->prefix, LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "prefix", "import"), LY_EVALID); + +cleanup: + return ret; +} + +/** + * @brief Parse the revision statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] revs Parsed revisions to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_revision(struct lysp_yang_ctx *ctx, struct lysp_revision **revs) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_revision *rev; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *revs, rev, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + /* check value */ + if (lysp_check_date((struct lysp_ctx *)ctx, word, word_len, "revision")) { + free(buf); + return LY_EVALID; + } + + strncpy(rev->date, word, word_len); + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, rev->dsc, LY_STMT_DESCRIPTION, 0, &rev->dsc, Y_STR_ARG, &rev->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, rev->ref, LY_STMT_REFERENCE, 0, &rev->ref, Y_STR_ARG, &rev->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, rev, LY_STMT_REVISION, 0, &rev->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "revision"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, rev->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse a generic text field that can have more instances such as base. + * + * @param[in] ctx yang parser context for logging. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[in,out] texts Parsed values to add to. + * @param[in] arg Type of the expected argument. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_text_fields(struct lysp_yang_ctx *ctx, enum ly_stmt parent_stmt, const char ***texts, enum yang_arg arg, + struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + const char **item; + size_t word_len; + enum ly_stmt kw; + + /* allocate new pointer */ + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *texts, item, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, arg, NULL, &word, &buf, &word_len)); + + INSERT_WORD_GOTO(ctx, buf, *item, word, word_len, ret, cleanup); + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, *texts, parent_stmt, LY_ARRAY_COUNT(*texts) - 1, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse a generic text field that can have more instances such as base. + * + * @param[in] ctx yang parser context for logging. + * @param[in] parent_stmt Type of statement stored in @p qnames. + * @param[in,out] qnames Parsed qnames to add to. + * @param[in] arg Type of the expected argument. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_qnames(struct lysp_yang_ctx *ctx, enum ly_stmt parent_stmt, struct lysp_qname **qnames, enum yang_arg arg, + struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + struct lysp_qname *item; + size_t word_len; + enum ly_stmt kw; + + /* allocate new pointer */ + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *qnames, item, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, arg, NULL, &word, &buf, &word_len)); + + INSERT_WORD_GOTO(ctx, buf, item->str, word, word_len, ret, cleanup); + item->mod = PARSER_CUR_PMOD(ctx); + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, *qnames, parent_stmt, LY_ARRAY_COUNT(*qnames) - 1, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the config statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_config(struct lysp_yang_ctx *ctx, uint16_t *flags, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (*flags & LYS_CONFIG_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "config"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + if ((word_len == ly_strlen_const("true")) && !strncmp(word, "true", word_len)) { + *flags |= LYS_CONFIG_W; + } else if ((word_len == ly_strlen_const("false")) && !strncmp(word, "false", word_len)) { + *flags |= LYS_CONFIG_R; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "config"); + free(buf); + return LY_EVALID; + } + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, flags, LY_STMT_CONFIG, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "config"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the mandatory statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_mandatory(struct lysp_yang_ctx *ctx, uint16_t *flags, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (*flags & LYS_MAND_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "mandatory"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + if ((word_len == ly_strlen_const("true")) && !strncmp(word, "true", word_len)) { + *flags |= LYS_MAND_TRUE; + } else if ((word_len == ly_strlen_const("false")) && !strncmp(word, "false", word_len)) { + *flags |= LYS_MAND_FALSE; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "mandatory"); + free(buf); + return LY_EVALID; + } + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, flags, LY_STMT_MANDATORY, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "mandatory"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse a restriction such as range or length. + * + * @param[in] ctx yang parser context for logging. + * @param[in] restr_kw Type of this particular restriction. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_restr(struct lysp_yang_ctx *ctx, enum ly_stmt restr_kw, struct lysp_restr *restr) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + CHECK_NONEMPTY(ctx, word_len, lyplg_ext_stmt2str(restr_kw)); + INSERT_WORD_GOTO(ctx, buf, restr->arg.str, word, word_len, ret, cleanup); + restr->arg.mod = PARSER_CUR_PMOD(ctx); + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, restr->dsc, LY_STMT_DESCRIPTION, 0, &restr->dsc, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, restr->ref, LY_STMT_REFERENCE, 0, &restr->ref, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_APP_TAG: + LY_CHECK_RET(parse_text_field(ctx, restr, LY_STMT_ERROR_APP_TAG, 0, &restr->eapptag, Y_STR_ARG, + &restr->exts)); + break; + case LY_STMT_ERROR_MESSAGE: + LY_CHECK_RET(parse_text_field(ctx, restr, LY_STMT_ERROR_MESSAGE, 0, &restr->emsg, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, restr, restr_kw, 0, &restr->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(restr_kw)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, restr->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse a restriction that can have more instances such as must. + * + * @param[in] ctx yang parser context for logging. + * @param[in] restr_kw Type of this particular restriction. + * @param[in,out] restrs Restrictions to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_restrs(struct lysp_yang_ctx *ctx, enum ly_stmt restr_kw, struct lysp_restr **restrs) +{ + struct lysp_restr *restr; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *restrs, restr, LY_EMEM); + return parse_restr(ctx, restr_kw, restr); +} + +/** + * @brief Parse the status statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_status(struct lysp_yang_ctx *ctx, uint16_t *flags, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (*flags & LYS_STATUS_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "status"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + if ((word_len == ly_strlen_const("current")) && !strncmp(word, "current", word_len)) { + *flags |= LYS_STATUS_CURR; + } else if ((word_len == ly_strlen_const("deprecated")) && !strncmp(word, "deprecated", word_len)) { + *flags |= LYS_STATUS_DEPRC; + } else if ((word_len == ly_strlen_const("obsolete")) && !strncmp(word, "obsolete", word_len)) { + *flags |= LYS_STATUS_OBSLT; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "status"); + free(buf); + return LY_EVALID; + } + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, flags, LY_STMT_STATUS, 0, exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "status"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the when statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] when_p When pointer to parse to. + * @return LY_ERR values. + */ +LY_ERR +parse_when(struct lysp_yang_ctx *ctx, struct lysp_when **when_p) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_when *when; + struct lysf_ctx fctx = {.ctx = PARSER_CTX(ctx)}; + + if (*when_p) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "when"); + return LY_EVALID; + } + + when = calloc(1, sizeof *when); + LY_CHECK_ERR_GOTO(!when, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + CHECK_NONEMPTY(ctx, word_len, "when"); + INSERT_WORD_GOTO(ctx, buf, when->cond, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_GOTO(ret = parse_text_field(ctx, when->dsc, LY_STMT_DESCRIPTION, 0, &when->dsc, Y_STR_ARG, &when->exts), + cleanup); + break; + case LY_STMT_REFERENCE: + LY_CHECK_GOTO(ret = parse_text_field(ctx, when->ref, LY_STMT_REFERENCE, 0, &when->ref, Y_STR_ARG, &when->exts), + cleanup); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, *when_p, LY_STMT_WHEN, 0, &when->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "when"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, when->exts, ret, cleanup); + } + +cleanup: + if (ret) { + lysp_when_free(&fctx, when); + free(when); + } else { + *when_p = when; + } + return ret; +} + +/** + * @brief Parse the anydata or anyxml statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in] any_kw Type of this particular keyword. + * @param[in] parent Node parent. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_any(struct lysp_yang_ctx *ctx, enum ly_stmt any_kw, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + struct lysp_node_anydata *any; + enum ly_stmt kw; + + /* create new structure and insert into siblings */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, any, next, LY_EMEM); + + any->nodetype = any_kw == LY_STMT_ANYDATA ? LYS_ANYDATA : LYS_ANYXML; + any->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, any->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &any->flags, &any->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, any->dsc, LY_STMT_DESCRIPTION, 0, &any->dsc, Y_STR_ARG, &any->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &any->iffeatures, Y_STR_ARG, &any->exts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(parse_mandatory(ctx, &any->flags, &any->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(parse_restrs(ctx, kw, &any->musts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, any->ref, LY_STMT_REFERENCE, 0, &any->ref, Y_STR_ARG, &any->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &any->flags, &any->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &any->when)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, any, any_kw, 0, &any->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(any_kw)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, any->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the value or position statement. Substatement of type enum statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in] val_kw Type of this particular keyword. + * @param[in,out] enm Structure to fill. + * @return LY_ERR values. + */ +LY_ERR +parse_type_enum_value_pos(struct lysp_yang_ctx *ctx, enum ly_stmt val_kw, struct lysp_type_enum *enm) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word, *ptr; + size_t word_len; + long long num = 0; + unsigned long long unum = 0; + enum ly_stmt kw; + + if (enm->flags & LYS_SET_VALUE) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + enm->flags |= LYS_SET_VALUE; + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + if (!word_len || (word[0] == '+') || ((word[0] == '0') && (word_len > 1)) || ((val_kw == LY_STMT_POSITION) && !strncmp(word, "-0", 2))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + + errno = 0; + if (val_kw == LY_STMT_VALUE) { + num = strtoll(word, &ptr, LY_BASE_DEC); + if ((num < INT64_C(-2147483648)) || (num > INT64_C(2147483647))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + } else { + unum = strtoull(word, &ptr, LY_BASE_DEC); + if (unum > UINT64_C(4294967295)) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + } + /* we have not parsed the whole argument */ + if ((size_t)(ptr - word) != word_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + if (errno == ERANGE) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + if (val_kw == LY_STMT_VALUE) { + enm->value = num; + } else { + enm->value = unum; + } + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + ret = parse_ext(ctx, word, word_len, enm, val_kw, 0, &enm->exts); + LY_CHECK_GOTO(ret, cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(val_kw)); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the enum or bit statement. Substatement of type statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in] enum_kw Type of this particular keyword. + * @param[in,out] enums Enums or bits to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_type_enum(struct lysp_yang_ctx *ctx, enum ly_stmt enum_kw, struct lysp_type_enum **enums) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_type_enum *enm; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *enums, enm, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, enum_kw == LY_STMT_ENUM ? Y_STR_ARG : Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + if (enum_kw == LY_STMT_ENUM) { + ret = lysp_check_enum_name((struct lysp_ctx *)ctx, (const char *)word, word_len); + LY_CHECK_ERR_RET(ret, free(buf), ret); + } /* else nothing specific for YANG_BIT */ + + INSERT_WORD_GOTO(ctx, buf, enm->name, word, word_len, ret, cleanup); + CHECK_UNIQUENESS(ctx, *enums, name, lyplg_ext_stmt2str(enum_kw), enm->name); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, enm->dsc, LY_STMT_DESCRIPTION, 0, &enm->dsc, Y_STR_ARG, &enm->exts)); + break; + case LY_STMT_IF_FEATURE: + PARSER_CHECK_STMTVER2_RET(ctx, "if-feature", lyplg_ext_stmt2str(enum_kw)); + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &enm->iffeatures, Y_STR_ARG, &enm->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, enm->ref, LY_STMT_REFERENCE, 0, &enm->ref, Y_STR_ARG, &enm->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &enm->flags, &enm->exts)); + break; + case LY_STMT_VALUE: + LY_CHECK_ERR_RET(enum_kw == LY_STMT_BIT, LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), + lyplg_ext_stmt2str(enum_kw)), LY_EVALID); + LY_CHECK_RET(parse_type_enum_value_pos(ctx, kw, enm)); + break; + case LY_STMT_POSITION: + LY_CHECK_ERR_RET(enum_kw == LY_STMT_ENUM, LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), + lyplg_ext_stmt2str(enum_kw)), LY_EVALID); + LY_CHECK_RET(parse_type_enum_value_pos(ctx, kw, enm)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, enm, enum_kw, 0, &enm->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(enum_kw)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, enm->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the fraction-digits statement. Substatement of type statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] type Type to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_type_fracdigits(struct lysp_yang_ctx *ctx, struct lysp_type *type) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word, *ptr; + size_t word_len; + unsigned long long num; + enum ly_stmt kw; + + if (type->fraction_digits) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "fraction-digits"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if (!word_len || (word[0] == '0') || !isdigit(word[0])) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "fraction-digits"); + ret = LY_EVALID; + goto cleanup; + } + + errno = 0; + num = strtoull(word, &ptr, LY_BASE_DEC); + /* we have not parsed the whole argument */ + if ((size_t)(ptr - word) != word_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "fraction-digits"); + ret = LY_EVALID; + goto cleanup; + } + if ((errno == ERANGE) || (num > LY_TYPE_DEC64_FD_MAX)) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, "fraction-digits"); + ret = LY_EVALID; + goto cleanup; + } + type->fraction_digits = num; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, type, LY_STMT_FRACTION_DIGITS, 0, &type->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "fraction-digits"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the require-instance statement. Substatement of type statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] type Type to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_type_reqinstance(struct lysp_yang_ctx *ctx, struct lysp_type *type) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word; + size_t word_len; + enum ly_stmt kw; + + if (type->flags & LYS_SET_REQINST) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "require-instance"); + return LY_EVALID; + } + type->flags |= LYS_SET_REQINST; + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if ((word_len == ly_strlen_const("true")) && !strncmp(word, "true", word_len)) { + type->require_instance = 1; + } else if ((word_len != ly_strlen_const("false")) || strncmp(word, "false", word_len)) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "require-instance"); + ret = LY_EVALID; + goto cleanup; + } + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, type, LY_STMT_REQUIRE_INSTANCE, 0, &type->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "require-instance"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the modifier statement. Substatement of type pattern statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] restr Restriction to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_type_pattern_modifier(struct lysp_yang_ctx *ctx, struct lysp_restr *restr) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word; + size_t word_len; + enum ly_stmt kw; + + if (restr->arg.str[0] == LYSP_RESTR_PATTERN_NACK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "modifier"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if ((word_len != ly_strlen_const("invert-match")) || strncmp(word, "invert-match", word_len)) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "modifier"); + ret = LY_EVALID; + goto cleanup; + } + + /* replace the value in the dictionary */ + buf = malloc(strlen(restr->arg.str) + 1); + LY_CHECK_ERR_GOTO(!buf, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + strcpy(buf, restr->arg.str); + lydict_remove(PARSER_CTX(ctx), restr->arg.str); + + assert(buf[0] == LYSP_RESTR_PATTERN_ACK); + buf[0] = LYSP_RESTR_PATTERN_NACK; + ret = lydict_insert_zc(PARSER_CTX(ctx), buf, &restr->arg.str); + buf = NULL; + LY_CHECK_GOTO(ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, restr, LY_STMT_MODIFIER, 0, &restr->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "modifier"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the pattern statement. Substatement of type statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] patterns Restrictions to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_type_pattern(struct lysp_yang_ctx *ctx, struct lysp_restr **patterns) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_restr *restr; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *patterns, restr, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + /* add special meaning first byte */ + if (buf) { + buf = ly_realloc(buf, word_len + 2); + word = buf; + } else { + buf = malloc(word_len + 2); + } + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + if (word_len) { + memmove(buf + 1, word, word_len); + } + buf[0] = LYSP_RESTR_PATTERN_ACK; /* pattern's default regular-match flag */ + buf[word_len + 1] = '\0'; /* terminating NULL byte */ + LY_CHECK_RET(lydict_insert_zc(PARSER_CTX(ctx), buf, &restr->arg.str)); + restr->arg.mod = PARSER_CUR_PMOD(ctx); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, restr->dsc, LY_STMT_DESCRIPTION, 0, &restr->dsc, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, restr->ref, LY_STMT_REFERENCE, 0, &restr->ref, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_APP_TAG: + LY_CHECK_RET(parse_text_field(ctx, restr, LY_STMT_ERROR_APP_TAG, 0, &restr->eapptag, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_ERROR_MESSAGE: + LY_CHECK_RET(parse_text_field(ctx, restr, LY_STMT_ERROR_MESSAGE, 0, &restr->emsg, Y_STR_ARG, &restr->exts)); + break; + case LY_STMT_MODIFIER: + PARSER_CHECK_STMTVER2_RET(ctx, "modifier", "pattern"); + LY_CHECK_RET(parse_type_pattern_modifier(ctx, restr)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, restr, LY_STMT_PATTERN, 0, &restr->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "pattern"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, restr->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the type statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] type Type to wrote to. + * @return LY_ERR values. + */ +static LY_ERR +parse_type(struct lysp_yang_ctx *ctx, struct lysp_type *type) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + const char *str_path = NULL; + size_t word_len; + enum ly_stmt kw; + struct lysp_type *nest_type; + + if (type->name) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "type"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_PREF_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, type->name, word, word_len, ret, cleanup); + + /* set module */ + type->pmod = PARSER_CUR_PMOD(ctx); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_BASE: + LY_CHECK_RET(parse_text_fields(ctx, LY_STMT_BASE, &type->bases, Y_PREF_IDENTIF_ARG, &type->exts)); + type->flags |= LYS_SET_BASE; + break; + case LY_STMT_BIT: + LY_CHECK_RET(parse_type_enum(ctx, kw, &type->bits)); + type->flags |= LYS_SET_BIT; + break; + case LY_STMT_ENUM: + LY_CHECK_RET(parse_type_enum(ctx, kw, &type->enums)); + type->flags |= LYS_SET_ENUM; + break; + case LY_STMT_FRACTION_DIGITS: + LY_CHECK_RET(parse_type_fracdigits(ctx, type)); + type->flags |= LYS_SET_FRDIGITS; + break; + case LY_STMT_LENGTH: + if (type->length) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(kw)); + return LY_EVALID; + } + type->length = calloc(1, sizeof *type->length); + LY_CHECK_ERR_RET(!type->length, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + + LY_CHECK_RET(parse_restr(ctx, kw, type->length)); + type->flags |= LYS_SET_LENGTH; + break; + case LY_STMT_PATH: + if (type->path) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(LY_STMT_PATH)); + return LY_EVALID; + } + + /* Usually, in the parser_yang.c, the result of the parsing is stored directly in the + * corresponding structure, so in case of failure, the lysp_module_free function will take + * care of removing the parsed value from the dictionary. But in this case, it is not possible + * to rely on lysp_module_free because the result of the parsing is stored in a local variable. + */ + LY_CHECK_ERR_RET(ret = parse_text_field(ctx, type, LY_STMT_PATH, 0, &str_path, Y_STR_ARG, &type->exts), + lydict_remove(PARSER_CTX(ctx), str_path), ret); + ret = ly_path_parse(PARSER_CTX(ctx), NULL, str_path, 0, 1, LY_PATH_BEGIN_EITHER, + LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &type->path); + /* Moreover, even if successful, the string is removed from the dictionary. */ + lydict_remove(PARSER_CTX(ctx), str_path); + LY_CHECK_RET(ret); + type->flags |= LYS_SET_PATH; + break; + case LY_STMT_PATTERN: + LY_CHECK_RET(parse_type_pattern(ctx, &type->patterns)); + type->flags |= LYS_SET_PATTERN; + break; + case LY_STMT_RANGE: + if (type->range) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(kw)); + return LY_EVALID; + } + type->range = calloc(1, sizeof *type->range); + LY_CHECK_ERR_RET(!type->range, LOGMEM(PARSER_CTX(ctx)), LY_EMEM); + + LY_CHECK_RET(parse_restr(ctx, kw, type->range)); + type->flags |= LYS_SET_RANGE; + break; + case LY_STMT_REQUIRE_INSTANCE: + LY_CHECK_RET(parse_type_reqinstance(ctx, type)); + /* LYS_SET_REQINST checked and set inside parse_type_reqinstance() */ + break; + case LY_STMT_TYPE: + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), type->types, nest_type, LY_EMEM); + LY_CHECK_RET(parse_type(ctx, nest_type)); + type->flags |= LYS_SET_TYPE; + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, type, LY_STMT_TYPE, 0, &type->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "type"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, type->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the leaf statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_leaf(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_leaf *leaf; + + /* create new leaf structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, leaf, next, LY_EMEM); + leaf->nodetype = LYS_LEAF; + leaf->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, leaf->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &leaf->flags, &leaf->exts)); + break; + case LY_STMT_DEFAULT: + LY_CHECK_RET(parse_text_field(ctx, &leaf->dflt, LY_STMT_DEFAULT, 0, &leaf->dflt.str, Y_STR_ARG, &leaf->exts)); + leaf->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, leaf->dsc, LY_STMT_DESCRIPTION, 0, &leaf->dsc, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &leaf->iffeatures, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(parse_mandatory(ctx, &leaf->flags, &leaf->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(parse_restrs(ctx, kw, &leaf->musts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, leaf->ref, LY_STMT_REFERENCE, 0, &leaf->ref, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &leaf->flags, &leaf->exts)); + break; + case LY_STMT_TYPE: + LY_CHECK_RET(parse_type(ctx, &leaf->type)); + break; + case LY_STMT_UNITS: + LY_CHECK_RET(parse_text_field(ctx, leaf->units, LY_STMT_UNITS, 0, &leaf->units, Y_STR_ARG, &leaf->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &leaf->when)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, leaf, LY_STMT_LEAF, 0, &leaf->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "leaf"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, leaf->exts, ret, cleanup); + } + + /* mandatory substatements */ + if (!leaf->type.name) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "type", "leaf"); + return LY_EVALID; + } + +cleanup: + return ret; +} + +/** + * @brief Parse the max-elements statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] max Value to write to. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_maxelements(struct lysp_yang_ctx *ctx, uint32_t *max, uint16_t *flags, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word, *ptr; + size_t word_len; + unsigned long long num; + enum ly_stmt kw; + + if (*flags & LYS_SET_MAX) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "max-elements"); + return LY_EVALID; + } + *flags |= LYS_SET_MAX; + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if (!word_len || (word[0] == '0') || ((word[0] != 'u') && !isdigit(word[0]))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + + if (ly_strncmp("unbounded", word, word_len)) { + errno = 0; + num = strtoull(word, &ptr, LY_BASE_DEC); + /* we have not parsed the whole argument */ + if ((size_t)(ptr - word) != word_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + if ((errno == ERANGE) || (num > UINT32_MAX)) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + + *max = num; + } else { + /* unbounded */ + *max = 0; + } + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, max, LY_STMT_MAX_ELEMENTS, 0, exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the min-elements statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] min Value to write to. + * @param[in,out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_minelements(struct lysp_yang_ctx *ctx, uint32_t *min, uint16_t *flags, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word, *ptr; + size_t word_len; + unsigned long long num; + enum ly_stmt kw; + + if (*flags & LYS_SET_MIN) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "min-elements"); + return LY_EVALID; + } + *flags |= LYS_SET_MIN; + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if (!word_len || !isdigit(word[0]) || ((word[0] == '0') && (word_len > 1))) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "min-elements"); + ret = LY_EVALID; + goto cleanup; + } + + errno = 0; + num = strtoull(word, &ptr, LY_BASE_DEC); + /* we have not parsed the whole argument */ + if ((size_t)(ptr - word) != word_len) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "min-elements"); + ret = LY_EVALID; + goto cleanup; + } + if ((errno == ERANGE) || (num > UINT32_MAX)) { + LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, "min-elements"); + ret = LY_EVALID; + goto cleanup; + } + *min = num; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, min, LY_STMT_MIN_ELEMENTS, 0, exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "min-elements"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the ordered-by statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] llist List or leaf-list to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_orderedby(struct lysp_yang_ctx *ctx, struct lysp_node *llist) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word; + size_t word_len; + enum ly_stmt kw; + + if (llist->flags & LYS_ORDBY_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "ordered-by"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if ((word_len == ly_strlen_const("system")) && !strncmp(word, "system", word_len)) { + llist->flags |= LYS_ORDBY_SYSTEM; + } else if ((word_len == ly_strlen_const("user")) && !strncmp(word, "user", word_len)) { + llist->flags |= LYS_ORDBY_USER; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "ordered-by"); + ret = LY_EVALID; + goto cleanup; + } + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, llist, LY_STMT_ORDERED_BY, 0, &llist->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "ordered-by"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + free(buf); + return ret; +} + +/** + * @brief Parse the leaf-list statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * + * @return LY_ERR values. + */ +LY_ERR +parse_leaflist(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_leaflist *llist; + + /* create new leaf-list structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, llist, next, LY_EMEM); + llist->nodetype = LYS_LEAFLIST; + llist->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, llist->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &llist->flags, &llist->exts)); + break; + case LY_STMT_DEFAULT: + PARSER_CHECK_STMTVER2_RET(ctx, "default", "leaf-list"); + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_DEFAULT, &llist->dflts, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, llist->dsc, LY_STMT_DESCRIPTION, 0, &llist->dsc, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &llist->iffeatures, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_MAX_ELEMENTS: + LY_CHECK_RET(parse_maxelements(ctx, &llist->max, &llist->flags, &llist->exts)); + break; + case LY_STMT_MIN_ELEMENTS: + LY_CHECK_RET(parse_minelements(ctx, &llist->min, &llist->flags, &llist->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(parse_restrs(ctx, kw, &llist->musts)); + break; + case LY_STMT_ORDERED_BY: + LY_CHECK_RET(parse_orderedby(ctx, &llist->node)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, llist->ref, LY_STMT_REFERENCE, 0, &llist->ref, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &llist->flags, &llist->exts)); + break; + case LY_STMT_TYPE: + LY_CHECK_RET(parse_type(ctx, &llist->type)); + break; + case LY_STMT_UNITS: + LY_CHECK_RET(parse_text_field(ctx, llist->units, LY_STMT_UNITS, 0, &llist->units, Y_STR_ARG, &llist->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &llist->when)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, llist, LY_STMT_LEAF_LIST, 0, &llist->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "llist"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, llist->exts, ret, cleanup); + } + + /* mandatory substatements */ + if (!llist->type.name) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "type", "leaf-list"); + return LY_EVALID; + } + +cleanup: + return ret; +} + +/** + * @brief Parse the refine statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] refines Refines to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_refine(struct lysp_yang_ctx *ctx, struct lysp_refine **refines) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_refine *rf; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *refines, rf, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + CHECK_NONEMPTY(ctx, word_len, "refine"); + INSERT_WORD_GOTO(ctx, buf, rf->nodeid, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &rf->flags, &rf->exts)); + break; + case LY_STMT_DEFAULT: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_DEFAULT, &rf->dflts, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, rf->dsc, LY_STMT_DESCRIPTION, 0, &rf->dsc, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_IF_FEATURE: + PARSER_CHECK_STMTVER2_RET(ctx, "if-feature", "refine"); + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &rf->iffeatures, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_MAX_ELEMENTS: + LY_CHECK_RET(parse_maxelements(ctx, &rf->max, &rf->flags, &rf->exts)); + break; + case LY_STMT_MIN_ELEMENTS: + LY_CHECK_RET(parse_minelements(ctx, &rf->min, &rf->flags, &rf->exts)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(parse_restrs(ctx, kw, &rf->musts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(parse_mandatory(ctx, &rf->flags, &rf->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, rf->ref, LY_STMT_REFERENCE, 0, &rf->ref, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_PRESENCE: + LY_CHECK_RET(parse_text_field(ctx, rf->presence, LY_STMT_PRESENCE, 0, &rf->presence, Y_STR_ARG, &rf->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, rf, LY_STMT_REFINE, 0, &rf->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "refine"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, rf->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the typedef statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] typedefs Typedefs to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_typedef(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_tpdf **typedefs) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_tpdf *tpdf; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *typedefs, tpdf, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, tpdf->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DEFAULT: + LY_CHECK_RET(parse_text_field(ctx, &tpdf->dflt, LY_STMT_DEFAULT, 0, &tpdf->dflt.str, Y_STR_ARG, &tpdf->exts)); + tpdf->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, tpdf->dsc, LY_STMT_DESCRIPTION, 0, &tpdf->dsc, Y_STR_ARG, &tpdf->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, tpdf->ref, LY_STMT_REFERENCE, 0, &tpdf->ref, Y_STR_ARG, &tpdf->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &tpdf->flags, &tpdf->exts)); + break; + case LY_STMT_TYPE: + LY_CHECK_RET(parse_type(ctx, &tpdf->type)); + break; + case LY_STMT_UNITS: + LY_CHECK_RET(parse_text_field(ctx, tpdf->units, LY_STMT_UNITS, 0, &tpdf->units, Y_STR_ARG, &tpdf->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, tpdf, LY_STMT_TYPEDEF, 0, &tpdf->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "typedef"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, tpdf->exts, ret, cleanup); + } + + /* mandatory substatements */ + if (!tpdf->type.name) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "type", "typedef"); + return LY_EVALID; + } + + /* store data for collision check */ + if (parent) { + assert(ctx->main_ctx); + LY_CHECK_RET(ly_set_add(&ctx->main_ctx->tpdfs_nodes, parent, 0, NULL)); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the input or output statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in] kw Type of this particular keyword + * @param[in,out] inout_p Input/output pointer to write to. + * @return LY_ERR values. + */ +static LY_ERR +parse_inout(struct lysp_yang_ctx *ctx, enum ly_stmt inout_kw, struct lysp_node *parent, + struct lysp_node_action_inout *inout_p) +{ + LY_ERR ret = LY_SUCCESS; + char *word; + size_t word_len; + enum ly_stmt kw; + ly_bool input = &((struct lysp_node_action *)parent)->input == inout_p ? 1 : 0; + + if (inout_p->nodetype) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(inout_kw)); + return LY_EVALID; + } + + /* initiate structure */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), input ? "input" : "output", 0, &inout_p->name)); + inout_p->nodetype = input ? LYS_INPUT : LYS_OUTPUT; + inout_p->parent = parent; + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", lyplg_ext_stmt2str(inout_kw)); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)inout_p, &inout_p->child)); + break; + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, (struct lysp_node *)inout_p, &inout_p->typedefs)); + break; + case LY_STMT_MUST: + PARSER_CHECK_STMTVER2_RET(ctx, "must", lyplg_ext_stmt2str(inout_kw)); + LY_CHECK_RET(parse_restrs(ctx, kw, &inout_p->musts)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, (struct lysp_node *)inout_p, &inout_p->groupings)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, inout_p, inout_kw, 0, &inout_p->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(inout_kw)); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, inout_p->exts, ret, cleanup); + } + + if (!inout_p->child) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "data-def-stmt", lyplg_ext_stmt2str(inout_kw)); + return LY_EVALID; + } + +cleanup: + return ret; +} + +/** + * @brief Parse the action statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] actions Actions to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_action(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node_action **actions) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_action *act; + + LY_LIST_NEW_RET(PARSER_CTX(ctx), actions, act, next, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, act->name, word, word_len, ret, cleanup); + act->nodetype = parent ? LYS_ACTION : LYS_RPC; + act->parent = parent; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, act->dsc, LY_STMT_DESCRIPTION, 0, &act->dsc, Y_STR_ARG, &act->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &act->iffeatures, Y_STR_ARG, &act->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, act->ref, LY_STMT_REFERENCE, 0, &act->ref, Y_STR_ARG, &act->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &act->flags, &act->exts)); + break; + + case LY_STMT_INPUT: + LY_CHECK_RET(parse_inout(ctx, kw, (struct lysp_node *)act, &act->input)); + break; + case LY_STMT_OUTPUT: + LY_CHECK_RET(parse_inout(ctx, kw, (struct lysp_node *)act, &act->output)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, (struct lysp_node *)act, &act->typedefs)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, (struct lysp_node *)act, &act->groupings)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, act, parent ? LY_STMT_ACTION : LY_STMT_RPC, 0, &act->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), parent ? "action" : "rpc"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, act->exts, ret, cleanup); + } + + /* always initialize inout, they are technically present (needed for later deviations/refines) */ + if (!act->input.nodetype) { + act->input.nodetype = LYS_INPUT; + act->input.parent = (struct lysp_node *)act; + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), "input", 0, &act->input.name)); + } + if (!act->output.nodetype) { + act->output.nodetype = LYS_OUTPUT; + act->output.parent = (struct lysp_node *)act; + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), "output", 0, &act->output.name)); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the notification statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] notifs Notifications to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_notif(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node_notif **notifs) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_notif *notif; + + LY_LIST_NEW_RET(PARSER_CTX(ctx), notifs, notif, next, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, notif->name, word, word_len, ret, cleanup); + notif->nodetype = LYS_NOTIF; + notif->parent = parent; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, notif->dsc, LY_STMT_DESCRIPTION, 0, ¬if->dsc, Y_STR_ARG, ¬if->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, ¬if->iffeatures, Y_STR_ARG, ¬if->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, notif->ref, LY_STMT_REFERENCE, 0, ¬if->ref, Y_STR_ARG, ¬if->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, ¬if->flags, ¬if->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "notification"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)notif, ¬if->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)notif, ¬if->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)notif, ¬if->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)notif, ¬if->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)notif, ¬if->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)notif, ¬if->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)notif, ¬if->child)); + break; + + case LY_STMT_MUST: + PARSER_CHECK_STMTVER2_RET(ctx, "must", "notification"); + LY_CHECK_RET(parse_restrs(ctx, kw, ¬if->musts)); + break; + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, (struct lysp_node *)notif, ¬if->typedefs)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, (struct lysp_node *)notif, ¬if->groupings)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, notif, LY_STMT_NOTIFICATION, 0, ¬if->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "notification"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, notif->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the grouping statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] groupings Groupings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_grouping(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node_grp **groupings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_grp *grp; + + LY_LIST_NEW_RET(PARSER_CTX(ctx), groupings, grp, next, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, grp->name, word, word_len, ret, cleanup); + grp->nodetype = LYS_GROUPING; + grp->parent = parent; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, grp->dsc, LY_STMT_DESCRIPTION, 0, &grp->dsc, Y_STR_ARG, &grp->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, grp->ref, LY_STMT_REFERENCE, 0, &grp->ref, Y_STR_ARG, &grp->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &grp->flags, &grp->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "grouping"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, &grp->node, &grp->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, &grp->node, &grp->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, &grp->node, &grp->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, &grp->node, &grp->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, &grp->node, &grp->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, &grp->node, &grp->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, &grp->node, &grp->child)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, &grp->node, &grp->typedefs)); + break; + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "grouping"); + LY_CHECK_RET(parse_action(ctx, &grp->node, &grp->actions)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, &grp->node, &grp->groupings)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "grouping"); + LY_CHECK_RET(parse_notif(ctx, &grp->node, &grp->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, grp, LY_STMT_GROUPING, 0, &grp->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "grouping"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, grp->exts, ret, cleanup); + } + + /* store data for collision check */ + if (parent) { + assert(ctx->main_ctx); + LY_CHECK_RET(ly_set_add(&ctx->main_ctx->grps_nodes, parent, 0, NULL)); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the augment statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] augments Augments to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_augment(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node_augment **augments) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_augment *aug; + + LY_LIST_NEW_RET(PARSER_CTX(ctx), augments, aug, next, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + CHECK_NONEMPTY(ctx, word_len, "augment"); + INSERT_WORD_GOTO(ctx, buf, aug->nodeid, word, word_len, ret, cleanup); + aug->nodetype = LYS_AUGMENT; + aug->parent = parent; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, aug->dsc, LY_STMT_DESCRIPTION, 0, &aug->dsc, Y_STR_ARG, &aug->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &aug->iffeatures, Y_STR_ARG, &aug->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, aug->ref, LY_STMT_REFERENCE, 0, &aug->ref, Y_STR_ARG, &aug->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &aug->flags, &aug->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &aug->when)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "augment"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_CASE: + LY_CHECK_RET(parse_case(ctx, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)aug, &aug->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)aug, &aug->child)); + break; + + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "augment"); + LY_CHECK_RET(parse_action(ctx, (struct lysp_node *)aug, &aug->actions)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "augment"); + LY_CHECK_RET(parse_notif(ctx, (struct lysp_node *)aug, &aug->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, aug, LY_STMT_AUGMENT, 0, &aug->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "augment"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, aug->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the uses statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_uses(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_uses *uses; + + /* create uses structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, uses, next, LY_EMEM); + uses->nodetype = LYS_USES; + uses->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_PREF_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, uses->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, uses->dsc, LY_STMT_DESCRIPTION, 0, &uses->dsc, Y_STR_ARG, &uses->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &uses->iffeatures, Y_STR_ARG, &uses->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, uses->ref, LY_STMT_REFERENCE, 0, &uses->ref, Y_STR_ARG, &uses->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &uses->flags, &uses->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &uses->when)); + break; + + case LY_STMT_REFINE: + LY_CHECK_RET(parse_refine(ctx, &uses->refines)); + break; + case LY_STMT_AUGMENT: + LY_CHECK_RET(parse_augment(ctx, (struct lysp_node *)uses, &uses->augments)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, uses, LY_STMT_USES, 0, &uses->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "uses"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, uses->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the case statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_case(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_case *cas; + + /* create new case structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, cas, next, LY_EMEM); + cas->nodetype = LYS_CASE; + cas->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, cas->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, cas->dsc, LY_STMT_DESCRIPTION, 0, &cas->dsc, Y_STR_ARG, &cas->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &cas->iffeatures, Y_STR_ARG, &cas->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, cas->ref, LY_STMT_REFERENCE, 0, &cas->ref, Y_STR_ARG, &cas->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &cas->flags, &cas->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &cas->when)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "case"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)cas, &cas->child)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, cas, LY_STMT_CASE, 0, &cas->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "case"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, cas->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the choice statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_choice(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_choice *choice; + + /* create new choice structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, choice, next, LY_EMEM); + choice->nodetype = LYS_CHOICE; + choice->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, choice->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &choice->flags, &choice->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, choice->dsc, LY_STMT_DESCRIPTION, 0, &choice->dsc, Y_STR_ARG, &choice->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &choice->iffeatures, Y_STR_ARG, &choice->exts)); + break; + case LY_STMT_MANDATORY: + LY_CHECK_RET(parse_mandatory(ctx, &choice->flags, &choice->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, choice->ref, LY_STMT_REFERENCE, 0, &choice->ref, Y_STR_ARG, &choice->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &choice->flags, &choice->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &choice->when)); + break; + case LY_STMT_DEFAULT: + LY_CHECK_RET(parse_text_field(ctx, &choice->dflt, LY_STMT_DEFAULT, 0, &choice->dflt.str, Y_PREF_IDENTIF_ARG, + &choice->exts)); + choice->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "choice"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_CASE: + LY_CHECK_RET(parse_case(ctx, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_CHOICE: + PARSER_CHECK_STMTVER2_RET(ctx, "choice", "choice"); + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)choice, &choice->child)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, choice, LY_STMT_CHOICE, 0, &choice->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "choice"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, choice->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the container statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_container(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = 0; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_container *cont; + + /* create new container structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, cont, next, LY_EMEM); + cont->nodetype = LYS_CONTAINER; + cont->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, cont->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &cont->flags, &cont->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, cont->dsc, LY_STMT_DESCRIPTION, 0, &cont->dsc, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &cont->iffeatures, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, cont->ref, LY_STMT_REFERENCE, 0, &cont->ref, Y_STR_ARG, &cont->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &cont->flags, &cont->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &cont->when)); + break; + case LY_STMT_PRESENCE: + LY_CHECK_RET(parse_text_field(ctx, cont->presence, LY_STMT_PRESENCE, 0, &cont->presence, Y_STR_ARG, &cont->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "container"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)cont, &cont->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)cont, &cont->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)cont, &cont->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)cont, &cont->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)cont, &cont->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)cont, &cont->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)cont, &cont->child)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, (struct lysp_node *)cont, &cont->typedefs)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(parse_restrs(ctx, kw, &cont->musts)); + break; + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "container"); + LY_CHECK_RET(parse_action(ctx, (struct lysp_node *)cont, &cont->actions)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, (struct lysp_node *)cont, &cont->groupings)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "container"); + LY_CHECK_RET(parse_notif(ctx, (struct lysp_node *)cont, &cont->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, cont, LY_STMT_CONTAINER, 0, &cont->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "container"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, cont->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the list statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] siblings Siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_list(struct lysp_yang_ctx *ctx, struct lysp_node *parent, struct lysp_node **siblings) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_node_list *list; + + /* create new list structure */ + LY_LIST_NEW_RET(PARSER_CTX(ctx), siblings, list, next, LY_EMEM); + list->nodetype = LYS_LIST; + list->parent = parent; + + /* get name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, list->name, word, word_len, ret, cleanup); + + /* parse substatements */ + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + LY_CHECK_RET(parse_config(ctx, &list->flags, &list->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, list->dsc, LY_STMT_DESCRIPTION, 0, &list->dsc, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &list->iffeatures, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, list->ref, LY_STMT_REFERENCE, 0, &list->ref, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &list->flags, &list->exts)); + break; + case LY_STMT_WHEN: + LY_CHECK_RET(parse_when(ctx, &list->when)); + break; + case LY_STMT_KEY: + LY_CHECK_RET(parse_text_field(ctx, list, LY_STMT_KEY, 0, &list->key, Y_STR_ARG, &list->exts)); + break; + case LY_STMT_MAX_ELEMENTS: + LY_CHECK_RET(parse_maxelements(ctx, &list->max, &list->flags, &list->exts)); + break; + case LY_STMT_MIN_ELEMENTS: + LY_CHECK_RET(parse_minelements(ctx, &list->min, &list->flags, &list->exts)); + break; + case LY_STMT_ORDERED_BY: + LY_CHECK_RET(parse_orderedby(ctx, &list->node)); + break; + case LY_STMT_UNIQUE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_UNIQUE, &list->uniques, Y_STR_ARG, &list->exts)); + break; + + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "list"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, (struct lysp_node *)list, &list->child)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)list, &list->child)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)list, &list->child)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)list, &list->child)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)list, &list->child)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)list, &list->child)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)list, &list->child)); + break; + + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, (struct lysp_node *)list, &list->typedefs)); + break; + case LY_STMT_MUST: + LY_CHECK_RET(parse_restrs(ctx, kw, &list->musts)); + break; + case LY_STMT_ACTION: + PARSER_CHECK_STMTVER2_RET(ctx, "action", "list"); + LY_CHECK_RET(parse_action(ctx, (struct lysp_node *)list, &list->actions)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, (struct lysp_node *)list, &list->groupings)); + break; + case LY_STMT_NOTIFICATION: + PARSER_CHECK_STMTVER2_RET(ctx, "notification", "list"); + LY_CHECK_RET(parse_notif(ctx, (struct lysp_node *)list, &list->notifs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, list, LY_STMT_LIST, 0, &list->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "list"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, list->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the yin-element statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] ext Extension to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_yinelement(struct lysp_yang_ctx *ctx, struct lysp_ext *ext) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (ext->flags & LYS_YINELEM_MASK) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "yin-element"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); + + if ((word_len == ly_strlen_const("true")) && !strncmp(word, "true", word_len)) { + ext->flags |= LYS_YINELEM_TRUE; + } else if ((word_len == ly_strlen_const("false")) && !strncmp(word, "false", word_len)) { + ext->flags |= LYS_YINELEM_FALSE; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "yin-element"); + free(buf); + return LY_EVALID; + } + free(buf); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, ext, LY_STMT_YIN_ELEMENT, 0, &ext->exts)); + LY_CHECK_RET(ret); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "yin-element"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the argument statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] ext Extension to fill. + * @return LY_ERR values. + */ +static LY_ERR +parse_argument(struct lysp_yang_ctx *ctx, struct lysp_ext *ext) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + + if (ext->argname) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "argument"); + return LY_EVALID; + } + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, ext->argname, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_YIN_ELEMENT: + LY_CHECK_RET(parse_yinelement(ctx, ext)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, ext, LY_STMT_ARGUMENT, 0, &ext->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "argument"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, NULL, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the extension statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] extensions Extensions to add to. + * @return LY_ERR values. + */ +static LY_ERR +parse_extension(struct lysp_yang_ctx *ctx, struct lysp_ext **extensions) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_ext *ex; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *extensions, ex, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, ex->name, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, ex->dsc, LY_STMT_DESCRIPTION, 0, &ex->dsc, Y_STR_ARG, &ex->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, ex->ref, LY_STMT_REFERENCE, 0, &ex->ref, Y_STR_ARG, &ex->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &ex->flags, &ex->exts)); + break; + case LY_STMT_ARGUMENT: + LY_CHECK_RET(parse_argument(ctx, ex)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, ex, LY_STMT_EXTENSION, 0, &ex->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "extension"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, ex->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the deviate statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] deviates Deviates to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_deviate(struct lysp_yang_ctx *ctx, struct lysp_deviate **deviates) +{ + LY_ERR ret = LY_SUCCESS; + char *buf = NULL, *word; + size_t word_len, dev_mod; + enum ly_stmt kw; + struct lysf_ctx fctx = {.ctx = PARSER_CTX(ctx)}; + struct lysp_deviate *d = NULL; + struct lysp_deviate_add *d_add = NULL; + struct lysp_deviate_rpl *d_rpl = NULL; + struct lysp_deviate_del *d_del = NULL; + const char **d_units = NULL; + struct lysp_qname **d_uniques = NULL, **d_dflts = NULL; + struct lysp_restr **d_musts = NULL; + uint16_t *d_flags = 0; + uint32_t *d_min = 0, *d_max = 0; + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + + if ((word_len == ly_strlen_const("not-supported")) && !strncmp(word, "not-supported", word_len)) { + dev_mod = LYS_DEV_NOT_SUPPORTED; + } else if ((word_len == ly_strlen_const("add")) && !strncmp(word, "add", word_len)) { + dev_mod = LYS_DEV_ADD; + } else if ((word_len == ly_strlen_const("replace")) && !strncmp(word, "replace", word_len)) { + dev_mod = LYS_DEV_REPLACE; + } else if ((word_len == ly_strlen_const("delete")) && !strncmp(word, "delete", word_len)) { + dev_mod = LYS_DEV_DELETE; + } else { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "deviate"); + ret = LY_EVALID; + goto cleanup; + } + + /* create structure */ + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + d = calloc(1, sizeof *d); + LY_CHECK_ERR_GOTO(!d, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + break; + case LYS_DEV_ADD: + d_add = calloc(1, sizeof *d_add); + LY_CHECK_ERR_GOTO(!d_add, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + d = (struct lysp_deviate *)d_add; + d_units = &d_add->units; + d_uniques = &d_add->uniques; + d_dflts = &d_add->dflts; + d_musts = &d_add->musts; + d_flags = &d_add->flags; + d_min = &d_add->min; + d_max = &d_add->max; + break; + case LYS_DEV_REPLACE: + d_rpl = calloc(1, sizeof *d_rpl); + LY_CHECK_ERR_GOTO(!d_rpl, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + d = (struct lysp_deviate *)d_rpl; + d_units = &d_rpl->units; + d_flags = &d_rpl->flags; + d_min = &d_rpl->min; + d_max = &d_rpl->max; + break; + case LYS_DEV_DELETE: + d_del = calloc(1, sizeof *d_del); + LY_CHECK_ERR_GOTO(!d_del, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + d = (struct lysp_deviate *)d_del; + d_units = &d_del->units; + d_uniques = &d_del->uniques; + d_dflts = &d_del->dflts; + d_musts = &d_del->musts; + break; + default: + assert(0); + LOGINT(PARSER_CTX(ctx)); + ret = LY_EINT; + goto cleanup; + } + d->mod = dev_mod; + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_CONFIG: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_DELETE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_config(ctx, d_flags, &d->exts), cleanup); + break; + } + break; + case LY_STMT_DEFAULT: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + case LYS_DEV_REPLACE: + ret = parse_text_field(ctx, &d_rpl->dflt, LY_STMT_DEFAULT, 0, &d_rpl->dflt.str, Y_STR_ARG, &d->exts); + LY_CHECK_GOTO(ret, cleanup); + d_rpl->dflt.mod = PARSER_CUR_PMOD(ctx); + break; + default: + LY_CHECK_GOTO(ret = parse_qnames(ctx, LY_STMT_DEFAULT, d_dflts, Y_STR_ARG, &d->exts), cleanup); + break; + } + break; + case LY_STMT_MANDATORY: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_DELETE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_mandatory(ctx, d_flags, &d->exts), cleanup); + break; + } + break; + case LY_STMT_MAX_ELEMENTS: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_DELETE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_maxelements(ctx, d_max, d_flags, &d->exts), cleanup); + break; + } + break; + case LY_STMT_MIN_ELEMENTS: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_DELETE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_minelements(ctx, d_min, d_flags, &d->exts), cleanup); + break; + } + break; + case LY_STMT_MUST: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_REPLACE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_restrs(ctx, kw, d_musts), cleanup); + break; + } + break; + case LY_STMT_TYPE: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_ADD: + case LYS_DEV_DELETE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + if (d_rpl->type) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + } + d_rpl->type = calloc(1, sizeof *d_rpl->type); + LY_CHECK_ERR_GOTO(!d_rpl->type, LOGMEM(PARSER_CTX(ctx)); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = parse_type(ctx, d_rpl->type), cleanup); + break; + } + break; + case LY_STMT_UNIQUE: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + case LYS_DEV_REPLACE: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_qnames(ctx, LY_STMT_UNIQUE, d_uniques, Y_STR_ARG, &d->exts), cleanup); + break; + } + break; + case LY_STMT_UNITS: + switch (dev_mod) { + case LYS_DEV_NOT_SUPPORTED: + LOGVAL_PARSER(ctx, LY_VCODE_INDEV, ly_devmod2str(dev_mod), lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + default: + LY_CHECK_GOTO(ret = parse_text_field(ctx, *d_units, LY_STMT_UNITS, 0, d_units, Y_STR_ARG, &d->exts), cleanup); + break; + } + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, d, LY_STMT_DEVIATE, 0, &d->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "deviate"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, d->exts, ret, cleanup); + } + +cleanup: + free(buf); + if (ret) { + lysp_deviate_free(&fctx, d); + free(d); + } else { + /* insert into siblings */ + LY_LIST_INSERT(deviates, d, next); + } + return ret; +} + +/** + * @brief Parse the deviation statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] deviations Deviations to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_deviation(struct lysp_yang_ctx *ctx, struct lysp_deviation **deviations) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_deviation *dev; + struct lysf_ctx fctx = {.ctx = PARSER_CTX(ctx)}; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *deviations, dev, LY_EMEM); + + /* get value */ + LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); + CHECK_NONEMPTY(ctx, word_len, "deviation"); + INSERT_WORD_GOTO(ctx, buf, dev->nodeid, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_GOTO(ret = parse_text_field(ctx, dev->dsc, LY_STMT_DESCRIPTION, 0, &dev->dsc, Y_STR_ARG, &dev->exts), cleanup); + break; + case LY_STMT_DEVIATE: + LY_CHECK_GOTO(ret = parse_deviate(ctx, &dev->deviates), cleanup); + break; + case LY_STMT_REFERENCE: + LY_CHECK_GOTO(ret = parse_text_field(ctx, dev->ref, LY_STMT_REFERENCE, 0, &dev->ref, Y_STR_ARG, &dev->exts), cleanup); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_GOTO(ret = parse_ext(ctx, word, word_len, dev, LY_STMT_DEVIATION, 0, &dev->exts), cleanup); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "deviation"); + ret = LY_EVALID; + goto cleanup; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, dev->exts, ret, cleanup); + } + + /* mandatory substatements */ + if (!dev->deviates) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "deviate", "deviation"); + ret = LY_EVALID; + goto cleanup; + } + +cleanup: + if (ret) { + lysp_deviation_free(&fctx, dev); + LY_ARRAY_DECREMENT_FREE(*deviations); + } + return ret; +} + +/** + * @brief Parse the feature statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] features Features to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_feature(struct lysp_yang_ctx *ctx, struct lysp_feature **features) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_feature *feat; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *features, feat, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, feat->name, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, feat->dsc, LY_STMT_DESCRIPTION, 0, &feat->dsc, Y_STR_ARG, &feat->exts)); + break; + case LY_STMT_IF_FEATURE: + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &feat->iffeatures, Y_STR_ARG, &feat->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, feat->ref, LY_STMT_REFERENCE, 0, &feat->ref, Y_STR_ARG, &feat->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &feat->flags, &feat->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, feat, LY_STMT_FEATURE, 0, &feat->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "feature"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, feat->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse the identity statement. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] identities Identities to add to. + * @return LY_ERR values. + */ +LY_ERR +parse_identity(struct lysp_yang_ctx *ctx, struct lysp_ident **identities) +{ + LY_ERR ret = LY_SUCCESS; + char *buf, *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_ident *ident; + + LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *identities, ident, LY_EMEM); + + /* get value */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, ident->name, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + switch (kw) { + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, ident->dsc, LY_STMT_DESCRIPTION, 0, &ident->dsc, Y_STR_ARG, &ident->exts)); + break; + case LY_STMT_IF_FEATURE: + PARSER_CHECK_STMTVER2_RET(ctx, "if-feature", "identity"); + LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &ident->iffeatures, Y_STR_ARG, &ident->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, ident->ref, LY_STMT_REFERENCE, 0, &ident->ref, Y_STR_ARG, &ident->exts)); + break; + case LY_STMT_STATUS: + LY_CHECK_RET(parse_status(ctx, &ident->flags, &ident->exts)); + break; + case LY_STMT_BASE: + if (ident->bases && (PARSER_CUR_PMOD(ctx)->version < LYS_VERSION_1_1)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Identity can be derived from multiple base identities only in YANG 1.1 modules"); + return LY_EVALID; + } + LY_CHECK_RET(parse_text_fields(ctx, LY_STMT_BASE, &ident->bases, Y_PREF_IDENTIF_ARG, &ident->exts)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, ident, LY_STMT_IDENTITY, 0, &ident->exts)); + break; + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "identity"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, ident->exts, ret, cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse module substatements. + * + * @param[in] ctx yang parser context for logging. + * @param[in,out] mod Module to write to. + * @return LY_ERR values. + */ +LY_ERR +parse_module(struct lysp_yang_ctx *ctx, struct lysp_module *mod) +{ + LY_ERR ret = 0; + char *buf, *word; + size_t word_len; + enum ly_stmt kw, prev_kw = 0; + enum yang_module_stmt mod_stmt = Y_MOD_MODULE_HEADER; + const struct lysp_submodule *dup; + + mod->is_submod = 0; + + /* module name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, mod->mod->name, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + +#define CHECK_ORDER(SECTION) \ + if (mod_stmt > SECTION) {\ + LOGVAL_PARSER(ctx, LY_VCODE_INORD, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(prev_kw)); return LY_EVALID;\ + } mod_stmt = SECTION + + switch (kw) { + /* module header */ + case LY_STMT_NAMESPACE: + case LY_STMT_PREFIX: + CHECK_ORDER(Y_MOD_MODULE_HEADER); + break; + case LY_STMT_YANG_VERSION: + CHECK_ORDER(Y_MOD_MODULE_HEADER); + break; + /* linkage */ + case LY_STMT_INCLUDE: + case LY_STMT_IMPORT: + CHECK_ORDER(Y_MOD_LINKAGE); + break; + /* meta */ + case LY_STMT_ORGANIZATION: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_REFERENCE: + CHECK_ORDER(Y_MOD_META); + break; + + /* revision */ + case LY_STMT_REVISION: + CHECK_ORDER(Y_MOD_REVISION); + break; + /* body */ + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_AUGMENT: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_DEVIATION: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IDENTITY: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_NOTIFICATION: + case LY_STMT_RPC: + case LY_STMT_TYPEDEF: + case LY_STMT_USES: + mod_stmt = Y_MOD_BODY; + break; + case LY_STMT_EXTENSION_INSTANCE: + /* no place in the statement order defined */ + break; + default: + /* error handled in the next switch */ + break; + } +#undef CHECK_ORDER + + prev_kw = kw; + switch (kw) { + /* module header */ + case LY_STMT_YANG_VERSION: + LY_CHECK_RET(parse_yangversion(ctx, mod)); + break; + case LY_STMT_NAMESPACE: + LY_CHECK_RET(parse_text_field(ctx, mod, LY_STMT_NAMESPACE, 0, &mod->mod->ns, Y_STR_ARG, &mod->exts)); + break; + case LY_STMT_PREFIX: + LY_CHECK_RET(parse_text_field(ctx, mod->mod->prefix, LY_STMT_PREFIX, 0, &mod->mod->prefix, Y_IDENTIF_ARG, &mod->exts)); + break; + + /* linkage */ + case LY_STMT_INCLUDE: + LY_CHECK_RET(parse_include(ctx, mod->mod->name, &mod->includes)); + break; + case LY_STMT_IMPORT: + LY_CHECK_RET(parse_import(ctx, mod->mod->prefix, &mod->imports)); + break; + + /* meta */ + case LY_STMT_ORGANIZATION: + LY_CHECK_RET(parse_text_field(ctx, mod, LY_STMT_ORGANIZATION, 0, &mod->mod->org, Y_STR_ARG, &mod->exts)); + break; + case LY_STMT_CONTACT: + LY_CHECK_RET(parse_text_field(ctx, mod, LY_STMT_CONTACT, 0, &mod->mod->contact, Y_STR_ARG, &mod->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, mod->mod->dsc, LY_STMT_DESCRIPTION, 0, &mod->mod->dsc, Y_STR_ARG, &mod->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, mod->mod->ref, LY_STMT_REFERENCE, 0, &mod->mod->ref, Y_STR_ARG, &mod->exts)); + break; + + /* revision */ + case LY_STMT_REVISION: + LY_CHECK_RET(parse_revision(ctx, &mod->revs)); + break; + + /* body */ + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "module"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, NULL, &mod->data)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, NULL, &mod->data)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, NULL, &mod->data)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, NULL, &mod->data)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, NULL, &mod->data)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, NULL, &mod->data)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, NULL, &mod->data)); + break; + + case LY_STMT_AUGMENT: + LY_CHECK_RET(parse_augment(ctx, NULL, &mod->augments)); + break; + case LY_STMT_DEVIATION: + LY_CHECK_RET(parse_deviation(ctx, &mod->deviations)); + break; + case LY_STMT_EXTENSION: + LY_CHECK_RET(parse_extension(ctx, &mod->extensions)); + break; + case LY_STMT_FEATURE: + LY_CHECK_RET(parse_feature(ctx, &mod->features)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, NULL, &mod->groupings)); + break; + case LY_STMT_IDENTITY: + LY_CHECK_RET(parse_identity(ctx, &mod->identities)); + break; + case LY_STMT_NOTIFICATION: + LY_CHECK_RET(parse_notif(ctx, NULL, &mod->notifs)); + break; + case LY_STMT_RPC: + LY_CHECK_RET(parse_action(ctx, NULL, &mod->rpcs)); + break; + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, NULL, &mod->typedefs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, mod, LY_STMT_MODULE, 0, &mod->exts)); + break; + + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "module"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, mod->exts, ret, cleanup); + } + + /* mandatory substatements */ + if (!mod->mod->ns) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "namespace", "module"); + return LY_EVALID; + } else if (!mod->mod->prefix) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "prefix", "module"); + return LY_EVALID; + } + + /* submodules share the namespace with the module names, so there must not be + * a submodule of the same name in the context, no need for revision matching */ + dup = ly_ctx_get_submodule_latest(PARSER_CTX(ctx), mod->mod->name); + if (dup) { + LOGVAL_PARSER(ctx, LY_VCODE_NAME2_COL, "module", "submodule", mod->mod->name); + return LY_EVALID; + } + +cleanup: + return ret; +} + +/** + * @brief Parse submodule substatements. + * + * @param[in] ctx yang parser context for logging. + * @param[out] submod Parsed submodule structure. + * + * @return LY_ERR values. + */ +LY_ERR +parse_submodule(struct lysp_yang_ctx *ctx, struct lysp_submodule *submod) +{ + LY_ERR ret = 0; + char *buf, *word; + size_t word_len; + enum ly_stmt kw, prev_kw = 0; + enum yang_module_stmt mod_stmt = Y_MOD_MODULE_HEADER; + const struct lysp_submodule *dup; + + submod->is_submod = 1; + + /* submodule name */ + LY_CHECK_RET(get_argument(ctx, Y_IDENTIF_ARG, NULL, &word, &buf, &word_len)); + INSERT_WORD_GOTO(ctx, buf, submod->name, word, word_len, ret, cleanup); + + YANG_READ_SUBSTMT_FOR_GOTO(ctx, kw, word, word_len, ret, cleanup) { + +#define CHECK_ORDER(SECTION) \ + if (mod_stmt > SECTION) {LOGVAL_PARSER(ctx, LY_VCODE_INORD, lyplg_ext_stmt2str(kw), lyplg_ext_stmt2str(prev_kw)); return LY_EVALID;}mod_stmt = SECTION + + switch (kw) { + /* module header */ + case LY_STMT_BELONGS_TO: + CHECK_ORDER(Y_MOD_MODULE_HEADER); + break; + case LY_STMT_YANG_VERSION: + CHECK_ORDER(Y_MOD_MODULE_HEADER); + break; + /* linkage */ + case LY_STMT_INCLUDE: + case LY_STMT_IMPORT: + CHECK_ORDER(Y_MOD_LINKAGE); + break; + /* meta */ + case LY_STMT_ORGANIZATION: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_REFERENCE: + CHECK_ORDER(Y_MOD_META); + break; + + /* revision */ + case LY_STMT_REVISION: + CHECK_ORDER(Y_MOD_REVISION); + break; + /* body */ + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_AUGMENT: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_DEVIATION: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IDENTITY: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_NOTIFICATION: + case LY_STMT_RPC: + case LY_STMT_TYPEDEF: + case LY_STMT_USES: + mod_stmt = Y_MOD_BODY; + break; + case LY_STMT_EXTENSION_INSTANCE: + /* no place in the statement order defined */ + break; + default: + /* error handled in the next switch */ + break; + } +#undef CHECK_ORDER + + prev_kw = kw; + switch (kw) { + /* module header */ + case LY_STMT_YANG_VERSION: + LY_CHECK_RET(parse_yangversion(ctx, (struct lysp_module *)submod)); + break; + case LY_STMT_BELONGS_TO: + LY_CHECK_RET(parse_belongsto(ctx, submod)); + break; + + /* linkage */ + case LY_STMT_INCLUDE: + if (submod->version == LYS_VERSION_1_1) { + LOGWRN(PARSER_CTX(ctx), "YANG version 1.1 expects all includes in main module, includes in submodules (%s) are not necessary.", + submod->name); + } + LY_CHECK_RET(parse_include(ctx, submod->name, &submod->includes)); + break; + case LY_STMT_IMPORT: + LY_CHECK_RET(parse_import(ctx, submod->prefix, &submod->imports)); + break; + + /* meta */ + case LY_STMT_ORGANIZATION: + LY_CHECK_RET(parse_text_field(ctx, submod, LY_STMT_ORGANIZATION, 0, &submod->org, Y_STR_ARG, &submod->exts)); + break; + case LY_STMT_CONTACT: + LY_CHECK_RET(parse_text_field(ctx, submod, LY_STMT_CONTACT, 0, &submod->contact, Y_STR_ARG, &submod->exts)); + break; + case LY_STMT_DESCRIPTION: + LY_CHECK_RET(parse_text_field(ctx, submod->dsc, LY_STMT_DESCRIPTION, 0, &submod->dsc, Y_STR_ARG, &submod->exts)); + break; + case LY_STMT_REFERENCE: + LY_CHECK_RET(parse_text_field(ctx, submod->ref, LY_STMT_REFERENCE, 0, &submod->ref, Y_STR_ARG, &submod->exts)); + break; + + /* revision */ + case LY_STMT_REVISION: + LY_CHECK_RET(parse_revision(ctx, &submod->revs)); + break; + + /* body */ + case LY_STMT_ANYDATA: + PARSER_CHECK_STMTVER2_RET(ctx, "anydata", "submodule"); + /* fall through */ + case LY_STMT_ANYXML: + LY_CHECK_RET(parse_any(ctx, kw, NULL, &submod->data)); + break; + case LY_STMT_CHOICE: + LY_CHECK_RET(parse_choice(ctx, NULL, &submod->data)); + break; + case LY_STMT_CONTAINER: + LY_CHECK_RET(parse_container(ctx, NULL, &submod->data)); + break; + case LY_STMT_LEAF: + LY_CHECK_RET(parse_leaf(ctx, NULL, &submod->data)); + break; + case LY_STMT_LEAF_LIST: + LY_CHECK_RET(parse_leaflist(ctx, NULL, &submod->data)); + break; + case LY_STMT_LIST: + LY_CHECK_RET(parse_list(ctx, NULL, &submod->data)); + break; + case LY_STMT_USES: + LY_CHECK_RET(parse_uses(ctx, NULL, &submod->data)); + break; + + case LY_STMT_AUGMENT: + LY_CHECK_RET(parse_augment(ctx, NULL, &submod->augments)); + break; + case LY_STMT_DEVIATION: + LY_CHECK_RET(parse_deviation(ctx, &submod->deviations)); + break; + case LY_STMT_EXTENSION: + LY_CHECK_RET(parse_extension(ctx, &submod->extensions)); + break; + case LY_STMT_FEATURE: + LY_CHECK_RET(parse_feature(ctx, &submod->features)); + break; + case LY_STMT_GROUPING: + LY_CHECK_RET(parse_grouping(ctx, NULL, &submod->groupings)); + break; + case LY_STMT_IDENTITY: + LY_CHECK_RET(parse_identity(ctx, &submod->identities)); + break; + case LY_STMT_NOTIFICATION: + LY_CHECK_RET(parse_notif(ctx, NULL, &submod->notifs)); + break; + case LY_STMT_RPC: + LY_CHECK_RET(parse_action(ctx, NULL, &submod->rpcs)); + break; + case LY_STMT_TYPEDEF: + LY_CHECK_RET(parse_typedef(ctx, NULL, &submod->typedefs)); + break; + case LY_STMT_EXTENSION_INSTANCE: + LY_CHECK_RET(parse_ext(ctx, word, word_len, submod, LY_STMT_SUBMODULE, 0, &submod->exts)); + break; + + default: + LOGVAL_PARSER(ctx, LY_VCODE_INCHILDSTMT, lyplg_ext_stmt2str(kw), "submodule"); + return LY_EVALID; + } + YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, submod->exts, ret, cleanup); + } + + /* mandatory substatements */ + if (!submod->prefix) { + LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "belongs-to", "submodule"); + return LY_EVALID; + } + + /* submodules share the namespace with the module names, so there must not be + * a submodule of the same name in the context, no need for revision matching */ + dup = ly_ctx_get_submodule_latest(PARSER_CTX(ctx), submod->name); + /* main modules may have different revisions */ + if (dup && strcmp(dup->mod->name, submod->mod->name)) { + LOGVAL_PARSER(ctx, LY_VCODE_NAME_COL, "submodules", dup->name); + return LY_EVALID; + } + +cleanup: + return ret; +} + +/** + * @brief Skip any redundant characters, namely whitespaces and comments. + * + * @param[in] ctx Yang parser context. + * @return LY_SUCCESS on success. + * @return LY_EVALID on invalid comment. + */ +static LY_ERR +skip_redundant_chars(struct lysp_yang_ctx *ctx) +{ + /* read some trailing spaces, new lines, or comments */ + while (ctx->in->current[0]) { + if (!strncmp(ctx->in->current, "//", 2)) { + /* one-line comment */ + ly_in_skip(ctx->in, 2); + LY_CHECK_RET(skip_comment(ctx, 1)); + } else if (!strncmp(ctx->in->current, "/*", 2)) { + /* block comment */ + ly_in_skip(ctx->in, 2); + LY_CHECK_RET(skip_comment(ctx, 2)); + } else if (isspace(ctx->in->current[0])) { + /* whitespace */ + if (ctx->in->current[0] == '\n') { + LY_IN_NEW_LINE(ctx->in); + } + ly_in_skip(ctx->in, 1); + } else { + break; + } + } + + return LY_SUCCESS; +} + +LY_ERR +yang_parse_submodule(struct lysp_yang_ctx **context, struct ly_ctx *ly_ctx, struct lysp_ctx *main_ctx, + struct ly_in *in, struct lysp_submodule **submod) +{ + LY_ERR ret = LY_SUCCESS; + char *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_submodule *mod_p = NULL; + struct lysf_ctx fctx = {.ctx = ly_ctx}; + + assert(context && ly_ctx && main_ctx && in && submod); + + /* create context */ + *context = calloc(1, sizeof **context); + LY_CHECK_ERR_RET(!(*context), LOGMEM(ly_ctx), LY_EMEM); + (*context)->format = LYS_IN_YANG; + (*context)->in = in; + (*context)->main_ctx = main_ctx; + + mod_p = calloc(1, sizeof *mod_p); + LY_CHECK_ERR_GOTO(!mod_p, LOGMEM(ly_ctx); ret = LY_EMEM, cleanup); + mod_p->mod = PARSER_CUR_PMOD(main_ctx)->mod; + mod_p->parsing = 1; + + /* use main context parsed mods adding the current one */ + (*context)->parsed_mods = main_ctx->parsed_mods; + ly_set_add((*context)->parsed_mods, mod_p, 1, NULL); + + LOG_LOCSET(NULL, NULL, NULL, in); + + /* skip redundant but valid characters at the beginning */ + ret = skip_redundant_chars(*context); + LY_CHECK_GOTO(ret, cleanup); + + /* "module"/"submodule" */ + ret = get_keyword(*context, &kw, &word, &word_len); + LY_CHECK_GOTO(ret, cleanup); + + if (kw == LY_STMT_MODULE) { + LOGERR(ly_ctx, LY_EDENIED, "Input data contains module in situation when a submodule is expected."); + ret = LY_EINVAL; + goto cleanup; + } else if (kw != LY_STMT_SUBMODULE) { + LOGVAL_PARSER(*context, LY_VCODE_MOD_SUBOMD, lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + } + + /* substatements */ + ret = parse_submodule(*context, mod_p); + LY_CHECK_GOTO(ret, cleanup); + + /* skip redundant but valid characters at the end */ + ret = skip_redundant_chars(*context); + LY_CHECK_GOTO(ret, cleanup); + if (in->current[0]) { + LOGVAL_PARSER(*context, LY_VCODE_TRAILING_SUBMOD, 15, in->current, strlen(in->current) > 15 ? "..." : ""); + ret = LY_EVALID; + goto cleanup; + } + + mod_p->parsing = 0; + *submod = mod_p; + +cleanup: + LOG_LOCBACK(0, 0, 0, 1); + if (ret) { + lysp_module_free(&fctx, (struct lysp_module *)mod_p); + lysp_yang_ctx_free(*context); + *context = NULL; + } + + return ret; +} + +LY_ERR +yang_parse_module(struct lysp_yang_ctx **context, struct ly_in *in, struct lys_module *mod) +{ + LY_ERR ret = LY_SUCCESS; + char *word; + size_t word_len; + enum ly_stmt kw; + struct lysp_module *mod_p = NULL; + struct lysf_ctx fctx = {.ctx = mod->ctx}; + + /* create context */ + *context = calloc(1, sizeof **context); + LY_CHECK_ERR_RET(!(*context), LOGMEM(mod->ctx), LY_EMEM); + (*context)->format = LYS_IN_YANG; + LY_CHECK_ERR_RET(ly_set_new(&(*context)->parsed_mods), free(*context); LOGMEM(mod->ctx), LY_EMEM); + (*context)->in = in; + (*context)->main_ctx = (struct lysp_ctx *)(*context); + + mod_p = calloc(1, sizeof *mod_p); + LY_CHECK_ERR_GOTO(!mod_p, LOGMEM(mod->ctx), cleanup); + mod_p->mod = mod; + ly_set_add((*context)->parsed_mods, mod_p, 1, NULL); + + LOG_LOCSET(NULL, NULL, NULL, in); + + /* skip redundant but valid characters at the beginning */ + ret = skip_redundant_chars(*context); + LY_CHECK_GOTO(ret, cleanup); + + /* "module"/"submodule" */ + ret = get_keyword(*context, &kw, &word, &word_len); + LY_CHECK_GOTO(ret, cleanup); + + if (kw == LY_STMT_SUBMODULE) { + LOGERR(mod->ctx, LY_EDENIED, "Input data contains submodule which cannot be parsed directly without its main module."); + ret = LY_EINVAL; + goto cleanup; + } else if (kw != LY_STMT_MODULE) { + LOGVAL_PARSER((*context), LY_VCODE_MOD_SUBOMD, lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + } + + /* substatements */ + ret = parse_module(*context, mod_p); + LY_CHECK_GOTO(ret, cleanup); + + /* skip redundant but valid characters at the end */ + ret = skip_redundant_chars(*context); + LY_CHECK_GOTO(ret, cleanup); + if (in->current[0]) { + LOGVAL_PARSER(*context, LY_VCODE_TRAILING_MOD, 15, in->current, strlen(in->current) > 15 ? "..." : ""); + ret = LY_EVALID; + goto cleanup; + } + + mod->parsed = mod_p; + +cleanup: + LOG_LOCBACK(0, 0, 0, 1); + if (ret) { + lysp_module_free(&fctx, mod_p); + lysp_yang_ctx_free(*context); + *context = NULL; + } + + return ret; +} diff --git a/src/parser_yin.c b/src/parser_yin.c new file mode 100644 index 0000000..fa44968 --- /dev/null +++ b/src/parser_yin.c @@ -0,0 +1,4012 @@ +/** + * @file parser_yin.c + * @author David Sedlák + * @author Michal Vasko + * @brief YIN parser. + * + * Copyright (c) 2015 - 2022 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "in.h" +#include "in_internal.h" +#include "log.h" +#include "parser_internal.h" +#include "parser_schema.h" +#include "path.h" +#include "set.h" +#include "tree.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xml.h" + +struct lys_glob_unres; + +/** + * @brief check if given string is URI of yin namespace. + * + * @param ns Namespace URI to check. + * + * @return true if ns equals YIN_NS_URI false otherwise. + */ +#define IS_YIN_NS(ns) (strcmp(ns, YIN_NS_URI) == 0) + +enum yin_argument { + YIN_ARG_UNKNOWN = 0, /**< parsed argument can not be matched with any supported yin argument keyword */ + YIN_ARG_NAME, /**< argument name */ + YIN_ARG_TARGET_NODE, /**< argument target-node */ + YIN_ARG_MODULE, /**< argument module */ + YIN_ARG_VALUE, /**< argument value */ + YIN_ARG_TEXT, /**< argument text */ + YIN_ARG_CONDITION, /**< argument condition */ + YIN_ARG_URI, /**< argument uri */ + YIN_ARG_DATE, /**< argument data */ + YIN_ARG_TAG, /**< argument tag */ + YIN_ARG_NONE /**< empty (special value) */ +}; + +const char * const yin_attr_list[] = { + [YIN_ARG_NAME] = "name", + [YIN_ARG_TARGET_NODE] = "target-node", + [YIN_ARG_MODULE] = "module", + [YIN_ARG_VALUE] = "value", + [YIN_ARG_TEXT] = "text", + [YIN_ARG_CONDITION] = "condition", + [YIN_ARG_URI] = "uri", + [YIN_ARG_DATE] = "date", + [YIN_ARG_TAG] = "tag", + [YIN_ARG_NONE] = "none", +}; + +#define yin_attr2str(STMT) yin_attr_list[STMT] + +#define VALID_VALS1 " Only valid value is \"%s\"." +#define VALID_VALS2 " Valid values are \"%s\" and \"%s\"." +#define VALID_VALS3 " Valid values are \"%s\", \"%s\" and \"%s\"." +#define VALID_VALS4 " Valid values are \"%s\", \"%s\", \"%s\" and \"%s\"." + +/* shortcut to determin if keyword can in general be subelement of deviation regardles of it's type */ +#define isdevsub(kw) (kw == LY_STMT_CONFIG || kw == LY_STMT_DEFAULT || kw == LY_STMT_MANDATORY || \ + kw == LY_STMT_MAX_ELEMENTS || kw == LY_STMT_MIN_ELEMENTS || \ + kw == LY_STMT_MUST || kw == LY_STMT_TYPE || kw == LY_STMT_UNIQUE || \ + kw == LY_STMT_UNITS || kw == LY_STMT_EXTENSION_INSTANCE) + +/* flags to set constraints of subelements */ +#define YIN_SUBELEM_MANDATORY 0x01 /**< is set when subelement is mandatory */ +#define YIN_SUBELEM_UNIQUE 0x02 /**< is set when subelement is unique */ +#define YIN_SUBELEM_FIRST 0x04 /**< is set when subelement is actually yang argument mapped to yin element */ +#define YIN_SUBELEM_VER2 0x08 /**< subelemnt is allowed only in modules with version at least 2 (YANG 1.1) */ + +#define YIN_SUBELEM_PARSED 0x80 /**< is set during parsing when given subelement is encountered for the first + time to simply check validity of given constraints */ + +struct yin_subelement { + enum ly_stmt type; /**< type of keyword */ + void *dest; /**< meta infromation passed to responsible function (mostly information about where parsed + subelement should be stored) */ + uint16_t flags; /**< describes constraints of subelement can be set to YIN_SUBELEM_MANDATORY, + YIN_SUBELEM_UNIQUE, YIN_SUBELEM_FIRST, YIN_SUBELEM_VER2, and YIN_SUBELEM_DEFAULT_TEXT */ +}; + +/* Meta information passed to yin_parse_argument function, + holds information about where content of argument element will be stored. */ +struct yin_argument_meta { + uint16_t *flags; /**< Argument flags */ + const char **argument; /**< Argument value */ +}; + +/** + * @brief Meta information passed to functions working with tree_schema, + * that require additional information about parent node. + */ +struct tree_node_meta { + struct lysp_node *parent; /**< parent node */ + struct lysp_node **nodes; /**< linked list of siblings */ +}; + +/** + * @brief Meta information passed to yin_parse_import function. + */ +struct import_meta { + const char *prefix; /**< module prefix. */ + struct lysp_import **imports; /**< imports to add to. */ +}; + +/** + * @brief Meta information passed to yin_parse_include function. + */ +struct include_meta { + const char *name; /**< Module/submodule name. */ + struct lysp_include **includes; /**< [Sized array](@ref sizedarrays) of parsed includes to add to. */ +}; + +/** + * @brief Meta information passed to yin_parse_inout function. + */ +struct inout_meta { + struct lysp_node *parent; /**< Parent node. */ + struct lysp_node_action_inout *inout_p; /**< inout_p Input/output pointer to write to. */ +}; + +/** + * @brief Meta information passed to yin_parse_minmax function. + */ +struct minmax_dev_meta { + uint32_t *lim; /**< min/max value to write to. */ + uint16_t *flags; /**< min/max flags to write to. */ + struct lysp_ext_instance **exts; /**< extension instances to add to. */ +}; + +LY_ERR yin_parse_content(struct lysp_yin_ctx *ctx, struct yin_subelement *subelem_info, size_t subelem_info_size, + const void *parent, enum ly_stmt parent_stmt, const char **text_content, struct lysp_ext_instance **exts); + +/** + * @brief Match yang keyword from yin data. + * + * @param[in,out] ctx Yin parser context for logging and to store current state. + * @param[in] name Start of keyword name + * @param[in] name_len Lenght of keyword name. + * @param[in] prefix Start of keyword prefix. + * @param[in] prefix_len Lenght of prefix. + * @param[in] parent Identification of parent element, use LY_STMT_NONE for elements without parent. + * @return yang_keyword values. + */ +enum ly_stmt +yin_match_keyword(struct lysp_yin_ctx *ctx, const char *name, size_t name_len, const char *prefix, size_t prefix_len, + enum ly_stmt parent) +{ + const char *start = NULL; + enum ly_stmt kw = LY_STMT_NONE; + const struct lyxml_ns *ns = NULL; + struct ly_in *in; + + if (!name || (name_len == 0)) { + return LY_STMT_NONE; + } + + ns = lyxml_ns_get(&ctx->xmlctx->ns, prefix, prefix_len); + if (ns) { + if (!IS_YIN_NS(ns->uri)) { + return LY_STMT_EXTENSION_INSTANCE; + } + } else { + /* elements without namespace are automatically unknown */ + return LY_STMT_NONE; + } + + LY_CHECK_RET(ly_in_new_memory(name, &in), LY_STMT_NONE); + start = in->current; + kw = lysp_match_kw(in, NULL); + name = in->current; + ly_in_free(in, 0); + + if (name - start == (long)name_len) { + /* this is done because of collision in yang statement value and yang argument mapped to yin element value */ + if ((kw == LY_STMT_VALUE) && (parent == LY_STMT_ERROR_MESSAGE)) { + return LY_STMT_ARG_VALUE; + } + return kw; + } else { + if (strncmp(start, "text", name_len) == 0) { + return LY_STMT_ARG_TEXT; + } else { + return LY_STMT_NONE; + } + } +} + +/** + * @brief Match argument name. + * + * @param[in] name String representing name. + * @param[in] len Lenght of the name. + * @return yin_argument values. + */ +enum yin_argument +yin_match_argument_name(const char *name, size_t len) +{ + enum yin_argument arg = YIN_ARG_UNKNOWN; + size_t already_read = 0; + + LY_CHECK_RET(len == 0, YIN_ARG_NONE); + +#define READ_INC(LEN) already_read += LEN +#define ARG_SET(STMT) arg=STMT +#define ARG_CHECK(STR, LEN) (!strncmp((name) + already_read, STR, LEN) && (READ_INC(LEN))) + + switch (*name) { + case 'c': + READ_INC(1); + if (ARG_CHECK("ondition", 8)) { + ARG_SET(YIN_ARG_CONDITION); + } + break; + + case 'd': + READ_INC(1); + if (ARG_CHECK("ate", 3)) { + ARG_SET(YIN_ARG_DATE); + } + break; + + case 'm': + READ_INC(1); + if (ARG_CHECK("odule", 5)) { + ARG_SET(YIN_ARG_MODULE); + } + break; + + case 'n': + READ_INC(1); + if (ARG_CHECK("ame", 3)) { + ARG_SET(YIN_ARG_NAME); + } + break; + + case 't': + READ_INC(1); + if (ARG_CHECK("a", 1)) { + if (ARG_CHECK("g", 1)) { + ARG_SET(YIN_ARG_TAG); + } else if (ARG_CHECK("rget-node", 9)) { + ARG_SET(YIN_ARG_TARGET_NODE); + } + } else if (ARG_CHECK("ext", 3)) { + ARG_SET(YIN_ARG_TEXT); + } + break; + + case 'u': + READ_INC(1); + if (ARG_CHECK("ri", 2)) { + ARG_SET(YIN_ARG_URI); + } + break; + + case 'v': + READ_INC(1); + if (ARG_CHECK("alue", 4)) { + ARG_SET(YIN_ARG_VALUE); + } + break; + } + + /* whole argument must be matched */ + if (already_read != len) { + arg = YIN_ARG_UNKNOWN; + } + +#undef ARG_CHECK +#undef ARG_SET +#undef READ_INC + + return arg; +} + +#define IS_NODE_ELEM(kw) (kw == LY_STMT_ANYXML || kw == LY_STMT_ANYDATA || kw == LY_STMT_LEAF || kw == LY_STMT_LEAF_LIST || \ + kw == LY_STMT_TYPEDEF || kw == LY_STMT_USES || kw == LY_STMT_LIST || kw == LY_STMT_NOTIFICATION || \ + kw == LY_STMT_GROUPING || kw == LY_STMT_CONTAINER || kw == LY_STMT_CASE || kw == LY_STMT_CHOICE || \ + kw == LY_STMT_ACTION || kw == LY_STMT_RPC || kw == LY_STMT_AUGMENT) + +#define HAS_META(kw) (IS_NODE_ELEM(kw) || kw == LY_STMT_IMPORT || kw == LY_STMT_INCLUDE || kw == LY_STMT_INPUT || kw == LY_STMT_OUTPUT) + +/** + * @brief Free subelems information allocated on heap. + * + * @param[in] count Size of subelems array. + * @param[in] subelems Subelems array to free. + */ +static void +subelems_deallocator(size_t count, struct yin_subelement *subelems) +{ + for (size_t i = 0; i < count; ++i) { + if (HAS_META(subelems[i].type)) { + free(subelems[i].dest); + } + } + + free(subelems); +} + +/** + * @brief Allocate subelems information on heap. + * + * @param[in] ctx YIN parser context, used for logging. + * @param[in] count Number of subelements. + * @param[in] parent Parent node if any. + * @param[out] result Allocated subelems array. + * @return LY_ERR values. + */ +static LY_ERR +subelems_allocator(struct lysp_yin_ctx *ctx, size_t count, struct lysp_node *parent, + struct yin_subelement **result, ...) +{ + va_list ap; + + *result = calloc(count, sizeof **result); + LY_CHECK_GOTO(!(*result), mem_err); + + va_start(ap, result); + for (size_t i = 0; i < count; ++i) { + /* TYPE */ + (*result)[i].type = va_arg(ap, enum ly_stmt); + /* DEST */ + if (IS_NODE_ELEM((*result)[i].type)) { + struct tree_node_meta *node_meta = NULL; + + node_meta = calloc(1, sizeof *node_meta); + LY_CHECK_GOTO(!node_meta, mem_err); + node_meta->parent = parent; + node_meta->nodes = va_arg(ap, void *); + (*result)[i].dest = node_meta; + } else if ((*result)[i].type == LY_STMT_IMPORT) { + struct import_meta *imp_meta = NULL; + + imp_meta = calloc(1, sizeof *imp_meta); + LY_CHECK_GOTO(!imp_meta, mem_err); + imp_meta->prefix = va_arg(ap, const char *); + imp_meta->imports = va_arg(ap, struct lysp_import **); + (*result)[i].dest = imp_meta; + } else if ((*result)[i].type == LY_STMT_INCLUDE) { + struct include_meta *inc_meta = NULL; + + inc_meta = calloc(1, sizeof *inc_meta); + LY_CHECK_GOTO(!inc_meta, mem_err); + inc_meta->name = va_arg(ap, const char *); + inc_meta->includes = va_arg(ap, struct lysp_include **); + (*result)[i].dest = inc_meta; + } else if (((*result)[i].type == LY_STMT_INPUT) || ((*result)[i].type == LY_STMT_OUTPUT)) { + struct inout_meta *inout_meta = NULL; + + inout_meta = calloc(1, sizeof *inout_meta); + LY_CHECK_GOTO(!inout_meta, mem_err); + inout_meta->parent = parent; + inout_meta->inout_p = va_arg(ap, struct lysp_node_action_inout *); + (*result)[i].dest = inout_meta; + } else { + (*result)[i].dest = va_arg(ap, void *); + } + /* FLAGS */ + (*result)[i].flags = va_arg(ap, int); + } + va_end(ap); + + return LY_SUCCESS; + +mem_err: + subelems_deallocator(count, *result); + LOGMEM(ctx->xmlctx->ctx); + return LY_EMEM; +} + +/** + * @brief Check that val is valid UTF8 character sequence of val_type. + * Doesn't check empty string, only character validity. + * + * @param[in] ctx Yin parser context for logging. + * @param[in] val_type Type of the input string to select method of checking character validity. + * @return LY_ERR values. + */ +LY_ERR +yin_validate_value(struct lysp_yin_ctx *ctx, enum yang_arg val_type) +{ + uint8_t prefix = 0; + uint32_t c; + size_t utf8_char_len, already_read = 0; + const char *val; + + assert((ctx->xmlctx->status == LYXML_ELEM_CONTENT) || (ctx->xmlctx->status == LYXML_ATTR_CONTENT)); + + val = ctx->xmlctx->value; + while (already_read < ctx->xmlctx->value_len) { + LY_CHECK_ERR_RET(ly_getutf8((const char **)&val, &c, &utf8_char_len), + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INCHAR, (val)[-utf8_char_len]), LY_EVALID); + + switch (val_type) { + case Y_IDENTIF_ARG: + LY_CHECK_RET(lysp_check_identifierchar((struct lysp_ctx *)ctx, c, already_read ? 0 : 1, NULL)); + break; + case Y_PREF_IDENTIF_ARG: + LY_CHECK_RET(lysp_check_identifierchar((struct lysp_ctx *)ctx, c, already_read ? 0 : 1, &prefix)); + break; + case Y_STR_ARG: + case Y_MAYBE_STR_ARG: + LY_CHECK_RET(lysp_check_stringchar((struct lysp_ctx *)ctx, c)); + break; + } + + already_read += utf8_char_len; + LY_CHECK_ERR_RET(already_read > ctx->xmlctx->value_len, LOGINT(ctx->xmlctx->ctx), LY_EINT); + } + + if ((already_read == 0) && + ((val_type == Y_PREF_IDENTIF_ARG) || (val_type == Y_IDENTIF_ARG))) { + /* Empty identifier or prefix*/ + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YIN, "Empty identifier is not allowed."); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse yin attributes. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] arg_type Type of argument that is expected in parsed element (use YIN_ARG_NONE for elements without + * special argument). + * @param[out] arg_val Where value of argument should be stored. Can be NULL iff arg_type is specified as YIN_ARG_NONE. + * @param[in] val_type Type of expected value of attribute. + * @param[in] current_element Identification of current element, used for logging. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_attribute(struct lysp_yin_ctx *ctx, enum yin_argument arg_type, const char **arg_val, enum yang_arg val_type, + enum ly_stmt current_element) +{ + enum yin_argument arg = YIN_ARG_UNKNOWN; + bool found = false; + + /* validation of attributes */ + while (ctx->xmlctx->status == LYXML_ATTRIBUTE) { + /* yin arguments represented as attributes have no namespace, which in this case means no prefix */ + if (!ctx->xmlctx->prefix) { + arg = yin_match_argument_name(ctx->xmlctx->name, ctx->xmlctx->name_len); + if (arg == YIN_ARG_NONE) { + /* skip it */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } else if (arg == arg_type) { + LY_CHECK_ERR_RET(found, LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_DUP_ATTR, + yin_attr2str(arg), lyplg_ext_stmt2str(current_element)), LY_EVALID); + found = true; + + /* go to value */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_validate_value(ctx, val_type)); + INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, *arg_val); + LY_CHECK_RET(!(*arg_val), LY_EMEM); + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_ATTR, ctx->xmlctx->name_len, + ctx->xmlctx->name, lyplg_ext_stmt2str(current_element)); + return LY_EVALID; + } + } else { + /* skip it */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + + /* next attribute */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + + /* anything else than Y_MAYBE_STR_ARG is mandatory */ + if ((val_type != Y_MAYBE_STR_ARG) && !found) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LYVE_SYNTAX_YIN, "Missing mandatory attribute %s of %s element.", + yin_attr2str(arg_type), lyplg_ext_stmt2str(current_element)); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Get record with given type. + * + * @param[in] type Type of wanted record. + * @param[in] array_size Size of array. + * @param[in] array Searched array. + * @return Pointer to desired record on success, NULL if element is not in the array. + */ +static struct yin_subelement * +get_record(enum ly_stmt type, LY_ARRAY_COUNT_TYPE array_size, struct yin_subelement *array) +{ + for (LY_ARRAY_COUNT_TYPE u = 0; u < array_size; ++u) { + if (array[u].type == type) { + return &array[u]; + } + } + return NULL; +} + +/** + * @brief Helper function to check mandatory constraint of subelement. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] subelem_info Array of information about subelements. + * @param[in] subelem_info_size Size of subelem_info array. + * @param[in] current_element Identification of element that is currently being parsed, used for logging. + * @return LY_ERR values. + */ +static LY_ERR +yin_check_subelem_mandatory_constraint(struct lysp_yin_ctx *ctx, struct yin_subelement *subelem_info, + signed char subelem_info_size, enum ly_stmt current_element) +{ + for (signed char i = 0; i < subelem_info_size; ++i) { + /* if there is element that is mandatory and isn't parsed log error and return LY_EVALID */ + if ((subelem_info[i].flags & YIN_SUBELEM_MANDATORY) && !(subelem_info[i].flags & YIN_SUBELEM_PARSED)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_MAND_SUBELEM, + lyplg_ext_stmt2str(subelem_info[i].type), lyplg_ext_stmt2str(current_element)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Helper function to check "first" constraint of subelement. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] subelem_info Array of information about subelements. + * @param[in] subelem_info_size Size of subelem_info array. + * @param[in] current_element Identification of element that is currently being parsed, used for logging. + * @param[in] exp_first Record in subelem_info array that is expected to be defined as first subelement, used for logging. + * @return LY_ERR values. + */ +static LY_ERR +yin_check_subelem_first_constraint(struct lysp_yin_ctx *ctx, struct yin_subelement *subelem_info, + signed char subelem_info_size, enum ly_stmt current_element, + struct yin_subelement *exp_first) +{ + for (signed char i = 0; i < subelem_info_size; ++i) { + if (subelem_info[i].flags & YIN_SUBELEM_PARSED) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_FIRT_SUBELEM, + lyplg_ext_stmt2str(exp_first->type), lyplg_ext_stmt2str(current_element)); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse simple element without any special constraints and argument mapped to yin attribute, + * for example prefix or namespace element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[out] value Where value of attribute should be stored. + * @param[in] arg_type Expected type of attribute. + * @param[in] arg_val_type Type of expected value of attribute. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_simple_element(struct lysp_yin_ctx *ctx, const void *parent, enum ly_stmt parent_stmt, const char **value, + enum yin_argument arg_type, enum yang_arg arg_val_type, struct lysp_ext_instance **exts) +{ + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, arg_type, value, arg_val_type, parent_stmt)); + + return yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, parent_stmt, NULL, exts); +} + +/** + * @brief Parse path element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[out] type Type structure to store parsed value, flags and extension instances. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_path(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + LY_ERR ret; + const char *str_path; + + LY_CHECK_RET(yin_parse_simple_element(ctx, type, LY_STMT_PATH, &str_path, YIN_ARG_VALUE, Y_STR_ARG, &type->exts)); + + ret = ly_path_parse(ctx->xmlctx->ctx, NULL, str_path, 0, 1, LY_PATH_BEGIN_EITHER, + LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &type->path); + lydict_remove(ctx->xmlctx->ctx, str_path); + LY_CHECK_RET(ret); + type->flags |= LYS_SET_PATH; + + return LY_SUCCESS; +} + +/** + * @brief Add extension array to context exts instances. + * + * @param[in] ctx YIN parser context. + * @param[in] exts Parsed extension array that is final (no realloc anymore). + * @return LY_ERR value. + */ +static LY_ERR +yin_unres_exts_add(struct lysp_yin_ctx *ctx, struct lysp_ext_instance *exts) +{ + if (!exts) { + return LY_SUCCESS; + } + + return ly_set_add(&ctx->main_ctx->ext_inst, exts, 1, NULL); +} + +/** + * @brief Parse pattern element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] type Type structure to store parsed value, flags and extension instances. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_pattern(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + const char *real_value = NULL; + char *saved_value = NULL; + struct lysp_restr *restr; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, type->patterns, restr, LY_EMEM); + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &real_value, Y_STR_ARG, LY_STMT_PATTERN)); + size_t len = strlen(real_value); + + saved_value = malloc(len + 2); + LY_CHECK_ERR_RET(!saved_value, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + memmove(saved_value + 1, real_value, len); + lydict_remove(ctx->xmlctx->ctx, real_value); + saved_value[0] = LYSP_RESTR_PATTERN_ACK; + saved_value[len + 1] = '\0'; + LY_CHECK_RET(lydict_insert_zc(ctx->xmlctx->ctx, saved_value, &restr->arg.str)); + restr->arg.mod = PARSER_CUR_PMOD(ctx); + type->flags |= LYS_SET_PATTERN; + + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &restr->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_ERROR_APP_TAG, &restr->eapptag, YIN_SUBELEM_UNIQUE}, + {LY_STMT_ERROR_MESSAGE, &restr->emsg, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MODIFIER, &restr->arg, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &restr->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), restr, LY_STMT_PATTERN, NULL, &restr->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, restr->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse fraction-digits element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] type Type structure to store value, flags and extension instances. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_fracdigits(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + const char *temp_val = NULL; + char *ptr; + unsigned long long num; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_FRACTION_DIGITS)); + + if ((temp_val[0] == '\0') || (temp_val[0] == '0') || !isdigit(temp_val[0])) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "fraction-digits"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + + errno = 0; + num = strtoull(temp_val, &ptr, LY_BASE_DEC); + if (*ptr != '\0') { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "fraction-digits"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + if ((errno == ERANGE) || (num > LY_TYPE_DEC64_FD_MAX)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "fraction-digits"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + type->fraction_digits = num; + type->flags |= LYS_SET_FRDIGITS; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), type, LY_STMT_FRACTION_DIGITS, NULL, &type->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, type->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse enum element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] type Type structure to store parsed value, flags and extension instances. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_enum(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + struct lysp_type_enum *en; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, type->enums, en, LY_EMEM); + type->flags |= LYS_SET_ENUM; + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &en->name, Y_STR_ARG, LY_STMT_ENUM)); + LY_CHECK_RET(lysp_check_enum_name((struct lysp_ctx *)ctx, en->name, strlen(en->name))); + CHECK_NONEMPTY((struct lysp_ctx *)ctx, strlen(en->name), "enum"); + CHECK_UNIQUENESS((struct lysp_ctx *)ctx, type->enums, name, "enum", en->name); + + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &en->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &en->iffeatures, 0}, + {LY_STMT_REFERENCE, &en->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &en->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_VALUE, en, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), en, LY_STMT_ENUM, NULL, &en->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, en->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse bit element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] type Type structure to store parsed value, flags and extension instances. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_bit(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + struct lysp_type_enum *en; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, type->bits, en, LY_EMEM); + type->flags |= LYS_SET_BIT; + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &en->name, Y_IDENTIF_ARG, LY_STMT_BIT)); + CHECK_UNIQUENESS((struct lysp_ctx *)ctx, type->bits, name, "bit", en->name); + + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &en->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &en->iffeatures, 0}, + {LY_STMT_POSITION, en, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &en->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &en->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), en, LY_STMT_BIT, NULL, &en->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, en->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse simple element without any special constraints and argument mapped to yin attribute, that can have + * more instances, such as base or if-feature. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[out] values Parsed values to add to. + * @param[in] arg_type Expected type of attribute. + * @param[in] arg_val_type Type of expected value of attribute. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_simple_elements(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, const char ***values, + enum yin_argument arg_type, enum yang_arg arg_val_type, struct lysp_ext_instance **exts) +{ + const char **value; + LY_ARRAY_COUNT_TYPE index = LY_ARRAY_COUNT(*values); + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, &index, 0} + }; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *values, value, LY_EMEM); + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, arg_type, value, arg_val_type, parent_stmt)); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), *values, parent_stmt, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse simple element without any special constraints and argument mapped to yin attribute. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[in] subinfo Information about subelement, is used to determin which function should be called and where to store parsed value. + * @param[in] arg_type Expected type of attribute. + * @param[in] arg_val_type Type of expected value of attribute. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_simple_elem(struct lysp_yin_ctx *ctx, const void *parent, enum ly_stmt parent_stmt, struct yin_subelement *subinfo, + enum yin_argument arg_type, enum yang_arg arg_val_type, struct lysp_ext_instance **exts) +{ + if (subinfo->flags & YIN_SUBELEM_UNIQUE) { + LY_CHECK_RET(yin_parse_simple_element(ctx, parent, parent_stmt, (const char **)subinfo->dest, arg_type, + arg_val_type, exts)); + } else { + LY_CHECK_RET(yin_parse_simple_elements(ctx, parent_stmt, (const char ***)subinfo->dest, arg_type, + arg_val_type, exts)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse base element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[out] dest Where parsed values should be stored. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_base(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, void *dest, struct lysp_ext_instance **exts) +{ + struct lysp_type *type; + + if (parent_stmt == LY_STMT_TYPE) { + type = (struct lysp_type *)dest; + LY_CHECK_RET(yin_parse_simple_elements(ctx, LY_STMT_BASE, &type->bases, YIN_ARG_NAME, Y_PREF_IDENTIF_ARG, exts)); + type->flags |= LYS_SET_BASE; + } else if (parent_stmt == LY_STMT_IDENTITY) { + LY_CHECK_RET(yin_parse_simple_elements(ctx, LY_STMT_BASE, (const char ***)dest, YIN_ARG_NAME, Y_PREF_IDENTIF_ARG, exts)); + } else { + LOGINT(ctx->xmlctx->ctx); + return LY_EINT; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse require-instance element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[out] type Type structure to store value, flag and extensions. + * @return LY_ERR values. + */ +static LY_ERR +yin_pasrse_reqinstance(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + const char *temp_val = NULL; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + type->flags |= LYS_SET_REQINST; + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_REQUIRE_INSTANCE)); + if (strcmp(temp_val, "true") == 0) { + type->require_instance = 1; + } else if (strcmp(temp_val, "false") != 0) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS2, temp_val, "value", + "require-instance", "true", "false"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), type, LY_STMT_REQUIRE_INSTANCE, NULL, &type->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, type->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse modifier element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in,out] pat Value to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_modifier(struct lysp_yin_ctx *ctx, const void *parent, const char **pat, struct lysp_ext_instance **exts) +{ + const char *temp_val; + char *modified_val; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + assert(**pat == 0x06); + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_MODIFIER)); + if (strcmp(temp_val, "invert-match") != 0) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS1, temp_val, "value", + "modifier", "invert-match"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + /* allocate new value */ + modified_val = malloc(strlen(*pat) + 1); + LY_CHECK_ERR_RET(!modified_val, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + strcpy(modified_val, *pat); + lydict_remove(ctx->xmlctx->ctx, *pat); + + /* modify the new value */ + modified_val[0] = LYSP_RESTR_PATTERN_NACK; + LY_CHECK_RET(lydict_insert_zc(ctx->xmlctx->ctx, modified_val, pat)); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, LY_STMT_MODIFIER, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse a restriction element (length, range or one instance of must). + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] restr_kw Identificaton of element that is being parsed, can be set to LY_STMT_MUST, LY_STMT_LENGTH or LY_STMT_RANGE. + * @param[in] restr Value to write to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_restriction(struct lysp_yin_ctx *ctx, enum ly_stmt restr_kw, struct lysp_restr *restr) +{ + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &restr->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_ERROR_APP_TAG, &restr->eapptag, YIN_SUBELEM_UNIQUE}, + {LY_STMT_ERROR_MESSAGE, &restr->emsg, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &restr->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + /* argument of must is called condition, but argument of length and range is called value */ + enum yin_argument arg_type = (restr_kw == LY_STMT_MUST) ? YIN_ARG_CONDITION : YIN_ARG_VALUE; + + assert(restr_kw == LY_STMT_MUST || restr_kw == LY_STMT_LENGTH || restr_kw == LY_STMT_RANGE); + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, arg_type, &restr->arg.str, Y_STR_ARG, restr_kw)); + restr->arg.mod = PARSER_CUR_PMOD(ctx); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), restr, restr_kw, NULL, &restr->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, restr->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse range element. + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[out] type Type structure to store parsed value and flags. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_range(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + type->range = calloc(1, sizeof *type->range); + LY_CHECK_ERR_RET(!type->range, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + LY_CHECK_RET(yin_parse_restriction(ctx, LY_STMT_RANGE, type->range)); + type->flags |= LYS_SET_RANGE; + + return LY_SUCCESS; +} + +/** + * @brief Parse length element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[out] type Type structure to store parsed value and flags. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_length(struct lysp_yin_ctx *ctx, struct lysp_type *type) +{ + type->length = calloc(1, sizeof *type->length); + LY_CHECK_ERR_RET(!type->length, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + LY_CHECK_RET(yin_parse_restriction(ctx, LY_STMT_LENGTH, type->length)); + type->flags |= LYS_SET_LENGTH; + + return LY_SUCCESS; +} + +/** + * @brief Parse must element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] restrs Restrictions to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_must(struct lysp_yin_ctx *ctx, struct lysp_restr **restrs) +{ + struct lysp_restr *restr; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *restrs, restr, LY_EMEM); + return yin_parse_restriction(ctx, LY_STMT_MUST, restr); +} + +/** + * @brief Parse a node id into an array. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[in] subinfo Information about subelement, is used to determin which function should be called and where to store parsed value. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_qname(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, struct yin_subelement *subinfo, + struct lysp_ext_instance **exts) +{ + struct lysp_qname *qname, **qnames; + + switch (parent_stmt) { + case LY_STMT_DEFAULT: + if (subinfo->flags & YIN_SUBELEM_UNIQUE) { + qname = (struct lysp_qname *)subinfo->dest; + } else { + qnames = (struct lysp_qname **)subinfo->dest; + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *qnames, qname, LY_EMEM); + } + qname->mod = PARSER_CUR_PMOD(ctx); + return yin_parse_simple_element(ctx, qname, parent_stmt, &qname->str, YIN_ARG_VALUE, Y_STR_ARG, exts); + case LY_STMT_UNIQUE: + assert(!(subinfo->flags & YIN_SUBELEM_UNIQUE)); + + qnames = (struct lysp_qname **)subinfo->dest; + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *qnames, qname, LY_EMEM); + qname->mod = PARSER_CUR_PMOD(ctx); + return yin_parse_simple_element(ctx, *qnames, parent_stmt, &qname->str, YIN_ARG_TAG, Y_STR_ARG, exts); + case LY_STMT_IF_FEATURE: + assert(!(subinfo->flags & YIN_SUBELEM_UNIQUE)); + + qnames = (struct lysp_qname **)subinfo->dest; + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *qnames, qname, LY_EMEM); + qname->mod = PARSER_CUR_PMOD(ctx); + return yin_parse_simple_element(ctx, *qnames, parent_stmt, &qname->str, YIN_ARG_NAME, Y_STR_ARG, exts); + default: + break; + } + + LOGINT(ctx->xmlctx->ctx); + return LY_EINT; +} + +/** + * @brief Parse position or value element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[out] enm Enum structure to save value, flags and extension instances. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_value_pos(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, struct lysp_type_enum *enm) +{ + LY_ERR ret = LY_SUCCESS; + const char *temp_val = NULL; + char *ptr; + long long num = 0; + unsigned long long unum = 0; + + assert(parent_stmt == LY_STMT_POSITION || parent_stmt == LY_STMT_VALUE); + + /* set value flag */ + enm->flags |= LYS_SET_VALUE; + + /* get attribute value */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + LY_CHECK_GOTO(ret = yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, parent_stmt), cleanup); + if (!temp_val || (temp_val[0] == '\0') || (temp_val[0] == '+') || + ((temp_val[0] == '0') && (temp_val[1] != '\0')) || ((parent_stmt == LY_STMT_POSITION) && !strcmp(temp_val, "-0"))) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } + + /* convert value */ + errno = 0; + if (parent_stmt == LY_STMT_VALUE) { + num = strtoll(temp_val, &ptr, LY_BASE_DEC); + if ((num < INT64_C(-2147483648)) || (num > INT64_C(2147483647))) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } + } else { + unum = strtoull(temp_val, &ptr, LY_BASE_DEC); + if (unum > UINT64_C(4294967295)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } + } + /* check if whole argument value was converted */ + if (*ptr != '\0') { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } + if (errno == ERANGE) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_OOB_YIN, temp_val, "value", lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } + /* save correctly ternary operator can't be used because num and unum have different signes */ + if (parent_stmt == LY_STMT_VALUE) { + enm->value = num; + } else { + enm->value = unum; + } + + /* parse subelements */ + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_GOTO(ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), enm, parent_stmt, NULL, &enm->exts), cleanup); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_GOTO(ret = yin_unres_exts_add(ctx, enm->exts), cleanup); + +cleanup: + lydict_remove(ctx->xmlctx->ctx, temp_val); + return ret; +} + +/** + * @brief Parse belongs-to element. + * + * @param[in] ctx YIN parser context for logging and to store current state. + * @param[out] submod Structure of submodule that is being parsed. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values + */ +static LY_ERR +yin_parse_belongs_to(struct lysp_yin_ctx *ctx, struct lysp_submodule *submod, struct lysp_ext_instance **exts) +{ + const char *belongsto; + struct yin_subelement subelems[] = { + {LY_STMT_PREFIX, &submod->prefix, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_MODULE, &belongsto, Y_IDENTIF_ARG, LY_STMT_BELONGS_TO)); + if (PARSER_CUR_PMOD(ctx)->mod->name != belongsto) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YIN, "Submodule \"belongs-to\" value \"%s\" does not match its module name \"%s\".", + belongsto, PARSER_CUR_PMOD(ctx)->mod->name); + lydict_remove(ctx->xmlctx->ctx, belongsto); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, belongsto); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), submod, LY_STMT_BELONGS_TO, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Function to parse meta tags (description, contact, ...) eg. elements with + * text element as child. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[out] value Where the content of meta element should be stored. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_meta(struct lysp_yin_ctx *ctx, const void *parent, enum ly_stmt parent_stmt, const char **value, + struct lysp_ext_instance **exts) +{ + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + {LY_STMT_ARG_TEXT, value, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE | YIN_SUBELEM_FIRST} + }; + + assert(parent_stmt == LY_STMT_ORGANIZATION || parent_stmt == LY_STMT_CONTACT || parent_stmt == LY_STMT_DESCRIPTION || + parent_stmt == LY_STMT_REFERENCE); + + /* check attributes */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NONE, NULL, Y_MAYBE_STR_ARG, parent_stmt)); + + /* parse content */ + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, parent_stmt, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse error-message element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[out] value Where the content of error-message element should be stored. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_err_msg(struct lysp_yin_ctx *ctx, const void *parent, const char **value, struct lysp_ext_instance **exts) +{ + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + {LY_STMT_ARG_VALUE, value, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE | YIN_SUBELEM_FIRST} + }; + + /* check attributes */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NONE, NULL, Y_MAYBE_STR_ARG, LY_STMT_ERROR_MESSAGE)); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, LY_STMT_ERROR_MESSAGE, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse type element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[in,out] subinfo Information about subelement, is used to determin which function should be called and where + * to store parsed value. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_type(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, struct yin_subelement *subinfo) +{ + struct lysp_type *type = NULL; + + if (parent_stmt == LY_STMT_DEVIATE) { + *(struct lysp_type **)subinfo->dest = calloc(1, sizeof **(struct lysp_type **)subinfo->dest); + LY_CHECK_ERR_RET(!(*(struct lysp_type **)subinfo->dest), LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + type = *((struct lysp_type **)subinfo->dest); + } else { + type = (struct lysp_type *)subinfo->dest; + } + + /* type as child of another type */ + if (parent_stmt == LY_STMT_TYPE) { + struct lysp_type *nested_type = NULL; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, type->types, nested_type, LY_EMEM); + type->flags |= LYS_SET_TYPE; + type = nested_type; + } + + type->pmod = PARSER_CUR_PMOD(ctx); + + struct yin_subelement subelems[] = { + {LY_STMT_BASE, type, 0}, + {LY_STMT_BIT, type, 0}, + {LY_STMT_ENUM, type, 0}, + {LY_STMT_FRACTION_DIGITS, type, YIN_SUBELEM_UNIQUE}, + {LY_STMT_LENGTH, type, YIN_SUBELEM_UNIQUE}, + {LY_STMT_PATH, type, YIN_SUBELEM_UNIQUE}, + {LY_STMT_PATTERN, type, 0}, + {LY_STMT_RANGE, type, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REQUIRE_INSTANCE, type, YIN_SUBELEM_UNIQUE}, + {LY_STMT_TYPE, type, 0}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &type->name, Y_PREF_IDENTIF_ARG, LY_STMT_TYPE)); + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), type, LY_STMT_TYPE, NULL, &type->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, type->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse max-elements element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] max Value to write to. + * @param[in] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_maxelements(struct lysp_yin_ctx *ctx, uint32_t *max, uint16_t *flags, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + const char *temp_val = NULL; + char *ptr; + unsigned long long num; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + *flags |= LYS_SET_MAX; + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + LY_CHECK_GOTO(ret = yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_MAX_ELEMENTS), cleanup); + if (!temp_val || (temp_val[0] == '\0') || (temp_val[0] == '0') || ((temp_val[0] != 'u') && !isdigit(temp_val[0]))) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + + if (strcmp(temp_val, "unbounded")) { + errno = 0; + num = strtoull(temp_val, &ptr, LY_BASE_DEC); + if (*ptr != '\0') { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + if ((errno == ERANGE) || (num > UINT32_MAX)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_OOB_YIN, temp_val, "value", "max-elements"); + ret = LY_EVALID; + goto cleanup; + } + *max = num; + } + + ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), max, LY_STMT_MAX_ELEMENTS, NULL, exts); + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + lydict_remove(ctx->xmlctx->ctx, temp_val); + return ret; +} + +/** + * @brief Parse min-elements element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] min Value to write to. + * @param[in] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_minelements(struct lysp_yin_ctx *ctx, uint32_t *min, uint16_t *flags, struct lysp_ext_instance **exts) +{ + const char *temp_val = NULL; + char *ptr; + unsigned long long num; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + *flags |= LYS_SET_MIN; + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_MIN_ELEMENTS)); + + if (!temp_val || (temp_val[0] == '\0') || ((temp_val[0] == '0') && (temp_val[1] != '\0'))) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "min-elements"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + + errno = 0; + num = strtoull(temp_val, &ptr, LY_BASE_DEC); + if (ptr[0] != 0) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN, temp_val, "value", "min-elements"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + if ((errno == ERANGE) || (num > UINT32_MAX)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_OOB_YIN, temp_val, "value", "min-elements"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + *min = num; + lydict_remove(ctx->xmlctx->ctx, temp_val); + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), min, LY_STMT_MIN_ELEMENTS, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse min-elements or max-elements element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[in] cur_stmt Type of current element. + * @param[in] dest Where the parsed value and flags should be stored. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_minmax(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, enum ly_stmt cur_stmt, void *dest) +{ + uint32_t *lim; + uint16_t *flags; + struct lysp_ext_instance **exts; + + assert(cur_stmt == LY_STMT_MAX_ELEMENTS || cur_stmt == LY_STMT_MIN_ELEMENTS); + assert(parent_stmt == LY_STMT_LEAF_LIST || parent_stmt == LY_STMT_REFINE || parent_stmt == LY_STMT_LIST || + parent_stmt == LY_STMT_DEVIATE); + + if (parent_stmt == LY_STMT_LEAF_LIST) { + lim = (cur_stmt == LY_STMT_MAX_ELEMENTS) ? &((struct lysp_node_leaflist *)dest)->max : &((struct lysp_node_leaflist *)dest)->min; + flags = &((struct lysp_node_leaflist *)dest)->flags; + exts = &((struct lysp_node_leaflist *)dest)->exts; + } else if (parent_stmt == LY_STMT_REFINE) { + lim = (cur_stmt == LY_STMT_MAX_ELEMENTS) ? &((struct lysp_refine *)dest)->max : &((struct lysp_refine *)dest)->min; + flags = &((struct lysp_refine *)dest)->flags; + exts = &((struct lysp_refine *)dest)->exts; + } else if (parent_stmt == LY_STMT_LIST) { + lim = (cur_stmt == LY_STMT_MAX_ELEMENTS) ? &((struct lysp_node_list *)dest)->max : &((struct lysp_node_list *)dest)->min; + flags = &((struct lysp_node_list *)dest)->flags; + exts = &((struct lysp_node_list *)dest)->exts; + } else { + lim = ((struct minmax_dev_meta *)dest)->lim; + flags = ((struct minmax_dev_meta *)dest)->flags; + exts = ((struct minmax_dev_meta *)dest)->exts; + } + + if (cur_stmt == LY_STMT_MAX_ELEMENTS) { + LY_CHECK_RET(yin_parse_maxelements(ctx, lim, flags, exts)); + } else { + LY_CHECK_RET(yin_parse_minelements(ctx, lim, flags, exts)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse ordered-by element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[out] flags Flags to write to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_orderedby(struct lysp_yin_ctx *ctx, const void *parent, uint16_t *flags, struct lysp_ext_instance **exts) +{ + const char *temp_val; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_ORDERED_BY)); + if (strcmp(temp_val, "system") == 0) { + *flags |= LYS_ORDBY_SYSTEM; + } else if (strcmp(temp_val, "user") == 0) { + *flags |= LYS_ORDBY_USER; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS2, temp_val, "value", + "ordered-by", "system", "user"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, LY_STMT_ORDERED_BY, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse any-data or any-xml element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] any_stmt Type of current statement, can be set to LY_STMT_ANYDATA or LY_STMT_ANYXML + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_any(struct lysp_yin_ctx *ctx, enum ly_stmt any_kw, struct tree_node_meta *node_meta) +{ + struct lysp_node_anydata *any; + + /* create new sibling */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, any, next, LY_EMEM); + any->nodetype = (any_kw == LY_STMT_ANYDATA) ? LYS_ANYDATA : LYS_ANYXML; + any->parent = node_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &any->name, Y_IDENTIF_ARG, any_kw)); + + struct yin_subelement subelems[] = { + {LY_STMT_CONFIG, &any->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DESCRIPTION, &any->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &any->iffeatures, 0}, + {LY_STMT_MANDATORY, &any->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MUST, &any->musts, 0}, + {LY_STMT_REFERENCE, &any->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &any->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_WHEN, &any->when, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), any, any_kw, NULL, &any->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, any->exts)); + + return LY_SUCCESS; +} + +/** + * @brief parse leaf element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_leaf(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + struct lysp_node_leaf *leaf; + + /* create structure new leaf */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, leaf, next, LY_EMEM); + leaf->nodetype = LYS_LEAF; + leaf->parent = node_meta->parent; + + /* parser argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &leaf->name, Y_IDENTIF_ARG, LY_STMT_LEAF)); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_CONFIG, &leaf->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DEFAULT, &leaf->dflt, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DESCRIPTION, &leaf->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &leaf->iffeatures, 0}, + {LY_STMT_MANDATORY, &leaf->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MUST, &leaf->musts, 0}, + {LY_STMT_REFERENCE, &leaf->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &leaf->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_TYPE, &leaf->type, YIN_SUBELEM_UNIQUE | YIN_SUBELEM_MANDATORY}, + {LY_STMT_UNITS, &leaf->units, YIN_SUBELEM_UNIQUE}, + {LY_STMT_WHEN, &leaf->when, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), leaf, LY_STMT_LEAF, NULL, &leaf->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, leaf->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse leaf-list element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_leaflist(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + struct lysp_node_leaflist *llist; + + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, llist, next, LY_EMEM); + + llist->nodetype = LYS_LEAFLIST; + llist->parent = node_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &llist->name, Y_IDENTIF_ARG, LY_STMT_LEAF_LIST)); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_CONFIG, &llist->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DEFAULT, &llist->dflts, 0}, + {LY_STMT_DESCRIPTION, &llist->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &llist->iffeatures, 0}, + {LY_STMT_MAX_ELEMENTS, llist, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MIN_ELEMENTS, llist, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MUST, &llist->musts, 0}, + {LY_STMT_ORDERED_BY, &llist->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &llist->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &llist->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_TYPE, &llist->type, YIN_SUBELEM_UNIQUE | YIN_SUBELEM_MANDATORY}, + {LY_STMT_UNITS, &llist->units, YIN_SUBELEM_UNIQUE}, + {LY_STMT_WHEN, &llist->when, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), llist, LY_STMT_LEAF_LIST, NULL, &llist->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, llist->exts)); + + /* check invalid combination of subelements */ + if ((llist->min) && (llist->dflts)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INCHILDSTMSCOMB_YIN, "min-elements", "default", "leaf-list"); + return LY_EVALID; + } + if (llist->max && (llist->min > llist->max)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_MINMAX, llist->min, llist->max); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse typedef element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] typedef_meta Meta information about parent node and typedefs to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_typedef(struct lysp_yin_ctx *ctx, struct tree_node_meta *typedef_meta) +{ + struct lysp_tpdf *tpdf; + struct lysp_tpdf **tpdfs = (struct lysp_tpdf **)typedef_meta->nodes; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *tpdfs, tpdf, LY_EMEM); + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &tpdf->name, Y_IDENTIF_ARG, LY_STMT_TYPEDEF)); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_DEFAULT, &tpdf->dflt, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DESCRIPTION, &tpdf->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &tpdf->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &tpdf->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_TYPE, &tpdf->type, YIN_SUBELEM_UNIQUE | YIN_SUBELEM_MANDATORY}, + {LY_STMT_UNITS, &tpdf->units, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), tpdf, LY_STMT_TYPEDEF, NULL, &tpdf->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, tpdf->exts)); + + /* store data for collision check */ + if (typedef_meta->parent) { + assert(ctx->main_ctx); + LY_CHECK_RET(ly_set_add(&ctx->main_ctx->tpdfs_nodes, typedef_meta->parent, 0, NULL)); + } + + return LY_SUCCESS; +} + +/** + * @brief Parse refine element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] refines Refines to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_refine(struct lysp_yin_ctx *ctx, struct lysp_refine **refines) +{ + struct lysp_refine *rf; + + /* allocate new refine */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *refines, rf, LY_EMEM); + + /* parse attribute */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_TARGET_NODE, &rf->nodeid, Y_STR_ARG, LY_STMT_REFINE)); + CHECK_NONEMPTY(ctx, strlen(rf->nodeid), "refine"); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_CONFIG, &rf->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DEFAULT, &rf->dflts, 0}, + {LY_STMT_DESCRIPTION, &rf->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &rf->iffeatures, 0}, + {LY_STMT_MANDATORY, &rf->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MAX_ELEMENTS, rf, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MIN_ELEMENTS, rf, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MUST, &rf->musts, 0}, + {LY_STMT_PRESENCE, &rf->presence, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &rf->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), rf, LY_STMT_REFINE, NULL, &rf->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, rf->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse uses element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_uses(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + struct lysp_node_uses *uses; + + /* create new uses */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, uses, next, LY_EMEM); + uses->nodetype = LYS_USES; + uses->parent = node_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &uses->name, Y_PREF_IDENTIF_ARG, LY_STMT_USES)); + + /* parse content */ + struct tree_node_meta augments = {(struct lysp_node *)uses, (struct lysp_node **)&uses->augments}; + struct yin_subelement subelems[] = { + {LY_STMT_AUGMENT, &augments, 0}, + {LY_STMT_DESCRIPTION, &uses->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &uses->iffeatures, 0}, + {LY_STMT_REFERENCE, &uses->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFINE, &uses->refines, 0}, + {LY_STMT_STATUS, &uses->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_WHEN, &uses->when, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), uses, LY_STMT_USES, NULL, &uses->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, uses->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse revision element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] revs Parsed revisions to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_revision(struct lysp_yin_ctx *ctx, struct lysp_revision **revs) +{ + struct lysp_revision *rev; + const char *temp_date = NULL; + + /* allocate new reivison */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *revs, rev, LY_EMEM); + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_DATE, &temp_date, Y_STR_ARG, LY_STMT_REVISION)); + /* check value */ + if (lysp_check_date((struct lysp_ctx *)ctx, temp_date, strlen(temp_date), "revision")) { + lydict_remove(ctx->xmlctx->ctx, temp_date); + return LY_EVALID; + } + memcpy(rev->date, temp_date, LY_REV_SIZE); + lydict_remove(ctx->xmlctx->ctx, temp_date); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &rev->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &rev->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), rev, LY_STMT_REVISION, NULL, &rev->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, rev->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse include element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] inc_meta Meta informatinou about module/submodule name and includes to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_include(struct lysp_yin_ctx *ctx, struct include_meta *inc_meta) +{ + struct lysp_include *inc; + + /* allocate new include */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *inc_meta->includes, inc, LY_EMEM); + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_MODULE, &inc->name, Y_IDENTIF_ARG, LY_STMT_INCLUDE)); + + /* submodules share the namespace with the module names, so there must not be + * a module of the same name in the context, no need for revision matching */ + if (!strcmp(inc_meta->name, inc->name) || ly_ctx_get_module_latest(ctx->xmlctx->ctx, inc->name)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_NAME2_COL, "module", "submodule", inc->name); + return LY_EVALID; + } + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &inc->dsc, YIN_SUBELEM_UNIQUE | YIN_SUBELEM_VER2}, + {LY_STMT_REFERENCE, &inc->ref, YIN_SUBELEM_UNIQUE | YIN_SUBELEM_VER2}, + {LY_STMT_REVISION_DATE, &inc->rev, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), inc, LY_STMT_INCLUDE, NULL, &inc->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, inc->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse revision-date element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] rev Array to store the parsed value in. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_revision_date(struct lysp_yin_ctx *ctx, char *rev, struct lysp_ext_instance **exts) +{ + const char *temp_rev; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_DATE, &temp_rev, Y_STR_ARG, LY_STMT_REVISION_DATE)); + LY_CHECK_ERR_RET(lysp_check_date((struct lysp_ctx *)ctx, temp_rev, strlen(temp_rev), "revision-date") != LY_SUCCESS, + lydict_remove(ctx->xmlctx->ctx, temp_rev), LY_EVALID); + + strcpy(rev, temp_rev); + lydict_remove(ctx->xmlctx->ctx, temp_rev); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), rev, LY_STMT_REVISION_DATE, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse config element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_config(struct lysp_yin_ctx *ctx, uint16_t *flags, struct lysp_ext_instance **exts) +{ + const char *temp_val = NULL; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_CONFIG)); + if (strcmp(temp_val, "true") == 0) { + *flags |= LYS_CONFIG_W; + } else if (strcmp(temp_val, "false") == 0) { + *flags |= LYS_CONFIG_R; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS2, temp_val, "value", "config", + "true", "false"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), flags, LY_STMT_CONFIG, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse yang-version element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[out] version Storage for the parsed information. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_yangversion(struct lysp_yin_ctx *ctx, const void *parent, uint8_t *version, struct lysp_ext_instance **exts) +{ + const char *temp_version = NULL; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_version, Y_STR_ARG, LY_STMT_YANG_VERSION)); + if (strcmp(temp_version, "1") == 0) { + *version = LYS_VERSION_1_0; + } else if (strcmp(temp_version, "1.1") == 0) { + *version = LYS_VERSION_1_1; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS2, temp_version, "value", + "yang-version", "1", "1.1"); + lydict_remove(ctx->xmlctx->ctx, temp_version); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_version); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, LY_STMT_YANG_VERSION, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse import element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] imp_meta Meta information about prefix and imports to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_import(struct lysp_yin_ctx *ctx, struct import_meta *imp_meta) +{ + struct lysp_import *imp; + + /* allocate new element in sized array for import */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *imp_meta->imports, imp, LY_EMEM); + + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &imp->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_PREFIX, &imp->prefix, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &imp->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REVISION_DATE, imp->rev, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + /* parse import attributes */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_MODULE, &imp->name, Y_IDENTIF_ARG, LY_STMT_IMPORT)); + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), imp, LY_STMT_IMPORT, NULL, &imp->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, imp->exts)); + + /* check prefix validity */ + LY_CHECK_RET(lysp_check_prefix((struct lysp_ctx *)ctx, *imp_meta->imports, imp_meta->prefix, &imp->prefix), LY_EVALID); + + return LY_SUCCESS; +} + +/** + * @brief Parse mandatory element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_mandatory(struct lysp_yin_ctx *ctx, uint16_t *flags, struct lysp_ext_instance **exts) +{ + const char *temp_val = NULL; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_MANDATORY)); + if (strcmp(temp_val, "true") == 0) { + *flags |= LYS_MAND_TRUE; + } else if (strcmp(temp_val, "false") == 0) { + *flags |= LYS_MAND_FALSE; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS2, temp_val, "value", + "mandatory", "true", "false"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), flags, LY_STMT_MANDATORY, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse status element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_status(struct lysp_yin_ctx *ctx, uint16_t *flags, struct lysp_ext_instance **exts) +{ + const char *value = NULL; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &value, Y_STR_ARG, LY_STMT_STATUS)); + if (strcmp(value, "current") == 0) { + *flags |= LYS_STATUS_CURR; + } else if (strcmp(value, "deprecated") == 0) { + *flags |= LYS_STATUS_DEPRC; + } else if (strcmp(value, "obsolete") == 0) { + *flags |= LYS_STATUS_OBSLT; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS3, value, "value", + "status", "current", "deprecated", "obsolete"); + lydict_remove(ctx->xmlctx->ctx, value); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, value); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), flags, LY_STMT_STATUS, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse when element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[out] when_p When pointer to parse to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_when(struct lysp_yin_ctx *ctx, struct lysp_when **when_p) +{ + struct lysp_when *when; + LY_ERR ret = LY_SUCCESS; + + when = calloc(1, sizeof *when); + LY_CHECK_ERR_RET(!when, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + + ret = lyxml_ctx_next(ctx->xmlctx); + LY_CHECK_ERR_RET(ret, free(when), ret); + + ret = yin_parse_attribute(ctx, YIN_ARG_CONDITION, &when->cond, Y_STR_ARG, LY_STMT_WHEN); + LY_CHECK_ERR_RET(ret, free(when), ret); + + *when_p = when; + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &when->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &when->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), when, LY_STMT_WHEN, NULL, &when->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, when->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse yin-elemenet element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in,out] flags Flags to add to. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_yin_element(struct lysp_yin_ctx *ctx, const void *parent, uint16_t *flags, struct lysp_ext_instance **exts) +{ + const char *temp_val = NULL; + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_YIN_ELEMENT)); + if (strcmp(temp_val, "true") == 0) { + *flags |= LYS_YINELEM_TRUE; + } else if (strcmp(temp_val, "false") == 0) { + *flags |= LYS_YINELEM_FALSE; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS2, temp_val, "value", + "yin-element", "true", "false"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, LY_STMT_YIN_ELEMENT, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse argument element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in,out] arg_meta Meta information about destination of parsed data. + * @param[in,out] exts Extension instances to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_argument(struct lysp_yin_ctx *ctx, const void *parent, struct yin_argument_meta *arg_meta, struct lysp_ext_instance **exts) +{ + struct yin_subelement subelems[] = { + {LY_STMT_YIN_ELEMENT, arg_meta->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, arg_meta->argument, Y_IDENTIF_ARG, LY_STMT_ARGUMENT)); + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), parent, LY_STMT_ARGUMENT, NULL, exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse the extension statement. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] extensions Extensions to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_extension(struct lysp_yin_ctx *ctx, struct lysp_ext **extensions) +{ + struct lysp_ext *ex; + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *extensions, ex, LY_EMEM); + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &ex->name, Y_IDENTIF_ARG, LY_STMT_EXTENSION)); + + struct yin_argument_meta arg_info = {&ex->flags, &ex->argname}; + struct yin_subelement subelems[] = { + {LY_STMT_ARGUMENT, &arg_info, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DESCRIPTION, &ex->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_REFERENCE, &ex->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &ex->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), ex, LY_STMT_EXTENSION, NULL, &ex->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, ex->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse feature element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] features Features to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_feature(struct lysp_yin_ctx *ctx, struct lysp_feature **features) +{ + struct lysp_feature *feat; + + /* allocate new feature */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *features, feat, LY_EMEM); + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &feat->name, Y_IDENTIF_ARG, LY_STMT_FEATURE)); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &feat->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &feat->iffeatures, 0}, + {LY_STMT_REFERENCE, &feat->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &feat->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), feat, LY_STMT_FEATURE, NULL, &feat->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, feat->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse identity element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] identities Identities to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_identity(struct lysp_yin_ctx *ctx, struct lysp_ident **identities) +{ + struct lysp_ident *ident; + + /* allocate new identity */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *identities, ident, LY_EMEM); + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &ident->name, Y_IDENTIF_ARG, LY_STMT_IDENTITY)); + + /* parse content */ + struct yin_subelement subelems[] = { + {LY_STMT_BASE, &ident->bases, 0}, + {LY_STMT_DESCRIPTION, &ident->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_IF_FEATURE, &ident->iffeatures, YIN_SUBELEM_VER2}, + {LY_STMT_REFERENCE, &ident->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_STATUS, &ident->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + LY_CHECK_RET(yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), ident, LY_STMT_IDENTITY, NULL, &ident->exts)); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, ident->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse list element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_list(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + struct lysp_node_list *list; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, list, next, LY_EMEM); + list->nodetype = LYS_LIST; + list->parent = node_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &list->name, Y_IDENTIF_ARG, LY_STMT_LIST)); + + /* parse list content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 25, (struct lysp_node *)list, &subelems, + LY_STMT_ACTION, &list->actions, 0, + LY_STMT_ANYDATA, &list->child, 0, + LY_STMT_ANYXML, &list->child, 0, + LY_STMT_CHOICE, &list->child, 0, + LY_STMT_CONFIG, &list->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_CONTAINER, &list->child, 0, + LY_STMT_DESCRIPTION, &list->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_GROUPING, &list->groupings, 0, + LY_STMT_IF_FEATURE, &list->iffeatures, 0, + LY_STMT_KEY, &list->key, YIN_SUBELEM_UNIQUE, + LY_STMT_LEAF, &list->child, 0, + LY_STMT_LEAF_LIST, &list->child, 0, + LY_STMT_LIST, &list->child, 0, + LY_STMT_MAX_ELEMENTS, list, YIN_SUBELEM_UNIQUE, + LY_STMT_MIN_ELEMENTS, list, YIN_SUBELEM_UNIQUE, + LY_STMT_MUST, &list->musts, 0, + LY_STMT_NOTIFICATION, &list->notifs, 0, + LY_STMT_ORDERED_BY, &list->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_REFERENCE, &list->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &list->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_TYPEDEF, &list->typedefs, 0, + LY_STMT_UNIQUE, &list->uniques, 0, + LY_STMT_USES, &list->child, 0, + LY_STMT_WHEN, &list->when, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, list, LY_STMT_LIST, NULL, &list->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, list->exts)); + + if (list->max && (list->min > list->max)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_MINMAX, list->min, list->max); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse notification element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] notif_meta Meta information about parent node and notifications to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_notification(struct lysp_yin_ctx *ctx, struct tree_node_meta *notif_meta) +{ + struct lysp_node_notif *notif; + struct lysp_node_notif **notifs = (struct lysp_node_notif **)notif_meta->nodes; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + /* allocate new notification */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, notifs, notif, next, LY_EMEM); + notif->nodetype = LYS_NOTIF; + notif->parent = notif_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, ¬if->name, Y_IDENTIF_ARG, LY_STMT_NOTIFICATION)); + + /* parse notification content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 16, (struct lysp_node *)notif, &subelems, + LY_STMT_ANYDATA, ¬if->child, 0, + LY_STMT_ANYXML, ¬if->child, 0, + LY_STMT_CHOICE, ¬if->child, 0, + LY_STMT_CONTAINER, ¬if->child, 0, + LY_STMT_DESCRIPTION, ¬if->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_GROUPING, ¬if->groupings, 0, + LY_STMT_IF_FEATURE, ¬if->iffeatures, 0, + LY_STMT_LEAF, ¬if->child, 0, + LY_STMT_LEAF_LIST, ¬if->child, 0, + LY_STMT_LIST, ¬if->child, 0, + LY_STMT_MUST, ¬if->musts, YIN_SUBELEM_VER2, + LY_STMT_REFERENCE, ¬if->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, ¬if->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_TYPEDEF, ¬if->typedefs, 0, + LY_STMT_USES, ¬if->child, 0, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + + ret = yin_parse_content(ctx, subelems, subelems_size, notif, LY_STMT_NOTIFICATION, NULL, ¬if->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, notif->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse grouping element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in,out] gr_meta Meta information about parent node and groupings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_grouping(struct lysp_yin_ctx *ctx, struct tree_node_meta *gr_meta) +{ + struct lysp_node_grp *grp; + struct lysp_node_grp **grps = (struct lysp_node_grp **)gr_meta->nodes; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + /* create new grouping */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, grps, grp, next, LY_EMEM); + grp->nodetype = LYS_GROUPING; + grp->parent = gr_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &grp->name, Y_IDENTIF_ARG, LY_STMT_GROUPING)); + + /* parse grouping content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 16, &grp->node, &subelems, + LY_STMT_ACTION, &grp->actions, 0, + LY_STMT_ANYDATA, &grp->child, 0, + LY_STMT_ANYXML, &grp->child, 0, + LY_STMT_CHOICE, &grp->child, 0, + LY_STMT_CONTAINER, &grp->child, 0, + LY_STMT_DESCRIPTION, &grp->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_GROUPING, &grp->groupings, 0, + LY_STMT_LEAF, &grp->child, 0, + LY_STMT_LEAF_LIST, &grp->child, 0, + LY_STMT_LIST, &grp->child, 0, + LY_STMT_NOTIFICATION, &grp->notifs, 0, + LY_STMT_REFERENCE, &grp->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &grp->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_TYPEDEF, &grp->typedefs, 0, + LY_STMT_USES, &grp->child, 0, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, grp, LY_STMT_GROUPING, NULL, &grp->exts); + subelems_deallocator(subelems_size, subelems); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, grp->exts)); + + /* store data for collision check */ + if (!ret && grp->parent) { + assert(ctx->main_ctx); + LY_CHECK_RET(ly_set_add(&ctx->main_ctx->grps_nodes, grp->parent, 0, NULL)); + } + + return ret; +} + +/** + * @brief Parse container element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_container(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + struct lysp_node_container *cont; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + /* create new container */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, cont, next, LY_EMEM); + cont->nodetype = LYS_CONTAINER; + cont->parent = node_meta->parent; + + /* parse aegument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &cont->name, Y_IDENTIF_ARG, LY_STMT_CONTAINER)); + + /* parse container content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 21, (struct lysp_node *)cont, &subelems, + LY_STMT_ACTION, &cont->actions, YIN_SUBELEM_VER2, + LY_STMT_ANYDATA, &cont->child, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &cont->child, 0, + LY_STMT_CHOICE, &cont->child, 0, + LY_STMT_CONFIG, &cont->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_CONTAINER, &cont->child, 0, + LY_STMT_DESCRIPTION, &cont->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_GROUPING, &cont->groupings, 0, + LY_STMT_IF_FEATURE, &cont->iffeatures, 0, + LY_STMT_LEAF, &cont->child, 0, + LY_STMT_LEAF_LIST, &cont->child, 0, + LY_STMT_LIST, &cont->child, 0, + LY_STMT_MUST, &cont->musts, 0, + LY_STMT_NOTIFICATION, &cont->notifs, YIN_SUBELEM_VER2, + LY_STMT_PRESENCE, &cont->presence, YIN_SUBELEM_UNIQUE, + LY_STMT_REFERENCE, &cont->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &cont->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_TYPEDEF, &cont->typedefs, 0, + LY_STMT_USES, &cont->child, 0, + LY_STMT_WHEN, &cont->when, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, cont, LY_STMT_CONTAINER, NULL, &cont->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, cont->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse case element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_case(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + struct lysp_node_case *cas; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + /* create new case */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, cas, next, LY_EMEM); + cas->nodetype = LYS_CASE; + cas->parent = node_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &cas->name, Y_IDENTIF_ARG, LY_STMT_CASE)); + + /* parse case content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 14, (struct lysp_node *)cas, &subelems, + LY_STMT_ANYDATA, &cas->child, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &cas->child, 0, + LY_STMT_CHOICE, &cas->child, 0, + LY_STMT_CONTAINER, &cas->child, 0, + LY_STMT_DESCRIPTION, &cas->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_IF_FEATURE, &cas->iffeatures, 0, + LY_STMT_LEAF, &cas->child, 0, + LY_STMT_LEAF_LIST, &cas->child, 0, + LY_STMT_LIST, &cas->child, 0, + LY_STMT_REFERENCE, &cas->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &cas->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_USES, &cas->child, 0, + LY_STMT_WHEN, &cas->when, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, cas, LY_STMT_CASE, NULL, &cas->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, cas->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse choice element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] node_meta Meta information about parent node and siblings to add to. + * @return LY_ERR values. + */ +LY_ERR +yin_parse_choice(struct lysp_yin_ctx *ctx, struct tree_node_meta *node_meta) +{ + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + struct lysp_node_choice *choice; + size_t subelems_size; + + /* create new choice */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, node_meta->nodes, choice, next, LY_EMEM); + + choice->nodetype = LYS_CHOICE; + choice->parent = node_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &choice->name, Y_IDENTIF_ARG, LY_STMT_CHOICE)); + + /* parse choice content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 17, (struct lysp_node *)choice, &subelems, + LY_STMT_ANYDATA, &choice->child, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &choice->child, 0, + LY_STMT_CASE, &choice->child, 0, + LY_STMT_CHOICE, &choice->child, YIN_SUBELEM_VER2, + LY_STMT_CONFIG, &choice->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_CONTAINER, &choice->child, 0, + LY_STMT_DEFAULT, &choice->dflt, YIN_SUBELEM_UNIQUE, + LY_STMT_DESCRIPTION, &choice->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_IF_FEATURE, &choice->iffeatures, 0, + LY_STMT_LEAF, &choice->child, 0, + LY_STMT_LEAF_LIST, &choice->child, 0, + LY_STMT_LIST, &choice->child, 0, + LY_STMT_MANDATORY, &choice->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_REFERENCE, &choice->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &choice->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_WHEN, &choice->when, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, choice, LY_STMT_CHOICE, NULL, &choice->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, choice->exts)); + + return ret; +} + +/** + * @brief Parse input or output element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] inout_kw Identification of input/output element. + * @param[in] inout_meta Meta information about parent node and siblings and input/output pointer to write to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_inout(struct lysp_yin_ctx *ctx, enum ly_stmt inout_kw, struct inout_meta *inout_meta) +{ + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + /* initiate structure */ + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), (inout_kw == LY_STMT_INPUT) ? "input" : "output", 0, &inout_meta->inout_p->name)); + inout_meta->inout_p->nodetype = (inout_kw == LY_STMT_INPUT) ? LYS_INPUT : LYS_OUTPUT; + inout_meta->inout_p->parent = inout_meta->parent; + + /* check attributes */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NONE, NULL, Y_MAYBE_STR_ARG, inout_kw)); + + /* parser input/output content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 12, (struct lysp_node *)inout_meta->inout_p, &subelems, + LY_STMT_ANYDATA, &inout_meta->inout_p->child, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &inout_meta->inout_p->child, 0, + LY_STMT_CHOICE, &inout_meta->inout_p->child, 0, + LY_STMT_CONTAINER, &inout_meta->inout_p->child, 0, + LY_STMT_GROUPING, &inout_meta->inout_p->groupings, 0, + LY_STMT_LEAF, &inout_meta->inout_p->child, 0, + LY_STMT_LEAF_LIST, &inout_meta->inout_p->child, 0, + LY_STMT_LIST, &inout_meta->inout_p->child, 0, + LY_STMT_MUST, &inout_meta->inout_p->musts, YIN_SUBELEM_VER2, + LY_STMT_TYPEDEF, &inout_meta->inout_p->typedefs, 0, + LY_STMT_USES, &inout_meta->inout_p->child, 0, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, inout_meta->inout_p, inout_kw, NULL, &inout_meta->inout_p->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, inout_meta->inout_p->exts)); + + if (!inout_meta->inout_p->child) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_MISSTMT, "data-def-stmt", lyplg_ext_stmt2str(inout_kw)); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse action element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] act_meta Meta information about parent node and actions to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_action(struct lysp_yin_ctx *ctx, struct tree_node_meta *act_meta) +{ + struct lysp_node_action *act, **acts = (struct lysp_node_action **)act_meta->nodes; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + enum ly_stmt kw = act_meta->parent ? LY_STMT_ACTION : LY_STMT_RPC; + + /* create new action */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, acts, act, next, LY_EMEM); + act->nodetype = act_meta->parent ? LYS_ACTION : LYS_RPC; + act->parent = act_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &act->name, Y_IDENTIF_ARG, kw)); + + /* parse content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 9, (struct lysp_node *)act, &subelems, + LY_STMT_DESCRIPTION, &act->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_GROUPING, &act->groupings, 0, + LY_STMT_IF_FEATURE, &act->iffeatures, 0, + LY_STMT_INPUT, &act->input, YIN_SUBELEM_UNIQUE, + LY_STMT_OUTPUT, &act->output, YIN_SUBELEM_UNIQUE, + LY_STMT_REFERENCE, &act->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &act->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_TYPEDEF, &act->typedefs, 0, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = (yin_parse_content(ctx, subelems, subelems_size, act, kw, NULL, &act->exts)); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* always initialize inout, they are technically present (needed for later deviations/refines) */ + if (!act->input.nodetype) { + act->input.nodetype = LYS_INPUT; + act->input.parent = (struct lysp_node *)act; + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), "input", 0, &act->input.name)); + } + if (!act->output.nodetype) { + act->output.nodetype = LYS_OUTPUT; + act->output.parent = (struct lysp_node *)act; + LY_CHECK_RET(lydict_insert(PARSER_CTX(ctx), "output", 0, &act->output.name)); + } + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, act->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse augment element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] aug_meta Meta information about parent node and augments to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_augment(struct lysp_yin_ctx *ctx, struct tree_node_meta *aug_meta) +{ + struct lysp_node_augment *aug; + struct lysp_node_augment **augs = (struct lysp_node_augment **)aug_meta->nodes; + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + size_t subelems_size; + + /* create new augment */ + LY_LIST_NEW_RET(ctx->xmlctx->ctx, augs, aug, next, LY_EMEM); + aug->nodetype = LYS_AUGMENT; + aug->parent = aug_meta->parent; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_TARGET_NODE, &aug->nodeid, Y_STR_ARG, LY_STMT_AUGMENT)); + CHECK_NONEMPTY((struct lysp_ctx *)ctx, strlen(aug->nodeid), "augment"); + + /* parser augment content */ + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 17, (struct lysp_node *)aug, &subelems, + LY_STMT_ACTION, &aug->actions, YIN_SUBELEM_VER2, + LY_STMT_ANYDATA, &aug->child, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &aug->child, 0, + LY_STMT_CASE, &aug->child, 0, + LY_STMT_CHOICE, &aug->child, 0, + LY_STMT_CONTAINER, &aug->child, 0, + LY_STMT_DESCRIPTION, &aug->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_IF_FEATURE, &aug->iffeatures, 0, + LY_STMT_LEAF, &aug->child, 0, + LY_STMT_LEAF_LIST, &aug->child, 0, + LY_STMT_LIST, &aug->child, 0, + LY_STMT_NOTIFICATION, &aug->notifs, YIN_SUBELEM_VER2, + LY_STMT_REFERENCE, &aug->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_STATUS, &aug->flags, YIN_SUBELEM_UNIQUE, + LY_STMT_USES, &aug->child, 0, + LY_STMT_WHEN, &aug->when, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + ret = yin_parse_content(ctx, subelems, subelems_size, aug, LY_STMT_AUGMENT, NULL, &aug->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, aug->exts)); + + return LY_SUCCESS; +} + +/** + * @brief Parse deviate element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] deviates Deviates to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_deviate(struct lysp_yin_ctx *ctx, struct lysp_deviate **deviates) +{ + LY_ERR ret = LY_SUCCESS; + uint8_t dev_mod; + const char *temp_val; + struct lysp_deviate *d; + struct lysp_deviate_add *d_add = NULL; + struct lysp_deviate_rpl *d_rpl = NULL; + struct lysp_deviate_del *d_del = NULL; + + /* parse argument */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, &temp_val, Y_STR_ARG, LY_STMT_DEVIATE)); + + if (strcmp(temp_val, "not-supported") == 0) { + dev_mod = LYS_DEV_NOT_SUPPORTED; + } else if (strcmp(temp_val, "add") == 0) { + dev_mod = LYS_DEV_ADD; + } else if (strcmp(temp_val, "replace") == 0) { + dev_mod = LYS_DEV_REPLACE; + } else if (strcmp(temp_val, "delete") == 0) { + dev_mod = LYS_DEV_DELETE; + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INVAL_YIN VALID_VALS4, temp_val, "value", "deviate", + "not-supported", "add", "replace", "delete"); + lydict_remove(ctx->xmlctx->ctx, temp_val); + return LY_EVALID; + } + lydict_remove(ctx->xmlctx->ctx, temp_val); + + if (dev_mod == LYS_DEV_NOT_SUPPORTED) { + d = calloc(1, sizeof *d); + LY_CHECK_ERR_RET(!d, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + struct yin_subelement subelems[] = { + {LY_STMT_EXTENSION_INSTANCE, NULL, 0} + }; + + ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), d, LY_STMT_DEVIATE, NULL, &d->exts); + + } else if (dev_mod == LYS_DEV_ADD) { + d_add = calloc(1, sizeof *d_add); + LY_CHECK_ERR_RET(!d_add, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + d = (struct lysp_deviate *)d_add; + struct minmax_dev_meta min = {&d_add->min, &d_add->flags, &d_add->exts}; + struct minmax_dev_meta max = {&d_add->max, &d_add->flags, &d_add->exts}; + struct yin_subelement subelems[] = { + {LY_STMT_CONFIG, &d_add->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DEFAULT, &d_add->dflts, 0}, + {LY_STMT_MANDATORY, &d_add->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MAX_ELEMENTS, &max, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MIN_ELEMENTS, &min, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MUST, &d_add->musts, 0}, + {LY_STMT_UNIQUE, &d_add->uniques, 0}, + {LY_STMT_UNITS, &d_add->units, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), d, LY_STMT_DEVIATE, NULL, &d->exts); + + } else if (dev_mod == LYS_DEV_REPLACE) { + d_rpl = calloc(1, sizeof *d_rpl); + LY_CHECK_ERR_RET(!d_rpl, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + d = (struct lysp_deviate *)d_rpl; + struct minmax_dev_meta min = {&d_rpl->min, &d_rpl->flags, &d_rpl->exts}; + struct minmax_dev_meta max = {&d_rpl->max, &d_rpl->flags, &d_rpl->exts}; + struct yin_subelement subelems[] = { + {LY_STMT_CONFIG, &d_rpl->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DEFAULT, &d_rpl->dflt, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MANDATORY, &d_rpl->flags, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MAX_ELEMENTS, &max, YIN_SUBELEM_UNIQUE}, + {LY_STMT_MIN_ELEMENTS, &min, YIN_SUBELEM_UNIQUE}, + {LY_STMT_TYPE, &d_rpl->type, YIN_SUBELEM_UNIQUE}, + {LY_STMT_UNITS, &d_rpl->units, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), d, LY_STMT_DEVIATE, NULL, &d->exts); + + } else { + d_del = calloc(1, sizeof *d_del); + LY_CHECK_ERR_RET(!d_del, LOGMEM(ctx->xmlctx->ctx), LY_EMEM); + d = (struct lysp_deviate *)d_del; + struct yin_subelement subelems[] = { + {LY_STMT_DEFAULT, &d_del->dflts, 0}, + {LY_STMT_MUST, &d_del->musts, 0}, + {LY_STMT_UNIQUE, &d_del->uniques, 0}, + {LY_STMT_UNITS, &d_del->units, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), d, LY_STMT_DEVIATE, NULL, &d->exts); + } + LY_CHECK_GOTO(ret, cleanup); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_GOTO(ret = yin_unres_exts_add(ctx, d->exts), cleanup); + + d->mod = dev_mod; + /* insert into siblings */ + LY_LIST_INSERT(deviates, d, next); + + return ret; + +cleanup: + free(d); + return ret; +} + +/** + * @brief Parse deviation element. + * + * @param[in,out] ctx YIN parser context for logging and to store current state. + * @param[in] deviations Deviations to add to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_deviation(struct lysp_yin_ctx *ctx, struct lysp_deviation **deviations) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_deviation *dev = NULL; + struct lysf_ctx fctx = {.ctx = PARSER_CTX(ctx)}; + + /* create new deviation */ + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *deviations, dev, LY_EMEM); + + /* parse argument */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + LY_CHECK_GOTO(ret = yin_parse_attribute(ctx, YIN_ARG_TARGET_NODE, &dev->nodeid, Y_STR_ARG, LY_STMT_DEVIATION), cleanup); + CHECK_NONEMPTY((struct lysp_ctx *)ctx, strlen(dev->nodeid), "deviation"); + struct yin_subelement subelems[] = { + {LY_STMT_DESCRIPTION, &dev->dsc, YIN_SUBELEM_UNIQUE}, + {LY_STMT_DEVIATE, &dev->deviates, YIN_SUBELEM_MANDATORY}, + {LY_STMT_REFERENCE, &dev->ref, YIN_SUBELEM_UNIQUE}, + {LY_STMT_EXTENSION_INSTANCE, NULL, 0}, + }; + + ret = yin_parse_content(ctx, subelems, ly_sizeofarray(subelems), dev, LY_STMT_DEVIATION, NULL, &dev->exts); + LY_CHECK_GOTO(ret, cleanup); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_GOTO(ret = yin_unres_exts_add(ctx, dev->exts), cleanup); + +cleanup: + if (ret) { + lysp_deviation_free(&fctx, dev); + LY_ARRAY_DECREMENT_FREE(*deviations); + } + return ret; +} + +/** + * @brief map keyword to keyword-group. + * + * @param[in] ctx YIN parser context used for logging. + * @param[in] kw Keyword that is child of module or submodule. + * @param[out] group Group of keyword. + * @return LY_SUCCESS on success LY_EINT if kw can't be mapped to kw_group, should not happen if called correctly. + */ +static LY_ERR +kw2kw_group(struct lysp_yin_ctx *ctx, enum ly_stmt kw, enum yang_module_stmt *group) +{ + switch (kw) { + /* module header */ + case LY_STMT_NONE: + case LY_STMT_NAMESPACE: + case LY_STMT_PREFIX: + case LY_STMT_BELONGS_TO: + case LY_STMT_YANG_VERSION: + *group = Y_MOD_MODULE_HEADER; + break; + /* linkage */ + case LY_STMT_INCLUDE: + case LY_STMT_IMPORT: + *group = Y_MOD_LINKAGE; + break; + /* meta */ + case LY_STMT_ORGANIZATION: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_REFERENCE: + *group = Y_MOD_META; + break; + /* revision */ + case LY_STMT_REVISION: + *group = Y_MOD_REVISION; + break; + /* body */ + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_AUGMENT: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_DEVIATION: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IDENTITY: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_NOTIFICATION: + case LY_STMT_RPC: + case LY_STMT_TYPEDEF: + case LY_STMT_USES: + case LY_STMT_EXTENSION_INSTANCE: + *group = Y_MOD_BODY; + break; + default: + LOGINT(ctx->xmlctx->ctx); + return LY_EINT; + } + + return LY_SUCCESS; +} + +/** + * @brief Check if relative order of two keywords is valid. + * + * @param[in] ctx YIN parser context used for logging. + * @param[in] cur_stmt Type of current statement. + * @param[in] next_stmt Type of next statement. + * @param[in] parent_stmt Type of parent statement, can be se to to LY_STMT_MODULE of LY_STMT_SUBMODULE, + * because relative order is required only in module and submodule sub-elements, used for logging. + * @return LY_SUCCESS on success and LY_EVALID if relative order is invalid. + */ +static LY_ERR +yin_check_relative_order(struct lysp_yin_ctx *ctx, enum ly_stmt cur_stmt, enum ly_stmt next_stmt, enum ly_stmt parent_stmt) +{ + enum yang_module_stmt gr, next_gr; + + assert(parent_stmt == LY_STMT_MODULE || parent_stmt == LY_STMT_SUBMODULE); + + if (cur_stmt == LY_STMT_EXTENSION_INSTANCE) { + /* no order defined */ + return LY_SUCCESS; + } + + LY_CHECK_RET(kw2kw_group(ctx, cur_stmt, &gr)); + LY_CHECK_RET(kw2kw_group(ctx, next_stmt, &next_gr)); + + if (gr > next_gr) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INORDER_YIN, lyplg_ext_stmt2str(parent_stmt), lyplg_ext_stmt2str(next_stmt), + lyplg_ext_stmt2str(cur_stmt)); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse argument of extension subelement that is classic yang keyword and not another instance of extension. + * + * @param[in,out] ctx Yin parser context for logging and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[out] arg Value to write to. + * @return LY_ERR values. + */ +static LY_ERR +yin_parse_extension_instance_arg(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, const char **arg) +{ + enum ly_stmt child; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + + switch (parent_stmt) { + case LY_STMT_ACTION: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_ARGUMENT: + case LY_STMT_BASE: + case LY_STMT_BIT: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_ENUM: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IDENTITY: + case LY_STMT_IF_FEATURE: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_MODULE: + case LY_STMT_NOTIFICATION: + case LY_STMT_RPC: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPE: + case LY_STMT_TYPEDEF: + case LY_STMT_UNITS: + case LY_STMT_USES: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_AUGMENT: + case LY_STMT_DEVIATION: + case LY_STMT_REFINE: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_TARGET_NODE, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_CONFIG: + case LY_STMT_DEFAULT: + case LY_STMT_DEVIATE: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_KEY: + case LY_STMT_LENGTH: + case LY_STMT_MANDATORY: + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + case LY_STMT_MODIFIER: + case LY_STMT_ORDERED_BY: + case LY_STMT_PATH: + case LY_STMT_PATTERN: + case LY_STMT_POSITION: + case LY_STMT_PREFIX: + case LY_STMT_PRESENCE: + case LY_STMT_RANGE: + case LY_STMT_REQUIRE_INSTANCE: + case LY_STMT_STATUS: + case LY_STMT_VALUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_VALUE, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + case LY_STMT_BELONGS_TO: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_MODULE, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NONE, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_MUST: + case LY_STMT_WHEN: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_CONDITION, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_NAMESPACE: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_URI, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_REVISION: + case LY_STMT_REVISION_DATE: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_DATE, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + case LY_STMT_UNIQUE: + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_TAG, arg, Y_MAYBE_STR_ARG, parent_stmt)); + break; + /* argument is mapped to yin element */ + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ORGANIZATION: + case LY_STMT_REFERENCE: + case LY_STMT_ERROR_MESSAGE: + /* there shouldn't be any attribute, argument is supposed to be first subelement */ + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NONE, arg, Y_MAYBE_STR_ARG, parent_stmt)); + + /* no content */ + assert(ctx->xmlctx->status == LYXML_ELEM_CONTENT); + if (ctx->xmlctx->ws_only) { + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + if (((ctx->xmlctx->status == LYXML_ELEM_CONTENT) && !ctx->xmlctx->ws_only) || (ctx->xmlctx->status != LYXML_ELEMENT)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_FIRT_SUBELEM, + parent_stmt == LY_STMT_ERROR_MESSAGE ? "value" : "text", lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + + /* parse child element */ + child = yin_match_keyword(ctx, ctx->xmlctx->name, ctx->xmlctx->name_len, ctx->xmlctx->prefix, + ctx->xmlctx->prefix_len, parent_stmt); + if (((parent_stmt == LY_STMT_ERROR_MESSAGE) && (child != LY_STMT_ARG_VALUE)) || + ((parent_stmt != LY_STMT_ERROR_MESSAGE) && (child != LY_STMT_ARG_TEXT))) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, ctx->xmlctx->name_len, ctx->xmlctx->name, + lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + + /* no attributes expected */ + while (ctx->xmlctx->status == LYXML_ATTRIBUTE) { + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + + /* load and save content */ + INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, *arg); + LY_CHECK_RET(!*arg, LY_EMEM); + + /* load closing tag of subelement */ + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + break; + default: + LOGINT(ctx->xmlctx->ctx); + return LY_EINT; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse yin element into generic structure. + * + * @param[in,out] ctx Yin parser context for XML context, logging, and to store current state. + * @param[in] parent_stmt Type of parent statement. + * @param[out] element Where the element structure should be stored. + * @return LY_ERR values. + */ +LY_ERR +yin_parse_element_generic(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, struct lysp_stmt **element) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_stmt *last = NULL, *new = NULL; + char *id; + + assert(ctx->xmlctx->status == LYXML_ELEMENT); + + /* allocate new structure for element */ + *element = calloc(1, sizeof(**element)); + LY_CHECK_ERR_GOTO(!(*element), LOGMEM(ctx->xmlctx->ctx); ret = LY_EMEM, cleanup); + + /* store identifier */ + if (ctx->xmlctx->prefix_len) { + if (asprintf(&id, "%.*s:%.*s", (int)ctx->xmlctx->prefix_len, ctx->xmlctx->prefix, (int)ctx->xmlctx->name_len, + ctx->xmlctx->name) == -1) { + LOGMEM(ctx->xmlctx->ctx); + ret = LY_EMEM; + goto cleanup; + } + LY_CHECK_GOTO(ret = lydict_insert_zc(ctx->xmlctx->ctx, id, &(*element)->stmt), cleanup); + + /* store prefix data for the statement */ + LY_CHECK_GOTO(ret = ly_store_prefix_data(ctx->xmlctx->ctx, (*element)->stmt, strlen((*element)->stmt), LY_VALUE_XML, + &ctx->xmlctx->ns, &(*element)->format, &(*element)->prefix_data), cleanup); + } else { + LY_CHECK_GOTO(ret = lydict_insert(ctx->xmlctx->ctx, ctx->xmlctx->name, ctx->xmlctx->name_len, &(*element)->stmt), cleanup); + } + + (*element)->kw = yin_match_keyword(ctx, ctx->xmlctx->name, ctx->xmlctx->name_len, ctx->xmlctx->prefix, + ctx->xmlctx->prefix_len, parent_stmt); + + last = (*element)->child; + if ((*element)->kw == LY_STMT_NONE) { + /* unrecognized element */ + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, ctx->xmlctx->name_len, ctx->xmlctx->name, + lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } else if ((*element)->kw != LY_STMT_EXTENSION_INSTANCE) { + /* element is known yang keyword, which means argument can be parsed correctly. */ + ret = yin_parse_extension_instance_arg(ctx, (*element)->kw, &(*element)->arg); + LY_CHECK_GOTO(ret, cleanup); + } else { + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + + /* load attributes in generic way, save all attributes in linked list */ + while (ctx->xmlctx->status == LYXML_ATTRIBUTE) { + new = calloc(1, sizeof(*last)); + LY_CHECK_ERR_GOTO(!new, LOGMEM(ctx->xmlctx->ctx); ret = LY_EMEM, cleanup); + if (!(*element)->child) { + /* save first */ + (*element)->child = new; + } else { + last->next = new; + } + last = new; + + last->flags |= LYS_YIN_ATTR; + LY_CHECK_GOTO(ret = lydict_insert(ctx->xmlctx->ctx, ctx->xmlctx->name, ctx->xmlctx->name_len, &last->stmt), cleanup); + last->kw = LY_STMT_NONE; + /* attributes with prefix are ignored */ + if (!ctx->xmlctx->prefix) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + + INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, last->arg); + LY_CHECK_ERR_GOTO(!last->arg, ret = LY_EMEM, cleanup); + } else { + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + } + + if ((ctx->xmlctx->status != LYXML_ELEM_CONTENT) || ctx->xmlctx->ws_only) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + while (ctx->xmlctx->status == LYXML_ELEMENT) { + /* parse subelements */ + ret = yin_parse_element_generic(ctx, (*element)->kw, &new); + LY_CHECK_GOTO(ret, cleanup); + if (!(*element)->child) { + /* save first */ + (*element)->child = new; + } else { + last->next = new; + } + last = new; + + assert(ctx->xmlctx->status == LYXML_ELEM_CLOSE); + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + } else { + /* save element content */ + if (ctx->xmlctx->value_len) { + INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, (*element)->arg); + LY_CHECK_ERR_GOTO(!(*element)->arg, ret = LY_EMEM, cleanup); + + /* store prefix data for the argument as well */ + LY_CHECK_GOTO(ret = ly_store_prefix_data(ctx->xmlctx->ctx, (*element)->arg, strlen((*element)->arg), LY_VALUE_XML, + &ctx->xmlctx->ns, &(*element)->format, &(*element)->prefix_data), cleanup); + } + + /* read closing tag */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Parse instance of extension. + * + * @param[in,out] ctx Yin parser context for logging and to store current state. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[in] parent_stmt_index In case of several @p parent_stmt, index of the relevant @p parent statement. + * @param[in,out] exts Extension instance to add to. + * @return LY_ERR values. + */ +LY_ERR +yin_parse_extension_instance(struct lysp_yin_ctx *ctx, const void *parent, enum ly_stmt parent_stmt, + LY_ARRAY_COUNT_TYPE parent_stmt_index, struct lysp_ext_instance **exts) +{ + struct lysp_ext_instance *e; + struct lysp_stmt *last_subelem = NULL, *new_subelem = NULL; + char *ext_name; + + assert(ctx->xmlctx->status == LYXML_ELEMENT); + assert(exts); + + LY_ARRAY_NEW_RET(ctx->xmlctx->ctx, *exts, e, LY_EMEM); + + if (!ctx->xmlctx->prefix_len) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%*.s\" without the mandatory prefix.", + (int)ctx->xmlctx->name_len, ctx->xmlctx->name); + return LY_EVALID; + } + + /* store prefixed name */ + if (asprintf(&ext_name, "%.*s:%.*s", (int)ctx->xmlctx->prefix_len, ctx->xmlctx->prefix, (int)ctx->xmlctx->name_len, + ctx->xmlctx->name) == -1) { + LOGMEM(ctx->xmlctx->ctx); + return LY_EMEM; + } + LY_CHECK_RET(lydict_insert_zc(ctx->xmlctx->ctx, ext_name, &e->name)); + + /* store prefix data for the name */ + LY_CHECK_RET(ly_store_prefix_data(ctx->xmlctx->ctx, e->name, strlen(e->name), LY_VALUE_XML, &ctx->xmlctx->ns, + &e->format, &e->prefix_data)); + + e->parent = (void *)parent; + e->parent_stmt = parent_stmt; + e->parent_stmt_index = parent_stmt_index; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + + /* store attributes as subelements */ + while (ctx->xmlctx->status == LYXML_ATTRIBUTE) { + if (!ctx->xmlctx->prefix) { + new_subelem = calloc(1, sizeof(*new_subelem)); + if (!e->child) { + e->child = new_subelem; + } else { + last_subelem->next = new_subelem; + } + last_subelem = new_subelem; + + last_subelem->flags |= LYS_YIN_ATTR; + LY_CHECK_RET(lydict_insert(ctx->xmlctx->ctx, ctx->xmlctx->name, ctx->xmlctx->name_len, &last_subelem->stmt)); + LY_CHECK_RET(!last_subelem->stmt, LY_EMEM); + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + + INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, + last_subelem->arg); + LY_CHECK_RET(!last_subelem->arg, LY_EMEM); + } else { + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + + /* parse subelements */ + assert(ctx->xmlctx->status == LYXML_ELEM_CONTENT); + if (ctx->xmlctx->ws_only) { + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + while (ctx->xmlctx->status == LYXML_ELEMENT) { + LY_CHECK_RET(yin_parse_element_generic(ctx, LY_STMT_EXTENSION_INSTANCE, &new_subelem)); + if (!e->child) { + e->child = new_subelem; + } else { + last_subelem->next = new_subelem; + } + last_subelem = new_subelem; + + assert(ctx->xmlctx->status == LYXML_ELEM_CLOSE); + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + } + } else if (ctx->xmlctx->value_len) { + /* invalid text content */ + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%s\" with unexpected text content \".*s\".", ext_name, + (int)ctx->xmlctx->value_len, ctx->xmlctx->value); + return LY_EVALID; + } + + e->parsed = NULL; + + return LY_SUCCESS; +} + +/** + * @brief Generic function for content parsing + * + * @param[in,out] ctx Yin parser context for logging and to store current state. + * @param[in] subelem_info array of valid subelement types and meta information + * @param[in] subelem_info_size Size of subelem_info array. + * @param[in] parent Current statement parent. + * @param[in] parent_stmt Type of @p parent statement. + * @param[out] text_content Where the text content of element should be stored if any. Text content is ignored if set to NULL. + * @param[in,out] exts Extension instance to add to. Can be set to null if element cannot have extension as subelements. + * @return LY_ERR values. + */ +LY_ERR +yin_parse_content(struct lysp_yin_ctx *ctx, struct yin_subelement *subelem_info, size_t subelem_info_size, + const void *parent, enum ly_stmt parent_stmt, const char **text_content, struct lysp_ext_instance **exts) +{ + LY_ERR ret = LY_SUCCESS; + enum LYXML_PARSER_STATUS next_status; + enum ly_stmt cur_stmt = LY_STMT_NONE, last_stmt = LY_STMT_NONE; + struct yin_subelement *subelem = NULL; + + assert(ctx->xmlctx->status == LYXML_ELEM_CONTENT); + + if (ctx->xmlctx->ws_only) { + /* check whether there are any children */ + LY_CHECK_GOTO(ret = lyxml_ctx_peek(ctx->xmlctx, &next_status), cleanup); + } else { + /* we want to parse the value */ + next_status = LYXML_ELEM_CLOSE; + } + + if (next_status == LYXML_ELEMENT) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + + /* current element has subelements as content */ + while (ctx->xmlctx->status == LYXML_ELEMENT) { + /* match keyword */ + last_stmt = cur_stmt; + cur_stmt = yin_match_keyword(ctx, ctx->xmlctx->name, ctx->xmlctx->name_len, ctx->xmlctx->prefix, + ctx->xmlctx->prefix_len, parent_stmt); + + /* check if this element can be child of current element */ + subelem = get_record(cur_stmt, subelem_info_size, subelem_info); + if (!subelem) { + if ((parent_stmt == LY_STMT_DEVIATE) && isdevsub(cur_stmt)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INDEV_YIN, lyplg_ext_stmt2str(cur_stmt)); + } else { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, ctx->xmlctx->name_len, + ctx->xmlctx->name, lyplg_ext_stmt2str(parent_stmt)); + } + ret = LY_EVALID; + goto cleanup; + } + + /* relative order is required only in module and submodule sub-elements */ + if ((parent_stmt == LY_STMT_MODULE) || (parent_stmt == LY_STMT_SUBMODULE)) { + ret = yin_check_relative_order(ctx, last_stmt, cur_stmt, parent_stmt); + LY_CHECK_GOTO(ret, cleanup); + } + + /* flag check */ + if ((subelem->flags & YIN_SUBELEM_UNIQUE) && (subelem->flags & YIN_SUBELEM_PARSED)) { + /* subelement uniquenes */ + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_SUBELEM_REDEF, lyplg_ext_stmt2str(cur_stmt), lyplg_ext_stmt2str(parent_stmt)); + return LY_EVALID; + } + if (subelem->flags & YIN_SUBELEM_FIRST) { + /* subelement is supposed to be defined as first subelement */ + ret = yin_check_subelem_first_constraint(ctx, subelem_info, subelem_info_size, parent_stmt, subelem); + LY_CHECK_GOTO(ret, cleanup); + } + if (subelem->flags & YIN_SUBELEM_VER2) { + /* subelement is supported only in version 1.1 or higher */ + if (PARSER_CUR_PMOD(ctx)->version < LYS_VERSION_1_1) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INSUBELEM2, lyplg_ext_stmt2str(cur_stmt), lyplg_ext_stmt2str(parent_stmt)); + ret = LY_EVALID; + goto cleanup; + } + } + /* note that element was parsed for easy uniqueness check in next iterations */ + subelem->flags |= YIN_SUBELEM_PARSED; + + switch (cur_stmt) { + /* call responsible function */ + case LY_STMT_EXTENSION_INSTANCE: + ret = yin_parse_extension_instance(ctx, parent, parent_stmt, + (subelem->dest) ? *((LY_ARRAY_COUNT_TYPE *)subelem->dest) : 0, exts); + break; + case LY_STMT_ACTION: + case LY_STMT_RPC: + ret = yin_parse_action(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + ret = yin_parse_any(ctx, cur_stmt, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_ARGUMENT: + ret = yin_parse_argument(ctx, parent, (struct yin_argument_meta *)subelem->dest, exts); + break; + case LY_STMT_AUGMENT: + ret = yin_parse_augment(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_BASE: + ret = yin_parse_base(ctx, parent_stmt, subelem->dest, exts); + break; + case LY_STMT_BELONGS_TO: + ret = yin_parse_belongs_to(ctx, (struct lysp_submodule *)subelem->dest, exts); + break; + case LY_STMT_BIT: + ret = yin_parse_bit(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_CASE: + ret = yin_parse_case(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_CHOICE: + ret = yin_parse_choice(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_CONFIG: + ret = yin_parse_config(ctx, (uint16_t *)subelem->dest, exts); + break; + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ORGANIZATION: + case LY_STMT_REFERENCE: + ret = yin_parse_meta(ctx, parent, cur_stmt, (const char **)subelem->dest, exts); + break; + case LY_STMT_CONTAINER: + ret = yin_parse_container(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_DEFAULT: + ret = yin_parse_qname(ctx, cur_stmt, subelem, exts); + break; + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_KEY: + ret = yin_parse_simple_elem(ctx, parent, cur_stmt, subelem, YIN_ARG_VALUE, Y_STR_ARG, exts); + break; + case LY_STMT_PRESENCE: + ret = yin_parse_simple_elem(ctx, *(const char **)subelem->dest, cur_stmt, subelem, YIN_ARG_VALUE, Y_STR_ARG, exts); + break; + case LY_STMT_DEVIATE: + ret = yin_parse_deviate(ctx, (struct lysp_deviate **)subelem->dest); + break; + case LY_STMT_DEVIATION: + ret = yin_parse_deviation(ctx, (struct lysp_deviation **)subelem->dest); + break; + case LY_STMT_ENUM: + ret = yin_parse_enum(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_ERROR_MESSAGE: + ret = yin_parse_err_msg(ctx, parent, (const char **)subelem->dest, exts); + break; + case LY_STMT_EXTENSION: + ret = yin_parse_extension(ctx, (struct lysp_ext **)subelem->dest); + break; + case LY_STMT_FEATURE: + ret = yin_parse_feature(ctx, (struct lysp_feature **)subelem->dest); + break; + case LY_STMT_FRACTION_DIGITS: + ret = yin_parse_fracdigits(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_GROUPING: + ret = yin_parse_grouping(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_IDENTITY: + ret = yin_parse_identity(ctx, (struct lysp_ident **)subelem->dest); + break; + case LY_STMT_UNIQUE: + case LY_STMT_IF_FEATURE: + ret = yin_parse_qname(ctx, cur_stmt, subelem, exts); + break; + case LY_STMT_UNITS: + ret = yin_parse_simple_elem(ctx, *(const char **)subelem->dest, cur_stmt, subelem, YIN_ARG_NAME, Y_STR_ARG, exts); + break; + case LY_STMT_IMPORT: + ret = yin_parse_import(ctx, (struct import_meta *)subelem->dest); + break; + case LY_STMT_INCLUDE: + if ((parent_stmt == LY_STMT_SUBMODULE) && (PARSER_CUR_PMOD(ctx)->version == LYS_VERSION_1_1)) { + LOGWRN(PARSER_CTX(ctx), "YANG version 1.1 expects all includes in main module, includes in " + "submodules (%s) are not necessary.", ((struct lysp_submodule *)PARSER_CUR_PMOD(ctx))->name); + } + ret = yin_parse_include(ctx, (struct include_meta *)subelem->dest); + break; + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + ret = yin_parse_inout(ctx, cur_stmt, (struct inout_meta *)subelem->dest); + break; + case LY_STMT_LEAF: + ret = yin_parse_leaf(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_LEAF_LIST: + ret = yin_parse_leaflist(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_LENGTH: + ret = yin_parse_length(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_LIST: + ret = yin_parse_list(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_MANDATORY: + ret = yin_parse_mandatory(ctx, (uint16_t *)subelem->dest, exts); + break; + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + ret = yin_parse_minmax(ctx, parent_stmt, cur_stmt, subelem->dest); + break; + case LY_STMT_MODIFIER: + ret = yin_parse_modifier(ctx, parent, (const char **)subelem->dest, exts); + break; + case LY_STMT_MUST: + ret = yin_parse_must(ctx, (struct lysp_restr **)subelem->dest); + break; + case LY_STMT_NAMESPACE: + ret = yin_parse_simple_elem(ctx, parent, cur_stmt, subelem, YIN_ARG_URI, Y_STR_ARG, exts); + break; + case LY_STMT_NOTIFICATION: + ret = yin_parse_notification(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_ORDERED_BY: + ret = yin_parse_orderedby(ctx, parent, (uint16_t *)subelem->dest, exts); + break; + case LY_STMT_PATH: + ret = yin_parse_path(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_PATTERN: + ret = yin_parse_pattern(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_VALUE: + case LY_STMT_POSITION: + ret = yin_parse_value_pos(ctx, cur_stmt, (struct lysp_type_enum *)subelem->dest); + break; + case LY_STMT_PREFIX: + ret = yin_parse_simple_elem(ctx, *(const char **)subelem->dest, cur_stmt, subelem, YIN_ARG_VALUE, + Y_IDENTIF_ARG, exts); + break; + case LY_STMT_RANGE: + ret = yin_parse_range(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_REFINE: + ret = yin_parse_refine(ctx, (struct lysp_refine **)subelem->dest); + break; + case LY_STMT_REQUIRE_INSTANCE: + ret = yin_pasrse_reqinstance(ctx, (struct lysp_type *)subelem->dest); + break; + case LY_STMT_REVISION: + ret = yin_parse_revision(ctx, (struct lysp_revision **)subelem->dest); + break; + case LY_STMT_REVISION_DATE: + ret = yin_parse_revision_date(ctx, (char *)subelem->dest, exts); + break; + case LY_STMT_STATUS: + ret = yin_parse_status(ctx, (uint16_t *)subelem->dest, exts); + break; + case LY_STMT_TYPE: + ret = yin_parse_type(ctx, parent_stmt, subelem); + break; + case LY_STMT_TYPEDEF: + ret = yin_parse_typedef(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_USES: + ret = yin_parse_uses(ctx, (struct tree_node_meta *)subelem->dest); + break; + case LY_STMT_WHEN: + ret = yin_parse_when(ctx, (struct lysp_when **)subelem->dest); + break; + case LY_STMT_YANG_VERSION: + ret = yin_parse_yangversion(ctx, parent, (uint8_t *)subelem->dest, exts); + break; + case LY_STMT_YIN_ELEMENT: + ret = yin_parse_yin_element(ctx, parent, (uint16_t *)subelem->dest, exts); + break; + case LY_STMT_ARG_TEXT: + case LY_STMT_ARG_VALUE: + /* TODO what to do with content/attributes? */ + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + while (ctx->xmlctx->status == LYXML_ATTRIBUTE) { + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + ret = yin_parse_content(ctx, NULL, 0, parent, cur_stmt, (const char **)subelem->dest, NULL); + break; + default: + LOGINT(ctx->xmlctx->ctx); + ret = LY_EINT; + } + LY_CHECK_GOTO(ret, cleanup); + subelem = NULL; + + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + } else { + LY_CHECK_RET(ret); + /* elements with text or none content */ + /* save text content, if text_content isn't set, it's just ignored */ + /* no resources are allocated in this branch, no need to use cleanup label */ + LY_CHECK_RET(yin_validate_value(ctx, Y_STR_ARG)); + if (text_content) { + INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, *text_content); + LY_CHECK_RET(!*text_content, LY_EMEM); + } + + LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); + } + + /* mandatory subelements are checked only after whole element was succesfully parsed */ + LY_CHECK_RET(yin_check_subelem_mandatory_constraint(ctx, subelem_info, subelem_info_size, parent_stmt)); + +cleanup: + return ret; +} + +/** + * @brief Parse module element. + * + * @param[in,out] ctx Yin parser context for logging and to store current state. + * @param[out] mod Parsed module structure. + * @return LY_ERR values. + */ +LY_ERR +yin_parse_mod(struct lysp_yin_ctx *ctx, struct lysp_module *mod) +{ + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + const struct lysp_submodule *dup; + size_t subelems_size; + + mod->is_submod = 0; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &mod->mod->name, Y_IDENTIF_ARG, LY_STMT_MODULE)); + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 28, NULL, &subelems, + LY_STMT_ANYDATA, &mod->data, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &mod->data, 0, + LY_STMT_AUGMENT, &mod->augments, 0, + LY_STMT_CHOICE, &mod->data, 0, + LY_STMT_CONTACT, &mod->mod->contact, YIN_SUBELEM_UNIQUE, + LY_STMT_CONTAINER, &mod->data, 0, + LY_STMT_DESCRIPTION, &mod->mod->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_DEVIATION, &mod->deviations, 0, + LY_STMT_EXTENSION, &mod->extensions, 0, + LY_STMT_FEATURE, &mod->features, 0, + LY_STMT_GROUPING, &mod->groupings, 0, + LY_STMT_IDENTITY, &mod->identities, 0, + LY_STMT_IMPORT, mod->mod->prefix, &mod->imports, 0, + LY_STMT_INCLUDE, mod->mod->name, &mod->includes, 0, + LY_STMT_LEAF, &mod->data, 0, + LY_STMT_LEAF_LIST, &mod->data, 0, + LY_STMT_LIST, &mod->data, 0, + LY_STMT_NAMESPACE, &mod->mod->ns, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE, + LY_STMT_NOTIFICATION, &mod->notifs, 0, + LY_STMT_ORGANIZATION, &mod->mod->org, YIN_SUBELEM_UNIQUE, + LY_STMT_PREFIX, &mod->mod->prefix, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE, + LY_STMT_REFERENCE, &mod->mod->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_REVISION, &mod->revs, 0, + LY_STMT_RPC, &mod->rpcs, 0, + LY_STMT_TYPEDEF, &mod->typedefs, 0, + LY_STMT_USES, &mod->data, 0, + LY_STMT_YANG_VERSION, &mod->version, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + + ret = yin_parse_content(ctx, subelems, subelems_size, mod, LY_STMT_MODULE, NULL, &mod->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, mod->exts)); + + /* submodules share the namespace with the module names, so there must not be + * a submodule of the same name in the context, no need for revision matching */ + dup = ly_ctx_get_submodule_latest(ctx->xmlctx->ctx, mod->mod->name); + if (dup) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_NAME2_COL, "module", "submodule", mod->mod->name); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse submodule element. + * + * @param[in,out] ctx Yin parser context for logging and to store current state. + * @param[in] mod_attrs Attributes of submodule element. + * @param[out] submod Parsed submodule structure. + * @return LY_ERR values. + */ +LY_ERR +yin_parse_submod(struct lysp_yin_ctx *ctx, struct lysp_submodule *submod) +{ + LY_ERR ret = LY_SUCCESS; + struct yin_subelement *subelems = NULL; + const struct lysp_submodule *dup; + size_t subelems_size; + + submod->is_submod = 1; + + LY_CHECK_RET(lyxml_ctx_next(ctx->xmlctx)); + LY_CHECK_RET(yin_parse_attribute(ctx, YIN_ARG_NAME, &submod->name, Y_IDENTIF_ARG, LY_STMT_SUBMODULE)); + LY_CHECK_RET(subelems_allocator(ctx, subelems_size = 27, NULL, &subelems, + LY_STMT_ANYDATA, &submod->data, YIN_SUBELEM_VER2, + LY_STMT_ANYXML, &submod->data, 0, + LY_STMT_AUGMENT, &submod->augments, 0, + LY_STMT_BELONGS_TO, submod, YIN_SUBELEM_MANDATORY | YIN_SUBELEM_UNIQUE, + LY_STMT_CHOICE, &submod->data, 0, + LY_STMT_CONTACT, &submod->contact, YIN_SUBELEM_UNIQUE, + LY_STMT_CONTAINER, &submod->data, 0, + LY_STMT_DESCRIPTION, &submod->dsc, YIN_SUBELEM_UNIQUE, + LY_STMT_DEVIATION, &submod->deviations, 0, + LY_STMT_EXTENSION, &submod->extensions, 0, + LY_STMT_FEATURE, &submod->features, 0, + LY_STMT_GROUPING, &submod->groupings, 0, + LY_STMT_IDENTITY, &submod->identities, 0, + LY_STMT_IMPORT, submod->prefix, &submod->imports, 0, + LY_STMT_INCLUDE, submod->name, &submod->includes, 0, + LY_STMT_LEAF, &submod->data, 0, + LY_STMT_LEAF_LIST, &submod->data, 0, + LY_STMT_LIST, &submod->data, 0, + LY_STMT_NOTIFICATION, &submod->notifs, 0, + LY_STMT_ORGANIZATION, &submod->org, YIN_SUBELEM_UNIQUE, + LY_STMT_REFERENCE, &submod->ref, YIN_SUBELEM_UNIQUE, + LY_STMT_REVISION, &submod->revs, 0, + LY_STMT_RPC, &submod->rpcs, 0, + LY_STMT_TYPEDEF, &submod->typedefs, 0, + LY_STMT_USES, &submod->data, 0, + LY_STMT_YANG_VERSION, &submod->version, YIN_SUBELEM_UNIQUE, + LY_STMT_EXTENSION_INSTANCE, NULL, 0)); + + ret = yin_parse_content(ctx, subelems, subelems_size, submod, LY_STMT_SUBMODULE, NULL, &submod->exts); + subelems_deallocator(subelems_size, subelems); + LY_CHECK_RET(ret); + + /* store extension instance array (no realloc anymore) to find the plugin records and finish parsing */ + LY_CHECK_RET(yin_unres_exts_add(ctx, submod->exts)); + + /* submodules share the namespace with the module names, so there must not be + * a submodule of the same name in the context, no need for revision matching */ + dup = ly_ctx_get_submodule_latest(ctx->xmlctx->ctx, submod->name); + if (dup && strcmp(dup->mod->name, submod->mod->name)) { + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_NAME_COL, "submodules", dup->name); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +LY_ERR +yin_parse_submodule(struct lysp_yin_ctx **yin_ctx, struct ly_ctx *ctx, struct lysp_ctx *main_ctx, + struct ly_in *in, struct lysp_submodule **submod) +{ + enum ly_stmt kw = LY_STMT_NONE; + LY_ERR ret = LY_SUCCESS; + struct lysp_submodule *mod_p = NULL; + struct lysf_ctx fctx = {.ctx = ctx}; + + assert(yin_ctx && ctx && main_ctx && in && submod); + + /* create context */ + *yin_ctx = calloc(1, sizeof **yin_ctx); + LY_CHECK_ERR_RET(!(*yin_ctx), LOGMEM(ctx), LY_EMEM); + (*yin_ctx)->format = LYS_IN_YIN; + (*yin_ctx)->main_ctx = main_ctx; + LY_CHECK_RET(lyxml_ctx_new(ctx, in, &(*yin_ctx)->xmlctx)); + + mod_p = calloc(1, sizeof *mod_p); + LY_CHECK_ERR_GOTO(!mod_p, LOGMEM(ctx); ret = LY_EMEM, cleanup); + mod_p->mod = PARSER_CUR_PMOD(main_ctx)->mod; + mod_p->parsing = 1; + + /* use main context parsed mods adding the current one */ + (*yin_ctx)->parsed_mods = main_ctx->parsed_mods; + ly_set_add((*yin_ctx)->parsed_mods, mod_p, 1, NULL); + + /* check submodule */ + kw = yin_match_keyword(*yin_ctx, (*yin_ctx)->xmlctx->name, (*yin_ctx)->xmlctx->name_len, (*yin_ctx)->xmlctx->prefix, + (*yin_ctx)->xmlctx->prefix_len, LY_STMT_NONE); + if (kw == LY_STMT_MODULE) { + LOGERR(ctx, LY_EDENIED, "Input data contains module when a submodule is expected."); + ret = LY_EINVAL; + goto cleanup; + } else if (kw != LY_STMT_SUBMODULE) { + LOGVAL_PARSER((struct lysp_ctx *)*yin_ctx, LY_VCODE_MOD_SUBOMD, lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + } + + ret = yin_parse_submod(*yin_ctx, mod_p); + LY_CHECK_GOTO(ret, cleanup); + + /* skip possible trailing whitespaces at end of the input */ + while (isspace(in->current[0])) { + if (in->current[0] == '\n') { + LY_IN_NEW_LINE(in); + } + ly_in_skip(in, 1); + } + if (in->current[0]) { + LOGVAL_PARSER((struct lysp_ctx *)*yin_ctx, LY_VCODE_TRAILING_SUBMOD, 15, in->current, + strlen(in->current) > 15 ? "..." : ""); + ret = LY_EVALID; + goto cleanup; + } + + mod_p->parsing = 0; + *submod = mod_p; + +cleanup: + LOG_LOCBACK(0, 0, 0, 1); + if (ret) { + lysp_module_free(&fctx, (struct lysp_module *)mod_p); + lysp_yin_ctx_free(*yin_ctx); + *yin_ctx = NULL; + } + return ret; +} + +LY_ERR +yin_parse_module(struct lysp_yin_ctx **yin_ctx, struct ly_in *in, struct lys_module *mod) +{ + LY_ERR ret = LY_SUCCESS; + enum ly_stmt kw = LY_STMT_NONE; + struct lysp_module *mod_p = NULL; + struct lysf_ctx fctx = {.ctx = mod->ctx}; + + /* create context */ + *yin_ctx = calloc(1, sizeof **yin_ctx); + LY_CHECK_ERR_RET(!(*yin_ctx), LOGMEM(mod->ctx), LY_EMEM); + (*yin_ctx)->format = LYS_IN_YIN; + LY_CHECK_ERR_RET(ly_set_new(&(*yin_ctx)->parsed_mods), free(*yin_ctx); LOGMEM(mod->ctx), LY_EMEM); + LY_CHECK_RET(lyxml_ctx_new(mod->ctx, in, &(*yin_ctx)->xmlctx)); + (*yin_ctx)->main_ctx = (struct lysp_ctx *)(*yin_ctx); + + mod_p = calloc(1, sizeof *mod_p); + LY_CHECK_ERR_GOTO(!mod_p, LOGMEM(mod->ctx), cleanup); + mod_p->mod = mod; + ly_set_add((*yin_ctx)->parsed_mods, mod_p, 1, NULL); + + /* check module */ + kw = yin_match_keyword(*yin_ctx, (*yin_ctx)->xmlctx->name, (*yin_ctx)->xmlctx->name_len, (*yin_ctx)->xmlctx->prefix, + (*yin_ctx)->xmlctx->prefix_len, LY_STMT_NONE); + if (kw == LY_STMT_SUBMODULE) { + LOGERR(mod->ctx, LY_EDENIED, "Input data contains submodule which cannot be parsed directly without its main module."); + ret = LY_EINVAL; + goto cleanup; + } else if (kw != LY_STMT_MODULE) { + LOGVAL_PARSER((struct lysp_ctx *)*yin_ctx, LY_VCODE_MOD_SUBOMD, lyplg_ext_stmt2str(kw)); + ret = LY_EVALID; + goto cleanup; + } + + /* parse module substatements */ + ret = yin_parse_mod(*yin_ctx, mod_p); + LY_CHECK_GOTO(ret, cleanup); + + /* skip possible trailing whitespaces at end of the input */ + while (isspace(in->current[0])) { + if (in->current[0] == '\n') { + LY_IN_NEW_LINE(in); + } + ly_in_skip(in, 1); + } + if (in->current[0]) { + LOGVAL_PARSER((struct lysp_ctx *)*yin_ctx, LY_VCODE_TRAILING_MOD, 15, in->current, + strlen(in->current) > 15 ? "..." : ""); + ret = LY_EVALID; + goto cleanup; + } + + mod->parsed = mod_p; + +cleanup: + LOG_LOCBACK(0, 0, 0, 1); + if (ret) { + lysp_module_free(&fctx, mod_p); + lysp_yin_ctx_free(*yin_ctx); + *yin_ctx = NULL; + } + return ret; +} diff --git a/src/path.c b/src/path.c new file mode 100644 index 0000000..72e5fb5 --- /dev/null +++ b/src/path.c @@ -0,0 +1,1193 @@ +/** + * @file path.c + * @author Michal Vasko + * @brief Path functions + * + * Copyright (c) 2020 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ +#include "path.h" + +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "log.h" +#include "plugins_types.h" +#include "schema_compile.h" +#include "set.h" +#include "tree.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +#define LOGVAL_P(CTX, CUR_NODE, CODE, ...) ly_vlog(CTX, (CUR_NODE) ? LY_VLOG_LYSC : LY_VLOG_NONE, CUR_NODE, CODE, ##__VA_ARGS__) + +/** + * @brief Check predicate syntax. + * + * @param[in] ctx libyang context. + * @param[in] cur_node Current (original context) node. + * @param[in] exp Parsed predicate. + * @param[in,out] tok_idx Index in @p exp, is adjusted. + * @param[in] prefix Prefix option. + * @param[in] pred Predicate option. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_check_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lyxp_expr *exp, + uint32_t *tok_idx, uint8_t prefix, uint8_t pred) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_set *set = NULL; + uint32_t i; + const char *name; + size_t name_len; + + LOG_LOCSET(cur_node, NULL, NULL, NULL); + + if (!lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_BRACK1)) { + /* '[' */ + + if (((pred == LY_PATH_PRED_SIMPLE) || (pred == LY_PATH_PRED_KEYS)) && + !lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_NAMETEST)) { + ret = ly_set_new(&set); + LY_CHECK_GOTO(ret, cleanup); + + do { + /* NameTest is always expected here */ + LY_CHECK_GOTO(lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NAMETEST), token_error); + + /* check prefix based on the options */ + name = strnstr(exp->expr + exp->tok_pos[*tok_idx], ":", exp->tok_len[*tok_idx]); + if ((prefix == LY_PATH_PREFIX_MANDATORY) && !name) { + LOGVAL(ctx, LYVE_XPATH, "Prefix missing for \"%.*s\" in path.", exp->tok_len[*tok_idx], + exp->expr + exp->tok_pos[*tok_idx]); + goto token_error; + } else if ((prefix == LY_PATH_PREFIX_STRICT_INHERIT) && name) { + LOGVAL(ctx, LYVE_XPATH, "Redundant prefix for \"%.*s\" in path.", exp->tok_len[*tok_idx], + exp->expr + exp->tok_pos[*tok_idx]); + goto token_error; + } + if (!name) { + name = exp->expr + exp->tok_pos[*tok_idx]; + name_len = exp->tok_len[*tok_idx]; + } else { + ++name; + name_len = exp->tok_len[*tok_idx] - (name - (exp->expr + exp->tok_pos[*tok_idx])); + } + + /* check whether it was not already specified */ + for (i = 0; i < set->count; ++i) { + /* all the keys must be from the same module so this comparison should be fine */ + if (!strncmp(set->objs[i], name, name_len) && + lysp_check_identifierchar(NULL, ((char *)set->objs[i])[name_len], 0, NULL)) { + LOGVAL(ctx, LYVE_XPATH, "Duplicate predicate key \"%.*s\" in path.", (int)name_len, name); + goto token_error; + } + } + + /* add it into the set */ + ret = ly_set_add(set, (void *)name, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* NameTest */ + ++(*tok_idx); + + /* '=' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_EQUAL), token_error); + + /* Literal or Number */ + LY_CHECK_GOTO(lyxp_next_token2(ctx, exp, tok_idx, LYXP_TOKEN_LITERAL, LYXP_TOKEN_NUMBER), token_error); + + /* ']' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_BRACK2), token_error); + + /* '[' */ + } while (!lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_BRACK1)); + + } else if ((pred == LY_PATH_PRED_SIMPLE) && !lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_DOT)) { + /* '.' */ + + /* '=' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_EQUAL), token_error); + + /* Literal or Number */ + LY_CHECK_GOTO(lyxp_next_token2(ctx, exp, tok_idx, LYXP_TOKEN_LITERAL, LYXP_TOKEN_NUMBER), token_error); + + /* ']' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_BRACK2), token_error); + + } else if ((pred == LY_PATH_PRED_SIMPLE) && !lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_NUMBER)) { + /* Number */ + + /* check for index 0 */ + if (!atoi(exp->expr + exp->tok_pos[*tok_idx - 1])) { + LOGVAL(ctx, LYVE_XPATH, "Invalid positional predicate \"%.*s\".", (int)exp->tok_len[*tok_idx - 1], + exp->expr + exp->tok_pos[*tok_idx - 1]); + goto token_error; + } + + /* ']' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_BRACK2), token_error); + + } else if ((pred == LY_PATH_PRED_LEAFREF) && !lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_NAMETEST)) { + assert(prefix == LY_PATH_PREFIX_OPTIONAL); + ret = ly_set_new(&set); + LY_CHECK_GOTO(ret, cleanup); + + do { + /* NameTest is always expected here */ + LY_CHECK_GOTO(lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NAMETEST), token_error); + + name = strnstr(exp->expr + exp->tok_pos[*tok_idx], ":", exp->tok_len[*tok_idx]); + if (!name) { + name = exp->expr + exp->tok_pos[*tok_idx]; + name_len = exp->tok_len[*tok_idx]; + } else { + ++name; + name_len = exp->tok_len[*tok_idx] - (name - (exp->expr + exp->tok_pos[*tok_idx])); + } + + /* check whether it was not already specified */ + for (i = 0; i < set->count; ++i) { + /* all the keys must be from the same module so this comparison should be fine */ + if (!strncmp(set->objs[i], name, name_len) && + lysp_check_identifierchar(NULL, ((char *)set->objs[i])[name_len], 0, NULL)) { + LOGVAL(ctx, LYVE_XPATH, "Duplicate predicate key \"%.*s\" in path.", (int)name_len, name); + goto token_error; + } + } + + /* add it into the set */ + ret = ly_set_add(set, (void *)name, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* NameTest */ + ++(*tok_idx); + + /* '=' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_EQUAL), token_error); + + /* FuncName */ + LY_CHECK_GOTO(lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_FUNCNAME), token_error); + if ((exp->tok_len[*tok_idx] != ly_strlen_const("current")) || + strncmp(exp->expr + exp->tok_pos[*tok_idx], "current", ly_strlen_const("current"))) { + LOGVAL(ctx, LYVE_XPATH, "Invalid function \"%.*s\" invocation in path.", + exp->tok_len[*tok_idx], exp->expr + exp->tok_pos[*tok_idx]); + goto token_error; + } + ++(*tok_idx); + + /* '(' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR1), token_error); + + /* ')' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR2), token_error); + + /* '/' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_PATH), token_error); + + /* '..' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_DDOT), token_error); + do { + /* '/' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_PATH), token_error); + } while (!lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_DDOT)); + + /* NameTest */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_NAMETEST), token_error); + + /* '/' */ + while (!lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_OPER_PATH)) { + /* NameTest */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_NAMETEST), token_error); + } + + /* ']' */ + LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_BRACK2), token_error); + + /* '[' */ + } while (!lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_BRACK1)); + + } else if (lyxp_check_token(ctx, exp, *tok_idx, 0)) { + /* unexpected EOF */ + goto token_error; + } else { + /* invalid token */ + LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), exp->expr + exp->tok_pos[*tok_idx]); + goto token_error; + } + } + +cleanup: + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + ly_set_free(set, NULL); + return ret; + +token_error: + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + ly_set_free(set, NULL); + return LY_EVALID; +} + +LY_ERR +ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len, + ly_bool lref, uint8_t begin, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + uint32_t tok_idx, cur_len; + const char *cur_node, *prev_prefix = NULL, *ptr; + + assert((begin == LY_PATH_BEGIN_ABSOLUTE) || (begin == LY_PATH_BEGIN_EITHER)); + assert((prefix == LY_PATH_PREFIX_OPTIONAL) || (prefix == LY_PATH_PREFIX_MANDATORY) || + (prefix == LY_PATH_PREFIX_STRICT_INHERIT)); + assert((pred == LY_PATH_PRED_KEYS) || (pred == LY_PATH_PRED_SIMPLE) || (pred == LY_PATH_PRED_LEAFREF)); + + LOG_LOCSET(ctx_node, NULL, NULL, NULL); + + /* parse as a generic XPath expression */ + LY_CHECK_GOTO(ret = lyxp_expr_parse(ctx, str_path, path_len, 1, &exp), error); + tok_idx = 0; + + if (begin == LY_PATH_BEGIN_EITHER) { + /* is the path relative? */ + if (lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_OPER_PATH)) { + /* relative path check specific to leafref */ + if (lref) { + /* mandatory '..' */ + LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_DDOT), ret = LY_EVALID, error); + + do { + /* '/' */ + LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error); + + /* optional '..' */ + } while (!lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_DDOT)); + } + } + } else { + /* '/' */ + LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error); + } + + do { + /* NameTest */ + LY_CHECK_ERR_GOTO(lyxp_check_token(ctx, exp, tok_idx, LYXP_TOKEN_NAMETEST), ret = LY_EVALID, error); + + /* check prefix based on the options */ + cur_node = exp->expr + exp->tok_pos[tok_idx]; + cur_len = exp->tok_len[tok_idx]; + if (prefix == LY_PATH_PREFIX_MANDATORY) { + if (!strnstr(cur_node, ":", cur_len)) { + LOGVAL(ctx, LYVE_XPATH, "Prefix missing for \"%.*s\" in path.", cur_len, cur_node); + ret = LY_EVALID; + goto error; + } + } else if (prefix == LY_PATH_PREFIX_STRICT_INHERIT) { + if (!prev_prefix) { + /* the first node must have a prefix */ + if (!strnstr(cur_node, ":", cur_len)) { + LOGVAL(ctx, LYVE_XPATH, "Prefix missing for \"%.*s\" in path.", cur_len, cur_node); + ret = LY_EVALID; + goto error; + } + + /* remember the first prefix */ + prev_prefix = cur_node; + } else { + /* the prefix must be different, if any */ + ptr = strnstr(cur_node, ":", cur_len); + if (ptr) { + if (!strncmp(prev_prefix, cur_node, ptr - cur_node) && (prev_prefix[ptr - cur_node] == ':')) { + LOGVAL(ctx, LYVE_XPATH, "Duplicate prefix for \"%.*s\" in path.", cur_len, cur_node); + ret = LY_EVALID; + goto error; + } + + /* remember this next prefix */ + prev_prefix = cur_node; + } + } + } + + ++tok_idx; + + /* Predicate* */ + LY_CHECK_GOTO(ret = ly_path_check_predicate(ctx, ctx_node, exp, &tok_idx, prefix, pred), error); + + /* '/' */ + } while (!lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_OPER_PATH)); + + /* trailing token check */ + if (exp->used > tok_idx) { + LOGVAL(ctx, LYVE_XPATH, "Unparsed characters \"%s\" left at the end of path.", exp->expr + exp->tok_pos[tok_idx]); + ret = LY_EVALID; + goto error; + } + + *expr = exp; + + LOG_LOCBACK(ctx_node ? 1 : 0, 0, 0, 0); + return LY_SUCCESS; + +error: + lyxp_expr_free(ctx, exp); + LOG_LOCBACK(ctx_node ? 1 : 0, 0, 0, 0); + return ret; +} + +LY_ERR +ly_path_parse_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const char *str_path, + size_t path_len, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + uint32_t tok_idx; + + assert((prefix == LY_PATH_PREFIX_OPTIONAL) || (prefix == LY_PATH_PREFIX_MANDATORY)); + assert((pred == LY_PATH_PRED_KEYS) || (pred == LY_PATH_PRED_SIMPLE) || (pred == LY_PATH_PRED_LEAFREF)); + + LOG_LOCSET(cur_node, NULL, NULL, NULL); + + /* parse as a generic XPath expression */ + LY_CHECK_GOTO(ret = lyxp_expr_parse(ctx, str_path, path_len, 0, &exp), error); + tok_idx = 0; + + LY_CHECK_GOTO(ret = ly_path_check_predicate(ctx, cur_node, exp, &tok_idx, prefix, pred), error); + + /* trailing token check */ + if (exp->used > tok_idx) { + LOGVAL(ctx, LYVE_XPATH, "Unparsed characters \"%s\" left at the end of predicate.", + exp->expr + exp->tok_pos[tok_idx]); + ret = LY_EVALID; + goto error; + } + + *expr = exp; + + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + return LY_SUCCESS; + +error: + lyxp_expr_free(ctx, exp); + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + return ret; +} + +/** + * @brief Parse NameTest and get the corresponding schema node. + * + * @param[in] ctx libyang context. + * @param[in] cur_node Optional current (original context) node. + * @param[in] cur_mod Current module of the path (where the path is "instantiated"). Needed for ::LY_VALUE_SCHEMA + * and ::LY_VALUE_SCHEMA_RESOLVED. + * @param[in] prev_ctx_node Previous context node. + * @param[in] expr Parsed path. + * @param[in] tok_idx Index in @p expr. + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] top_ext Optional top-level extension to use for searching the schema node. + * @param[in] getnext_opts Options to be used for ::lys_getnext() calls. + * @param[out] snode Resolved schema node. + * @param[out] ext Optional extension instance of @p snode, if any. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_compile_snode(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lys_module *cur_mod, + const struct lysc_node *prev_ctx_node, const struct lyxp_expr *expr, uint32_t tok_idx, LY_VALUE_FORMAT format, + void *prefix_data, const struct lysc_ext_instance *top_ext, uint32_t getnext_opts, const struct lysc_node **snode, + struct lysc_ext_instance **ext) +{ + LY_ERR ret; + const struct lys_module *mod = NULL; + struct lysc_ext_instance *e = NULL; + const char *pref, *name; + size_t len, name_len; + + assert(expr->tokens[tok_idx] == LYXP_TOKEN_NAMETEST); + + *snode = NULL; + if (ext) { + *ext = NULL; + } + + /* get prefix */ + if ((pref = strnstr(expr->expr + expr->tok_pos[tok_idx], ":", expr->tok_len[tok_idx]))) { + len = pref - (expr->expr + expr->tok_pos[tok_idx]); + pref = expr->expr + expr->tok_pos[tok_idx]; + } else { + len = 0; + } + + /* set name */ + if (pref) { + name = pref + len + 1; + name_len = expr->tok_len[tok_idx] - len - 1; + } else { + name = expr->expr + expr->tok_pos[tok_idx]; + name_len = expr->tok_len[tok_idx]; + } + + /* find node module */ + if (pref) { + LOG_LOCSET(cur_node, NULL, NULL, NULL); + + mod = ly_resolve_prefix(prev_ctx_node ? prev_ctx_node->module->ctx : ctx, pref, len, format, prefix_data); + if ((!mod || !mod->implemented) && prev_ctx_node) { + /* check for nested ext data */ + ret = ly_nested_ext_schema(NULL, prev_ctx_node, pref, len, format, prefix_data, name, name_len, snode, &e); + if (!ret) { + goto success; + } else if (ret != LY_ENOT) { + goto error; + } + } + + if (!mod) { + LOGVAL(ctx, LYVE_XPATH, "No module connected with the prefix \"%.*s\" found (prefix format %s).", + (int)len, pref, ly_format2str(format)); + ret = LY_EVALID; + goto error; + } else if (!mod->implemented) { + LOGVAL(ctx, LYVE_XPATH, "Not implemented module \"%s\" in path.", mod->name); + ret = LY_EVALID; + goto error; + } + + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + } else { + switch (format) { + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + if (!cur_mod) { + LOGINT_RET(ctx); + } + /* use current module */ + mod = cur_mod; + break; + case LY_VALUE_JSON: + case LY_VALUE_LYB: + if (!prev_ctx_node) { + LOGINT_RET(ctx); + } + /* inherit module of the previous node */ + mod = prev_ctx_node->module; + break; + case LY_VALUE_CANON: + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + /* not really defined or accepted */ + LOGINT_RET(ctx); + } + } + + /* find schema node */ + if (!prev_ctx_node && top_ext) { + *snode = lysc_ext_find_node(top_ext, mod, name, name_len, 0, getnext_opts); + } else { + *snode = lys_find_child(prev_ctx_node, mod, name, name_len, 0, getnext_opts); + if (!(*snode) && prev_ctx_node) { + ret = ly_nested_ext_schema(NULL, prev_ctx_node, pref, len, format, prefix_data, name, name_len, snode, &e); + LY_CHECK_RET(ret && (ret != LY_ENOT), ret); + } + } + if (!(*snode)) { + LOGVAL(ctx, LYVE_XPATH, "Not found node \"%.*s\" in path.", (int)name_len, name); + return LY_ENOTFOUND; + } + +success: + if (ext) { + *ext = e; + } + return LY_SUCCESS; + +error: + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + return ret; +} + +LY_ERR +ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lys_module *cur_mod, + const struct lysc_node *ctx_node, const struct lyxp_expr *expr, uint32_t *tok_idx, LY_VALUE_FORMAT format, + void *prefix_data, struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_path_predicate *p; + const struct lysc_node *key; + const char *val; + size_t val_len, key_count; + + assert(ctx && ctx_node); + + LOG_LOCSET(cur_node, NULL, NULL, NULL); + + *pred_type = 0; + + if (lyxp_next_token(NULL, expr, tok_idx, LYXP_TOKEN_BRACK1)) { + /* '[', no predicate */ + goto cleanup; /* LY_SUCCESS */ + } + + if (expr->tokens[*tok_idx] == LYXP_TOKEN_NAMETEST) { + if (ctx_node->nodetype != LYS_LIST) { + LOGVAL(ctx, LYVE_XPATH, "List predicate defined for %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + ret = LY_EVALID; + goto cleanup; + } else if (ctx_node->flags & LYS_KEYLESS) { + LOGVAL(ctx, LYVE_XPATH, "List predicate defined for keyless %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + ret = LY_EVALID; + goto cleanup; + } + + do { + /* NameTest, find the key */ + LY_CHECK_RET(ly_path_compile_snode(ctx, cur_node, cur_mod, ctx_node, expr, *tok_idx, format, prefix_data, + NULL, 0, &key, NULL)); + if ((key->nodetype != LYS_LEAF) || !(key->flags & LYS_KEY)) { + LOGVAL(ctx, LYVE_XPATH, "Key expected instead of %s \"%s\" in path.", lys_nodetype2str(key->nodetype), + key->name); + ret = LY_EVALID; + goto cleanup; + } + ++(*tok_idx); + + if (!*pred_type) { + /* new predicate */ + *pred_type = LY_PATH_PREDTYPE_LIST; + } + assert(*pred_type == LY_PATH_PREDTYPE_LIST); + LY_ARRAY_NEW_GOTO(ctx, *predicates, p, ret, cleanup); + p->key = key; + + /* '=' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL); + ++(*tok_idx); + + /* Literal or Number */ + assert((expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) || (expr->tokens[*tok_idx] == LYXP_TOKEN_NUMBER)); + if (expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) { + /* skip quotes */ + val = expr->expr + expr->tok_pos[*tok_idx] + 1; + val_len = expr->tok_len[*tok_idx] - 2; + } else { + val = expr->expr + expr->tok_pos[*tok_idx]; + val_len = expr->tok_len[*tok_idx]; + } + + /* store the value */ + LOG_LOCSET(key, NULL, NULL, NULL); + ret = lyd_value_store(ctx, &p->value, ((struct lysc_node_leaf *)key)->type, val, val_len, NULL, format, + prefix_data, LYD_HINT_DATA, key, NULL); + LOG_LOCBACK(key ? 1 : 0, 0, 0, 0); + LY_CHECK_ERR_GOTO(ret, p->value.realtype = NULL, cleanup); + ++(*tok_idx); + + /* "allocate" the type to avoid problems when freeing the value after the type was freed */ + LY_ATOMIC_INC_BARRIER(((struct lysc_type *)p->value.realtype)->refcount); + + /* ']' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_BRACK2); + ++(*tok_idx); + + /* another predicate follows? */ + } while (!lyxp_next_token(NULL, expr, tok_idx, LYXP_TOKEN_BRACK1)); + + /* check that all keys were set */ + key_count = 0; + for (key = lysc_node_child(ctx_node); key && (key->flags & LYS_KEY); key = key->next) { + ++key_count; + } + if (LY_ARRAY_COUNT(*predicates) != key_count) { + /* names (keys) are unique - it was checked when parsing */ + LOGVAL(ctx, LYVE_XPATH, "Predicate missing for a key of %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + ly_path_predicates_free(ctx, LY_PATH_PREDTYPE_LIST, *predicates); + *predicates = NULL; + ret = LY_EVALID; + goto cleanup; + } + + } else if (expr->tokens[*tok_idx] == LYXP_TOKEN_DOT) { + if (ctx_node->nodetype != LYS_LEAFLIST) { + LOGVAL(ctx, LYVE_XPATH, "Leaf-list predicate defined for %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + ret = LY_EVALID; + goto cleanup; + } + ++(*tok_idx); + + /* new predicate */ + *pred_type = LY_PATH_PREDTYPE_LEAFLIST; + LY_ARRAY_NEW_GOTO(ctx, *predicates, p, ret, cleanup); + + /* '=' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL); + ++(*tok_idx); + + /* Literal or Number */ + assert((expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) || (expr->tokens[*tok_idx] == LYXP_TOKEN_NUMBER)); + if (expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) { + /* skip quotes */ + val = expr->expr + expr->tok_pos[*tok_idx] + 1; + val_len = expr->tok_len[*tok_idx] - 2; + } else { + val = expr->expr + expr->tok_pos[*tok_idx]; + val_len = expr->tok_len[*tok_idx]; + } + + /* store the value */ + LOG_LOCSET(ctx_node, NULL, NULL, NULL); + ret = lyd_value_store(ctx, &p->value, ((struct lysc_node_leaflist *)ctx_node)->type, val, val_len, NULL, format, + prefix_data, LYD_HINT_DATA, ctx_node, NULL); + LOG_LOCBACK(ctx_node ? 1 : 0, 0, 0, 0); + LY_CHECK_ERR_GOTO(ret, p->value.realtype = NULL, cleanup); + ++(*tok_idx); + + /* "allocate" the type to avoid problems when freeing the value after the type was freed */ + LY_ATOMIC_INC_BARRIER(((struct lysc_type *)p->value.realtype)->refcount); + + /* ']' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_BRACK2); + ++(*tok_idx); + } else { + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_NUMBER); + if (!(ctx_node->nodetype & (LYS_LEAFLIST | LYS_LIST))) { + ret = LY_EVALID; + LOGVAL(ctx, LYVE_XPATH, "Positional predicate defined for %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + goto cleanup; + } else if (ctx_node->flags & LYS_CONFIG_W) { + ret = LY_EVALID; + LOGVAL(ctx, LYVE_XPATH, "Positional predicate defined for configuration %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + goto cleanup; + } + + /* new predicate */ + *pred_type = LY_PATH_PREDTYPE_POSITION; + LY_ARRAY_NEW_GOTO(ctx, *predicates, p, ret, cleanup); + + /* syntax was already checked */ + p->position = strtoull(expr->expr + expr->tok_pos[*tok_idx], (char **)&val, LY_BASE_DEC); + ++(*tok_idx); + + /* ']' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_BRACK2); + ++(*tok_idx); + } + +cleanup: + LOG_LOCBACK(cur_node ? 1 : 0, 0, 0, 0); + return ret; +} + +/** + * @brief Compile leafref predicate. Actually, it is only checked. + * + * @param[in] ctx_node Context node, node for which the predicate is defined. + * @param[in] cur_node Current (original context) node. + * @param[in] expr Parsed path. + * @param[in,out] tok_idx Index in @p expr, is adjusted for parsed tokens. + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @return LY_ERR value. + */ +static LY_ERR +ly_path_compile_predicate_leafref(const struct lysc_node *ctx_node, const struct lysc_node *cur_node, + const struct lyxp_expr *expr, uint32_t *tok_idx, LY_VALUE_FORMAT format, void *prefix_data) +{ + LY_ERR ret = LY_SUCCESS; + const struct lysc_node *key, *node, *node2; + struct ly_ctx *ctx = cur_node->module->ctx; + + if (lyxp_next_token(NULL, expr, tok_idx, LYXP_TOKEN_BRACK1)) { + /* '[', no predicate */ + goto cleanup; /* LY_SUCCESS */ + } + + if (ctx_node->nodetype != LYS_LIST) { + LOGVAL(ctx, LYVE_XPATH, "List predicate defined for %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + ret = LY_EVALID; + goto cleanup; + } else if (ctx_node->flags & LYS_KEYLESS) { + LOGVAL(ctx, LYVE_XPATH, "List predicate defined for keyless %s \"%s\" in path.", + lys_nodetype2str(ctx_node->nodetype), ctx_node->name); + ret = LY_EVALID; + goto cleanup; + } + + do { + /* NameTest, find the key */ + ret = ly_path_compile_snode(ctx, cur_node, cur_node->module, ctx_node, expr, *tok_idx, format, prefix_data, + NULL, 0, &key, NULL); + LY_CHECK_GOTO(ret, cleanup); + if ((key->nodetype != LYS_LEAF) || !(key->flags & LYS_KEY)) { + LOGVAL(ctx, LYVE_XPATH, "Key expected instead of %s \"%s\" in path.", + lys_nodetype2str(key->nodetype), key->name); + ret = LY_EVALID; + goto cleanup; + } + ++(*tok_idx); + + /* we are not actually compiling, throw the key away */ + (void)key; + + /* '=' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL); + ++(*tok_idx); + + /* FuncName */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_FUNCNAME); + ++(*tok_idx); + + /* evaluating from the "current()" node */ + node = cur_node; + + /* '(' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_PAR1); + ++(*tok_idx); + + /* ')' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_PAR2); + ++(*tok_idx); + + do { + /* '/' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_PATH); + ++(*tok_idx); + + /* go to parent */ + if (!node) { + LOGVAL(ctx, LYVE_XPATH, "Too many parent references in path."); + ret = LY_EVALID; + goto cleanup; + } + node = lysc_data_parent(node); + + /* '..' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_DDOT); + ++(*tok_idx); + } while (expr->tokens[*tok_idx + 1] == LYXP_TOKEN_DDOT); + + do { + /* '/' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_PATH); + ++(*tok_idx); + + /* NameTest */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_NAMETEST); + LY_CHECK_RET(ly_path_compile_snode(ctx, cur_node, cur_node->module, node, expr, *tok_idx, format, + prefix_data, NULL, 0, &node2, NULL)); + node = node2; + ++(*tok_idx); + } while ((*tok_idx + 1 < expr->used) && (expr->tokens[*tok_idx + 1] == LYXP_TOKEN_NAMETEST)); + + /* check the last target node */ + if (node->nodetype != LYS_LEAF) { + LOGVAL(ctx, LYVE_XPATH, "Leaf expected instead of %s \"%s\" in leafref predicate in path.", + lys_nodetype2str(node->nodetype), node->name); + ret = LY_EVALID; + goto cleanup; + } + + /* we are not actually compiling, throw the rightside node away */ + (void)node; + + /* ']' */ + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_BRACK2); + ++(*tok_idx); + + /* another predicate follows? */ + } while (!lyxp_next_token(NULL, expr, tok_idx, LYXP_TOKEN_BRACK1)); + +cleanup: + return (ret == LY_ENOTFOUND) ? LY_EVALID : ret; +} + +/** + * @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled. + * + * @param[in] ctx libyang context. + * @param[in] cur_mod Current module of the path (where it was "instantiated"), ignored of @p lref. Used for nodes + * without a prefix for ::LY_VALUE_SCHEMA and ::LY_VALUE_SCHEMA_RESOLVED format. + * @param[in] ctx_node Optional context node, mandatory of @p lref. + * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find the top-level + * node inside the extension instance instead of a module. Note that this is the case not only if the @p ctx_node is NULL, + * but also if the relative path starting in @p ctx_node reaches the document root via double dots. + * @param[in] expr Parsed path. + * @param[in] lref Whether leafref is being compiled or not. + * @param[in] oper Oper option (@ref path_oper_options). + * @param[in] target Target option (@ref path_target_options). + * @param[in] limit_access_tree Whether to limit accessible tree as described in + * [XPath context](https://datatracker.ietf.org/doc/html/rfc7950#section-6.4.1). + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[out] path Compiled path. + * @return LY_ERECOMPILE, only if @p lref. + * @return LY_ERR value. + */ +static LY_ERR +_ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, ly_bool lref, uint8_t oper, uint8_t target, + ly_bool limit_access_tree, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t tok_idx = 0, getnext_opts; + const struct lysc_node *node2, *cur_node, *op; + struct ly_path *p = NULL; + struct lysc_ext_instance *ext = NULL; + + assert(ctx); + assert(!lref || ctx_node); + assert((oper == LY_PATH_OPER_INPUT) || (oper == LY_PATH_OPER_OUTPUT)); + assert((target == LY_PATH_TARGET_SINGLE) || (target == LY_PATH_TARGET_MANY)); + + if (!limit_access_tree) { + op = NULL; + } else { + /* find operation, if we are in any */ + for (op = ctx_node; op && !(op->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); op = op->parent) {} + } + + *path = NULL; + + /* remember original context node */ + cur_node = ctx_node; + LOG_LOCSET(cur_node, NULL, NULL, NULL); + + if (oper == LY_PATH_OPER_OUTPUT) { + getnext_opts = LYS_GETNEXT_OUTPUT; + } else { + getnext_opts = 0; + } + + if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) { + /* absolute path */ + ctx_node = NULL; + + ++tok_idx; + } else { + /* relative path */ + if (!ctx_node) { + LOGVAL(ctx, LYVE_XPATH, "No initial schema parent for a relative path."); + ret = LY_EVALID; + goto cleanup; + } + + /* go up the parents for leafref */ + while (lref && (expr->tokens[tok_idx] == LYXP_TOKEN_DDOT)) { + if (!ctx_node) { + LOGVAL(ctx, LYVE_XPATH, "Too many parent references in path."); + ret = LY_EVALID; + goto cleanup; + } + + /* get parent */ + ctx_node = lysc_data_parent(ctx_node); + + ++tok_idx; + + assert(expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH); + ++tok_idx; + } + } + + do { + /* check last compiled inner node, whether it is uniquely identified (even key-less list) */ + if (p && !lref && (target == LY_PATH_TARGET_SINGLE) && (p->node->nodetype == LYS_LIST) && !p->predicates) { + LOGVAL(ctx, LYVE_XPATH, "Predicate missing for %s \"%s\" in path.", + lys_nodetype2str(p->node->nodetype), p->node->name); + ret = LY_EVALID; + goto cleanup; + } + + /* NameTest */ + LY_CHECK_ERR_GOTO(lyxp_check_token(ctx, expr, tok_idx, LYXP_TOKEN_NAMETEST), ret = LY_EVALID, cleanup); + + /* get schema node */ + LY_CHECK_GOTO(ret = ly_path_compile_snode(ctx, cur_node, cur_mod, ctx_node, expr, tok_idx, format, prefix_data, + top_ext, getnext_opts, &node2, &ext), cleanup); + ++tok_idx; + if ((op && (node2->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && (node2 != op))) { + LOGVAL(ctx, LYVE_XPATH, "Not found node \"%s\" in path.", node2->name); + ret = LY_EVALID; + goto cleanup; + } + ctx_node = node2; + + /* new path segment */ + LY_ARRAY_NEW_GOTO(ctx, *path, p, ret, cleanup); + p->node = ctx_node; + p->ext = ext; + + /* compile any predicates */ + if (lref) { + ret = ly_path_compile_predicate_leafref(ctx_node, cur_node, expr, &tok_idx, format, prefix_data); + } else { + ret = ly_path_compile_predicate(ctx, cur_node, cur_mod, ctx_node, expr, &tok_idx, format, prefix_data, + &p->predicates, &p->pred_type); + } + LY_CHECK_GOTO(ret, cleanup); + } while (!lyxp_next_token(NULL, expr, &tok_idx, LYXP_TOKEN_OPER_PATH)); + + /* check leftover tokens */ + if (tok_idx < expr->used) { + LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(expr->tokens[tok_idx]), &expr->expr[expr->tok_pos[tok_idx]]); + ret = LY_EVALID; + goto cleanup; + } + + /* check last compiled node */ + if (!lref && (target == LY_PATH_TARGET_SINGLE) && (p->node->nodetype & (LYS_LIST | LYS_LEAFLIST)) && !p->predicates) { + LOGVAL(ctx, LYVE_XPATH, "Predicate missing for %s \"%s\" in path.", + lys_nodetype2str(p->node->nodetype), p->node->name); + ret = LY_EVALID; + goto cleanup; + } + +cleanup: + if (ret) { + ly_path_free(ctx, *path); + *path = NULL; + } + LOG_LOCBACK(1, 0, 0, 0); + return (ret == LY_ENOTFOUND) ? LY_EVALID : ret; +} + +LY_ERR +ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint8_t oper, uint8_t target, + ly_bool limit_access_tree, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path) +{ + return _ly_path_compile(ctx, cur_mod, ctx_node, top_ext, expr, 0, oper, target, limit_access_tree, format, + prefix_data, path); +} + +LY_ERR +ly_path_compile_leafref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const struct lysc_ext_instance *top_ext, + const struct lyxp_expr *expr, uint8_t oper, uint8_t target, LY_VALUE_FORMAT format, void *prefix_data, + struct ly_path **path) +{ + return _ly_path_compile(ctx, ctx_node->module, ctx_node, top_ext, expr, 1, oper, target, 1, format, prefix_data, path); +} + +LY_ERR +ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, LY_ARRAY_COUNT_TYPE *path_idx, + struct lyd_node **match) +{ + LY_ARRAY_COUNT_TYPE u; + struct lyd_node *prev_node = NULL, *elem, *node = NULL, *target; + uint64_t pos; + + assert(path && start); + + if (lysc_data_parent(path[0].node)) { + /* relative path, start from the parent children */ + start = lyd_child(start); + } else { + /* absolute path, start from the first top-level sibling */ + while (start->parent) { + start = lyd_parent(start); + } + while (start->prev->next) { + start = start->prev; + } + } + + LY_ARRAY_FOR(path, u) { + switch (path[u].pred_type) { + case LY_PATH_PREDTYPE_POSITION: + /* we cannot use hashes and want an instance on a specific position */ + pos = 1; + node = NULL; + LYD_LIST_FOR_INST(start, path[u].node, elem) { + if (pos == path[u].predicates[0].position) { + node = elem; + break; + } + ++pos; + } + break; + case LY_PATH_PREDTYPE_LEAFLIST: + /* we will use hashes to find one leaf-list instance */ + LY_CHECK_RET(lyd_create_term2(path[u].node, &path[u].predicates[0].value, &target)); + lyd_find_sibling_first(start, target, &node); + lyd_free_tree(target); + break; + case LY_PATH_PREDTYPE_LIST: + /* we will use hashes to find one list instance */ + LY_CHECK_RET(lyd_create_list(path[u].node, path[u].predicates, &target)); + lyd_find_sibling_first(start, target, &node); + lyd_free_tree(target); + break; + case LY_PATH_PREDTYPE_NONE: + /* we will use hashes to find one any/container/leaf instance */ + lyd_find_sibling_val(start, path[u].node, NULL, 0, &node); + break; + } + + if (!node) { + /* no matching nodes */ + break; + } + + /* rememeber previous node */ + prev_node = node; + + /* next path segment, if any */ + start = lyd_child(node); + } + + if (node) { + /* we have found the full path */ + if (path_idx) { + *path_idx = u; + } + if (match) { + *match = node; + } + return LY_SUCCESS; + + } else if (prev_node) { + /* we have found only some partial match */ + if (path_idx) { + *path_idx = u - 1; + } + if (match) { + *match = prev_node; + } + return LY_EINCOMPLETE; + } + + /* we have not found any nodes */ + if (path_idx) { + *path_idx = 0; + } + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; +} + +LY_ERR +ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct lyd_node **match) +{ + LY_ERR ret; + struct lyd_node *m; + + ret = ly_path_eval_partial(path, start, NULL, &m); + + if (ret == LY_SUCCESS) { + /* last node was found */ + if (match) { + *match = m; + } + return LY_SUCCESS; + } + + /* not a full match */ + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; +} + +LY_ERR +ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path **dup) +{ + LY_ARRAY_COUNT_TYPE u, v; + + if (!path) { + return LY_SUCCESS; + } + + LY_ARRAY_CREATE_RET(ctx, *dup, LY_ARRAY_COUNT(path), LY_EMEM); + LY_ARRAY_FOR(path, u) { + LY_ARRAY_INCREMENT(*dup); + (*dup)[u].node = path[u].node; + if (path[u].predicates) { + LY_ARRAY_CREATE_RET(ctx, (*dup)[u].predicates, LY_ARRAY_COUNT(path[u].predicates), LY_EMEM); + (*dup)[u].pred_type = path[u].pred_type; + LY_ARRAY_FOR(path[u].predicates, v) { + struct ly_path_predicate *pred = &path[u].predicates[v]; + + LY_ARRAY_INCREMENT((*dup)[u].predicates); + switch (path[u].pred_type) { + case LY_PATH_PREDTYPE_POSITION: + /* position-predicate */ + (*dup)[u].predicates[v].position = pred->position; + break; + case LY_PATH_PREDTYPE_LIST: + case LY_PATH_PREDTYPE_LEAFLIST: + /* key-predicate or leaf-list-predicate */ + (*dup)[u].predicates[v].key = pred->key; + pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].predicates[v].value); + LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount); + break; + case LY_PATH_PREDTYPE_NONE: + break; + } + } + } + } + + return LY_SUCCESS; +} + +void +ly_path_predicates_free(const struct ly_ctx *ctx, enum ly_path_pred_type pred_type, struct ly_path_predicate *predicates) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysf_ctx fctx = {.ctx = (struct ly_ctx *)ctx}; + + if (!predicates) { + return; + } + + LY_ARRAY_FOR(predicates, u) { + switch (pred_type) { + case LY_PATH_PREDTYPE_POSITION: + case LY_PATH_PREDTYPE_NONE: + /* nothing to free */ + break; + case LY_PATH_PREDTYPE_LIST: + case LY_PATH_PREDTYPE_LEAFLIST: + if (predicates[u].value.realtype) { + predicates[u].value.realtype->plugin->free(ctx, &predicates[u].value); + lysc_type_free(&fctx, (struct lysc_type *)predicates[u].value.realtype); + } + break; + } + } + LY_ARRAY_FREE(predicates); +} + +void +ly_path_free(const struct ly_ctx *ctx, struct ly_path *path) +{ + LY_ARRAY_COUNT_TYPE u; + + if (!path) { + return; + } + + LY_ARRAY_FOR(path, u) { + ly_path_predicates_free(ctx, path[u].pred_type, path[u].predicates); + } + LY_ARRAY_FREE(path); +} diff --git a/src/path.h b/src/path.h new file mode 100644 index 0000000..ca1c84a --- /dev/null +++ b/src/path.h @@ -0,0 +1,263 @@ +/** + * @file path.h + * @author Michal Vasko + * @brief Path structure and manipulation routines. + * + * Copyright (c) 2020 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_PATH_H_ +#define LY_PATH_H_ + +#include +#include + +#include "log.h" +#include "tree.h" +#include "tree_data.h" + +struct ly_ctx; +struct lys_module; +struct lysc_ext_instance; +struct lysc_node; +struct lyxp_expr; + +enum ly_path_pred_type { + LY_PATH_PREDTYPE_NONE = 0, /**< no predicate */ + LY_PATH_PREDTYPE_POSITION, /**< position predicate - [2] */ + LY_PATH_PREDTYPE_LIST, /**< keys predicate - [key1='val1'][key2='val2']... */ + LY_PATH_PREDTYPE_LEAFLIST /**< leaflist value predicate - [.='value'] */ +}; + +/** + * @brief Structure for simple path predicate. + */ +struct ly_path_predicate { + union { + uint64_t position; /**< position value for the position-predicate */ + + struct { + const struct lysc_node *key; /**< key node of the predicate, NULL in + case of a leaf-list predicate */ + struct lyd_value value; /**< value representation according to the + key's type, its realtype is allocated */ + }; + }; +}; + +/** + * @brief Structure for holding one segment of resolved path on schema including + * simple predicates. Is used as a [sized array](@ref sizedarrays). + */ +struct ly_path { + const struct lysc_node *node; /**< Schema node representing the path segment, first node has special meaning: + - is a top-level node - path is absolute, + - is inner node - path is relative */ + const struct lysc_ext_instance *ext; /**< Extension instance of @p node, if any */ + struct ly_path_predicate *predicates; /**< [Sized array](@ref sizedarrays) of the path segment's predicates */ + enum ly_path_pred_type pred_type; /**< Predicate type (see YANG ABNF) */ +}; + +/** + * @defgroup path_begin_options Path begin options. + * @{ + */ +#define LY_PATH_BEGIN_ABSOLUTE 0x01 /**< path must be absolute */ +#define LY_PATH_BEGIN_EITHER 0x02 /**< path be be either absolute or relative */ +/** @} */ + +/** + * @defgroup path_prefix_options Path prefix options. + * @{ + */ +#define LY_PATH_PREFIX_OPTIONAL 0x10 /**< prefixes in the path are optional */ +#define LY_PATH_PREFIX_MANDATORY 0x20 /**< prefixes in the path are mandatory (XML instance-identifier) */ +#define LY_PATH_PREFIX_STRICT_INHERIT 0x30 /**< prefixes in the path are mandatory in case they differ from the + previous prefixes, otherwise they are prohibited (JSON instance-identifier) */ +/** @} */ + +/** + * @defgroup path_pred_options Path predicate options. + * @{ + */ +#define LY_PATH_PRED_KEYS 0x40 /* expected predicate only - [node='value']* */ +#define LY_PATH_PRED_SIMPLE 0x80 /* expected predicates - [node='value']*; [.='value']; [1] */ +#define LY_PATH_PRED_LEAFREF 0xC0 /* expected predicates only leafref - [node=current()/../../../node/node]; + at least 1 ".." and 1 "node" after */ +/** @} */ + +/** + * @brief Parse path into XPath token structure and perform all additional checks. + * + * @param[in] ctx libyang context. + * @param[in] ctx_node Optional context node, used for logging. + * @param[in] str_path Path to parse. + * @param[in] path_len Length of @p str_path. + * @param[in] lref Whether leafref is being parsed or not. + * @param[in] begin Begin option (@ref path_begin_options). + * @param[in] prefix Prefix option (@ref path_prefix_options). + * @param[in] pred Predicate option (@ref path_pred_options). + * @param[out] expr Parsed path. + * @return LY_ERR value. + */ +LY_ERR ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len, + ly_bool lref, uint8_t begin, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr); + +/** + * @brief Parse predicate into XPath token structure and perform all additional checks. + * + * @param[in] ctx libyang context. + * @param[in] cur_node Optional current (original context) node, used for logging. + * @param[in] str_path Path to parse. + * @param[in] path_len Length of @p str_path. + * @param[in] prefix Prefix option (@ref path_prefix_options). + * @param[in] pred Predicate option (@ref path_pred_options). + * @param[out] expr Parsed path. + * @return LY_ERR value. + */ +LY_ERR ly_path_parse_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const char *str_path, + size_t path_len, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr); + +/** + * @defgroup path_oper_options Path operation options. + * @{ + */ +#define LY_PATH_OPER_INPUT 0x01 /**< if any RPC/action is traversed, its input nodes are used */ +#define LY_PATH_OPER_OUTPUT 0x02 /**< if any RPC/action is traversed, its output nodes are used */ +/** @} */ + +/* lref */ + +/** + * @defgroup path_target_options Path target options. + * @{ + */ +#define LY_PATH_TARGET_SINGLE 0x10 /**< last (target) node must identify an exact instance */ +#define LY_PATH_TARGET_MANY 0x20 /**< last (target) node may identify all instances (of leaf-list/list) */ +/** @} */ + +/** + * @brief Compile path into ly_path structure. + * + * @param[in] ctx libyang context. + * @param[in] cur_mod Current module of the path (where it was "instantiated"). Used for nodes in schema-nodeid + * without a prefix for ::LY_VALUE_SCHEMA and ::LY_VALUE_SCHEMA_RESOLVED format. + * @param[in] ctx_node Optional context node. + * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find the top-level + * node inside the extension instance instead of a module. Note that this is the case not only if the @p ctx_node is NULL, + * but also if the relative path starting in @p ctx_node reaches the document root via double dots. + * @param[in] expr Parsed path. + * @param[in] oper Oper option (@ref path_oper_options). + * @param[in] target Target option (@ref path_target_options). + * @param[in] limit_access_tree Whether to limit accessible tree. + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[out] path Compiled path. + * @return LY_ERR value. + */ +LY_ERR ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint8_t oper, uint8_t target, + ly_bool limit_access_tree, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path); + +/** + * @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled. + * + * @param[in] ctx libyang context. + * @param[in] ctx_node Context node. + * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find the top-level + * node inside the extension instance instead of a module. Note that this is the case not only if the @p ctx_node is NULL, + * but also if the relative path starting in @p ctx_node reaches the document root via double dots. + * @param[in] expr Parsed path. + * @param[in] oper Oper option (@ref path_oper_options). + * @param[in] target Target option (@ref path_target_options). + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[out] path Compiled path. + * @return LY_ERR value. + */ +LY_ERR ly_path_compile_leafref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint8_t oper, uint8_t target, + LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path); + +/** + * @brief Compile predicate into ly_path_predicate structure. Only simple predicates (not leafref) are supported. + * + * @param[in] ctx libyang context. + * @param[in] cur_node Optional current (original context) node. + * @param[in] cur_mod Current module of the path (where it was "instantiated"). Used for nodes without a prefix + * for ::LY_VALUE_SCHEMA and ::LY_VALUE_SCHEMA_RESOLVED format. + * @param[in] ctx_node Context node, node for which the predicate is defined. + * @param[in] expr Parsed path. + * @param[in,out] tok_idx Index in @p expr, is adjusted for parsed tokens. + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[out] predicates Compiled predicates. + * @param[out] pred_type Type of the compiled predicate(s). + * @return LY_ERR value. + */ +LY_ERR ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lys_module *cur_mod, + const struct lysc_node *ctx_node, const struct lyxp_expr *expr, uint32_t *tok_idx, LY_VALUE_FORMAT format, + void *prefix_data, struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type); + +/** + * @brief Resolve at least partially the target defined by ly_path structure. Not supported for leafref! + * + * @param[in] path Path structure specifying the target. + * @param[in] start Starting node for relative paths, can be any for absolute paths. + * @param[out] path_idx Last found path segment index, can be NULL, set to 0 if not found. + * @param[out] match Last found matching node, can be NULL, set to NULL if not found. + * @return LY_ENOTFOUND if no nodes were found, + * @return LY_EINCOMPLETE if some node was found but not the last one, + * @return LY_SUCCESS when the last node in the path was found, + * @return LY_ERR on another error. + */ +LY_ERR ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, LY_ARRAY_COUNT_TYPE *path_idx, + struct lyd_node **match); + +/** + * @brief Resolve the target defined by ly_path structure. Not supported for leafref! + * + * @param[in] path Path structure specifying the target. + * @param[in] start Starting node for relative paths, can be any for absolute paths. + * @param[out] match Found matching node, can be NULL, set to NULL if not found. + * @return LY_ENOTFOUND if no nodes were found, + * @return LY_SUCCESS when the last node in the path was found, + * @return LY_ERR on another error. + */ +LY_ERR ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct lyd_node **match); + +/** + * @brief Duplicate ly_path structure. + * + * @param[in] ctx libyang context. + * @param[in] path Path to duplicate. + * @param[out] dup Duplicated path. + * @return LY_ERR value. + */ +LY_ERR ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path **dup); + +/** + * @brief Free ly_path_predicate structure. + * + * @param[in] ctx libyang context. + * @param[in] pred_type Predicate type. + * @param[in] predicates Predicates ([sized array](@ref sizedarrays)) to free. + */ +void ly_path_predicates_free(const struct ly_ctx *ctx, enum ly_path_pred_type pred_type, + struct ly_path_predicate *predicates); + +/** + * @brief Free ly_path structure. + * + * @param[in] ctx libyang context. + * @param[in] path The structure ([sized array](@ref sizedarrays)) to free. + */ +void ly_path_free(const struct ly_ctx *ctx, struct ly_path *path); + +#endif /* LY_PATH_H_ */ diff --git a/src/plugins.c b/src/plugins.c new file mode 100644 index 0000000..d62da1c --- /dev/null +++ b/src/plugins.c @@ -0,0 +1,550 @@ +/** + * @file plugins.c + * @author Radek Krejci + * @brief Manipulate with the type and extension plugins. + * + * Copyright (c) 2021 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 + +#include "plugins.h" +#include "plugins_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "config.h" +#include "plugins_exts.h" +#include "plugins_types.h" +#include "set.h" + +/* + * internal type plugins records + */ +extern const struct lyplg_type_record plugins_binary[]; +extern const struct lyplg_type_record plugins_bits[]; +extern const struct lyplg_type_record plugins_boolean[]; +extern const struct lyplg_type_record plugins_decimal64[]; +extern const struct lyplg_type_record plugins_empty[]; +extern const struct lyplg_type_record plugins_enumeration[]; +extern const struct lyplg_type_record plugins_identityref[]; +extern const struct lyplg_type_record plugins_instanceid[]; +extern const struct lyplg_type_record plugins_integer[]; +extern const struct lyplg_type_record plugins_leafref[]; +extern const struct lyplg_type_record plugins_string[]; +extern const struct lyplg_type_record plugins_union[]; + +/* + * yang + */ +extern const struct lyplg_type_record plugins_instanceid_keys[]; + +/* + * ietf-inet-types + */ +extern const struct lyplg_type_record plugins_ipv4_address[]; +extern const struct lyplg_type_record plugins_ipv4_address_no_zone[]; +extern const struct lyplg_type_record plugins_ipv6_address[]; +extern const struct lyplg_type_record plugins_ipv6_address_no_zone[]; +extern const struct lyplg_type_record plugins_ipv4_prefix[]; +extern const struct lyplg_type_record plugins_ipv6_prefix[]; + +/* + * ietf-yang-types + */ +extern const struct lyplg_type_record plugins_date_and_time[]; +extern const struct lyplg_type_record plugins_xpath10[]; + +/* + * ietf-netconf-acm + */ +extern const struct lyplg_type_record plugins_node_instanceid[]; + +/* + * internal extension plugins records + */ +extern struct lyplg_ext_record plugins_metadata[]; +extern struct lyplg_ext_record plugins_nacm[]; +extern struct lyplg_ext_record plugins_yangdata[]; +extern struct lyplg_ext_record plugins_schema_mount[]; +extern struct lyplg_ext_record plugins_structure[]; + +static pthread_mutex_t plugins_guard = PTHREAD_MUTEX_INITIALIZER; + +/** + * @brief Counter for currently present contexts able to refer to the loaded plugins. + * + * Plugins are shared among all the created contexts. They are loaded with the creation of the very first context and + * unloaded with the destroy of the last context. Therefore, to reload the list of plugins, all the contexts must be + * destroyed and with the creation of a first new context after that, the plugins will be reloaded. + */ +static uint32_t context_refcount = 0; + +/** + * @brief Record describing an implemented extension. + * + * Matches ::lyplg_ext_record and ::lyplg_type_record + */ +struct lyplg_record { + const char *module; /**< name of the module where the extension/type is defined */ + const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision, + which is not an optimal approach due to a possible future revisions of the module. + Instead, there should be defined multiple items in the plugins list, each with the + different revision, but all with the same pointer to the plugin functions. The + only valid use case for the NULL revision is the case the module has no revision. */ + const char *name; /**< name of the extension/typedef */ + int8_t plugin[]; /**< specific plugin type's data - ::lyplg_ext or ::lyplg_type */ +}; + +#ifndef STATIC +static struct ly_set plugins_handlers = {0}; +#endif +static struct ly_set plugins_types = {0}; +static struct ly_set plugins_extensions = {0}; + +/** + * @brief Iterate over list of loaded plugins of the given @p type. + * + * @param[in] type Type of the plugins to iterate. + * @param[in,out] index The iterator - set to 0 for the first call. + * @return The plugin records, NULL if no more record is available. + */ +static struct lyplg_record * +plugins_iter(enum LYPLG type, uint32_t *index) +{ + struct ly_set *plugins; + + assert(index); + + if (type == LYPLG_EXTENSION) { + plugins = &plugins_extensions; + } else { + plugins = &plugins_types; + } + + if (*index == plugins->count) { + return NULL; + } + + *index += 1; + return plugins->objs[*index - 1]; +} + +static void * +lyplg_record_find(enum LYPLG type, const char *module, const char *revision, const char *name) +{ + uint32_t i = 0; + struct lyplg_record *item; + + assert(module); + assert(name); + + while ((item = plugins_iter(type, &i)) != NULL) { + if (!strcmp(item->module, module) && !strcmp(item->name, name)) { + if (item->revision && revision && strcmp(item->revision, revision)) { + continue; + } else if (!revision && item->revision) { + continue; + } + + return item; + } + } + + return NULL; +} + +struct lyplg_type * +lyplg_type_plugin_find(const char *module, const char *revision, const char *name) +{ + struct lyplg_record *record; + + record = lyplg_record_find(LYPLG_TYPE, module, revision, name); + return record ? &((struct lyplg_type_record *)record)->plugin : NULL; +} + +struct lyplg_ext_record * +lyplg_ext_record_find(const char *module, const char *revision, const char *name) +{ + return lyplg_record_find(LYPLG_EXTENSION, module, revision, name); +} + +/** + * @brief Insert the provided extension plugin records into the internal set of extension plugins for use by libyang. + * + * @param[in] recs An array of plugin records provided by the plugin implementation. The array must be terminated by a zeroed + * record. + * @return LY_SUCCESS in case of success + * @return LY_EINVAL for invalid information in @p recs. + * @return LY_EMEM in case of memory allocation failure. + */ +static LY_ERR +plugins_insert(enum LYPLG type, const void *recs) +{ + if (!recs) { + return LY_SUCCESS; + } + + if (type == LYPLG_EXTENSION) { + const struct lyplg_ext_record *rec = (const struct lyplg_ext_record *)recs; + + for (uint32_t i = 0; rec[i].name; i++) { + LY_CHECK_RET(ly_set_add(&plugins_extensions, (void *)&rec[i], 0, NULL)); + } + } else { /* LYPLG_TYPE */ + const struct lyplg_type_record *rec = (const struct lyplg_type_record *)recs; + + for (uint32_t i = 0; rec[i].name; i++) { + LY_CHECK_RET(ly_set_add(&plugins_types, (void *)&rec[i], 0, NULL)); + } + } + + return LY_SUCCESS; +} + +#ifndef STATIC + +static void +lyplg_close_cb(void *handle) +{ + dlclose(handle); +} + +static void +lyplg_clean_(void) +{ + if (--context_refcount) { + /* there is still some other context, do not remove the plugins */ + return; + } + + ly_set_erase(&plugins_types, NULL); + ly_set_erase(&plugins_extensions, NULL); + ly_set_erase(&plugins_handlers, lyplg_close_cb); +} + +#endif + +void +lyplg_clean(void) +{ +#ifndef STATIC + pthread_mutex_lock(&plugins_guard); + lyplg_clean_(); + pthread_mutex_unlock(&plugins_guard); +#endif +} + +#ifndef STATIC + +/** + * @brief Just a variadic data to cover extension and type plugins by a single ::plugins_load() function. + * + * The values are taken from ::LY_PLUGINS_EXTENSIONS and ::LYPLG_TYPES macros. + */ +static const struct { + const char *id; /**< string identifier: type/extension */ + const char *apiver_var; /**< expected variable name holding API version value */ + const char *plugins_var; /**< expected variable name holding plugin records */ + const char *envdir; /**< environment variable containing directory with the plugins */ + const char *dir; /**< default directory with the plugins (has less priority than envdir) */ + uint32_t apiver; /**< expected API version */ +} plugins_load_info[] = { + { /* LYPLG_TYPE */ + .id = "type", + .apiver_var = "plugins_types_apiver__", + .plugins_var = "plugins_types__", + .envdir = "LIBYANG_TYPES_PLUGINS_DIR", + .dir = LYPLG_TYPE_DIR, + .apiver = LYPLG_TYPE_API_VERSION + }, {/* LYPLG_EXTENSION */ + .id = "extension", + .apiver_var = "plugins_extensions_apiver__", + .plugins_var = "plugins_extensions__", + .envdir = "LIBYANG_EXTENSIONS_PLUGINS_DIR", + .dir = LYPLG_EXT_DIR, + .apiver = LYPLG_EXT_API_VERSION + } +}; + +/** + * @brief Get the expected plugin objects from the loaded dynamic object and add the defined plugins into the lists of + * available extensions/types plugins. + * + * @param[in] dlhandler Loaded dynamic library handler. + * @param[in] pathname Path of the loaded library for logging. + * @param[in] type Type of the plugins to get from the dynamic library. Note that a single library can hold both types + * and extensions plugins implementations, so this function should be called twice (once for each plugin type) with + * different @p type values + * @return LY_ERR values. + */ +static LY_ERR +plugins_load(void *dlhandler, const char *pathname, enum LYPLG type) +{ + const void *plugins; + uint32_t *version; + + /* type plugin */ + version = dlsym(dlhandler, plugins_load_info[type].apiver_var); + if (version) { + /* check version ... */ + if (*version != plugins_load_info[type].apiver) { + LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, wrong API version - %d expected, %d found.", + plugins_load_info[type].id, pathname, plugins_load_info[type].apiver, *version); + return LY_EINVAL; + } + + /* ... get types plugins information ... */ + if (!(plugins = dlsym(dlhandler, plugins_load_info[type].plugins_var))) { + char *errstr = dlerror(); + + LOGERR(NULL, LY_EINVAL, "Processing user %s plugin \"%s\" failed, missing %s plugins information (%s).", + plugins_load_info[type].id, pathname, plugins_load_info[type].id, errstr); + return LY_EINVAL; + } + + /* ... and load all the types plugins */ + LY_CHECK_RET(plugins_insert(type, plugins)); + } + + return LY_SUCCESS; +} + +static LY_ERR +plugins_load_module(const char *pathname) +{ + LY_ERR ret = LY_SUCCESS; + void *dlhandler; + uint32_t types_count = 0, extensions_count = 0; + + dlerror(); /* Clear any existing error */ + + dlhandler = dlopen(pathname, RTLD_NOW); + if (!dlhandler) { + LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", pathname, dlerror()); + return LY_ESYS; + } + + if (ly_set_contains(&plugins_handlers, dlhandler, NULL)) { + /* the plugin is already loaded */ + LOGVRB("Plugin \"%s\" already loaded.", pathname); + + /* keep the correct refcount */ + dlclose(dlhandler); + return LY_SUCCESS; + } + + /* remember the current plugins lists for recovery */ + types_count = plugins_types.count; + extensions_count = plugins_extensions.count; + + /* type plugin */ + ret = plugins_load(dlhandler, pathname, LYPLG_TYPE); + LY_CHECK_GOTO(ret, error); + + /* extension plugin */ + ret = plugins_load(dlhandler, pathname, LYPLG_EXTENSION); + LY_CHECK_GOTO(ret, error); + + /* remember the dynamic plugin */ + ret = ly_set_add(&plugins_handlers, dlhandler, 1, NULL); + LY_CHECK_GOTO(ret, error); + + return LY_SUCCESS; + +error: + dlclose(dlhandler); + + /* revert changes in the lists */ + while (plugins_types.count > types_count) { + ly_set_rm_index(&plugins_types, plugins_types.count - 1, NULL); + } + while (plugins_extensions.count > extensions_count) { + ly_set_rm_index(&plugins_extensions, plugins_extensions.count - 1, NULL); + } + + return ret; +} + +static LY_ERR +plugins_insert_dir(enum LYPLG type) +{ + LY_ERR ret = LY_SUCCESS; + const char *pluginsdir; + DIR *dir; + ly_bool default_dir = 0; + + /* try to get the plugins directory from environment variable */ + pluginsdir = getenv(plugins_load_info[type].envdir); + if (!pluginsdir) { + /* remember that we are going to a default dir and do not print warning if the directory doesn't exist */ + default_dir = 1; + pluginsdir = plugins_load_info[type].dir; + } + + dir = opendir(pluginsdir); + if (!dir) { + /* no directory (or no access to it), no extension plugins */ + if (!default_dir || (errno != ENOENT)) { + LOGWRN(NULL, "Failed to open libyang %s plugins directory \"%s\" (%s).", plugins_load_info[type].id, + pluginsdir, strerror(errno)); + } + } else { + struct dirent *file; + + while ((file = readdir(dir))) { + size_t len; + char pathname[PATH_MAX]; + + /* required format of the filename is *LYPLG_SUFFIX */ + len = strlen(file->d_name); + if ((len < LYPLG_SUFFIX_LEN + 1) || strcmp(&file->d_name[len - LYPLG_SUFFIX_LEN], LYPLG_SUFFIX)) { + continue; + } + + /* and construct the filepath */ + snprintf(pathname, PATH_MAX, "%s/%s", pluginsdir, file->d_name); + + ret = plugins_load_module(pathname); + if (ret) { + break; + } + } + closedir(dir); + } + + return ret; +} + +#endif + +LY_ERR +lyplg_init(void) +{ + LY_ERR ret; + + pthread_mutex_lock(&plugins_guard); + /* let only the first context to initiate plugins, but let others wait for finishing the initiation */ + if (context_refcount++) { + /* already initiated */ + pthread_mutex_unlock(&plugins_guard); + return LY_SUCCESS; + } + + /* internal types */ + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_binary), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_bits), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_boolean), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_decimal64), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_empty), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_enumeration), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_identityref), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_integer), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_leafref), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_string), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_union), error); + + /* yang */ + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_instanceid_keys), error); + + /* ietf-inet-types */ + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_address_no_zone), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_address_no_zone), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv4_prefix), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_ipv6_prefix), error); + + /* ietf-yang-types */ + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error); + + /* ietf-netconf-acm */ + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_node_instanceid), error); + + /* internal extensions */ + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_metadata), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_nacm), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_yangdata), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_schema_mount), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_EXTENSION, plugins_structure), error); + +#ifndef STATIC + /* external types */ + LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_TYPE), error); + + /* external extensions */ + LY_CHECK_GOTO(ret = plugins_insert_dir(LYPLG_EXTENSION), error); +#endif + + /* initiation done, wake-up possibly waiting threads creating another contexts */ + pthread_mutex_unlock(&plugins_guard); + + return LY_SUCCESS; + +error: + /* initiation was not successful - cleanup (and let others to try) */ +#ifndef STATIC + lyplg_clean_(); +#endif + pthread_mutex_unlock(&plugins_guard); + + if (ret == LY_EINVAL) { + /* all the plugins here are internal, invalid record actually means an internal libyang error */ + ret = LY_EINT; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_add(const char *pathname) +{ +#ifdef STATIC + (void)pathname; + + LOGERR(NULL, LY_EINVAL, "Plugins are not supported in statically built library."); + return LY_EINVAL; +#elif defined (_WIN32) + (void)pathname; + + LOGERR(NULL, LY_EINVAL, "Plugins are not (yet) supported on Windows."); + return LY_EINVAL; +#else + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(NULL, pathname, LY_EINVAL); + + /* works only in case a context exists */ + pthread_mutex_lock(&plugins_guard); + if (!context_refcount) { + /* no context */ + pthread_mutex_unlock(&plugins_guard); + LOGERR(NULL, LY_EDENIED, "To add a plugin, at least one context must exists."); + return LY_EDENIED; + } + + ret = plugins_load_module(pathname); + + pthread_mutex_unlock(&plugins_guard); + + return ret; +#endif +} diff --git a/src/plugins.h b/src/plugins.h new file mode 100644 index 0000000..f868879 --- /dev/null +++ b/src/plugins.h @@ -0,0 +1,94 @@ +/** + * @file plugins.h + * @author Radek Krejci + * @brief Plugins manipulation. + * + * Copyright (c) 2021 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_PLUGINS_H_ +#define LY_PLUGINS_H_ + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page howtoPlugins Plugins + * + * libyang supports two types of plugins to better support generic features of YANG that need some specific code for + * their specific instances in YANG modules. This is the case of YANG types, which are derived from YANG built-in types. + * The description of a derived type can specify some additional requirements or restriction that cannot be implemented + * generically and some special code is needed. The second case for libyang plugins are YANG extensions. For YANG extensions, + * most of the specification is hidden in their description (e.g. allowed substatements or place of the extension + * instantiation) and libyang is not able to process such a text in a generic way. + * + * In both cases, libyang provides API to get functionality implementing the specifics of each type or extension. + * Furthermore, there are several internal plugins, implementing built-in data types and selected derived types and YANG + * extensions. These internal plugins uses the same API and can be taken as examples for implementing user plugins. Internal + * plugins are always loaded with the first created [context](@ref howtoContext) and unloaded with destroying the last one. + * The external plugins are in the same phase loaded from the default directories specified at compile time via cmake + * variables `PLUGINS_DIR` (where the `extensions` and `types` subdirectories are added for each plugin type) or separately + * via `PLUGINS_DIR_EXTENSIONS` and `PLUGINS_DIR_TYPES` for each plugin type. The default directories can be replaced runtime + * using environment variables `LIBYANG_TYPES_PLUGINS_DIR` and `LIBYANG_EXTENSIONS_PLUGINS_DIR`. + * + * Order of the plugins determines their priority. libyang searches for the first match with the extension and type, so the + * firstly loaded plugin for the specific item is used. Since the internal plugins are loaded always before the external + * plugins, the internal plugins cannot be replaced. + * + * There is also a separate function ::lyplg_add() to add a plugin anytime later. Note, that such a plugin is being used + * after it is added with the lowest priority among other already loaded plugins. Also note that since all the plugins are + * unloaded with the destruction of the last context, creating a new context after that starts the standard plugins + * initiation and the manually added plugins are not loaded automatically. + * + * The following pages contain description of the API for creating user plugins. + * + * - @subpage howtoPluginsTypes + * - @subpage howtoPluginsExtensions + */ + +/** + * @defgroup plugins Plugins + * @{ + * + */ + +/** + * @brief Identifiers of the plugin type. + */ +enum LYPLG { + LYPLG_TYPE, /**< Specific type (typedef) */ + LYPLG_EXTENSION /**< YANG extension */ +}; + +/** + * @brief Manually load a plugin file. + * + * Note, that a plugin can be loaded only if there is at least one context. The loaded plugins are connected with the + * existence of a context. When all the contexts are destroyed, all the plugins are unloaded. + * + * @param[in] pathname Path to the plugin file. It can contain types or extensions plugins, both are accepted and correctly + * loaded. + * + * @return LY_SUCCESS if the file contains valid plugin compatible with the library version. + * @return LY_EDENIED in case there is no context and the plugin cannot be loaded. + * @return LY_EINVAL when pathname is NULL or the plugin contains invalid content for this libyang version. + * @return LY_ESYS when the plugin file cannot be loaded. + */ +LIBYANG_API_DECL LY_ERR lyplg_add(const char *pathname); + +/** @} plugins */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PLUGINS_H_ */ diff --git a/src/plugins_exts.c b/src/plugins_exts.c new file mode 100644 index 0000000..00970fa --- /dev/null +++ b/src/plugins_exts.c @@ -0,0 +1,680 @@ +/** + * @file plugins_exts.c + * @author Radek Krejci + * @author Michal Vasko + * @brief helper functions for extension plugins + * + * Copyright (c) 2019 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "plugins_exts.h" + +#include +#include +#include + +#include "common.h" +#include "dict.h" +#include "parser_internal.h" +#include "printer_internal.h" +#include "schema_compile.h" +#include "schema_compile_amend.h" +#include "schema_compile_node.h" +#include "schema_features.h" +#include "tree_schema_internal.h" + +LIBYANG_API_DEF const struct lysp_module * +lyplg_ext_parse_get_cur_pmod(const struct lysp_ctx *pctx) +{ + return PARSER_CUR_PMOD(pctx); +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_parse_extension_instance(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + LY_ERR rc = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; + struct lysp_stmt *stmt; + + /* check for invalid substatements */ + LY_LIST_FOR(ext->child, stmt) { + if (stmt->flags & (LYS_YIN_ATTR | LYS_YIN_ARGUMENT)) { + continue; + } + LY_ARRAY_FOR(ext->substmts, u) { + if (ext->substmts[u].stmt == stmt->kw) { + break; + } + } + if (u == LY_ARRAY_COUNT(ext->substmts)) { + LOGVAL(PARSER_CTX(pctx), LYVE_SYNTAX_YANG, "Invalid keyword \"%s\" as a child of \"%s%s%s\" extension instance.", + stmt->stmt, ext->name, ext->argument ? " " : "", ext->argument ? ext->argument : ""); + rc = LY_EVALID; + goto cleanup; + } + } + + /* parse all the known statements */ + LY_ARRAY_FOR(ext->substmts, u) { + LY_LIST_FOR(ext->child, stmt) { + if (ext->substmts[u].stmt != stmt->kw) { + continue; + } + + if ((rc = lys_parse_ext_instance_stmt(pctx, &ext->substmts[u], stmt))) { + goto cleanup; + } + } + } + +cleanup: + return rc; +} + +/** + * @brief Compile an instance extension statement. + * + * @param[in] ctx Compile context. + * @param[in] parsed Parsed ext instance substatement structure. + * @param[in] ext Compiled ext instance. + * @param[in] substmt Compled ext instance substatement info. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_ext_instance_stmt(struct lysc_ctx *ctx, const void *parsed, struct lysc_ext_instance *ext, + struct lysc_ext_substmt *substmt) +{ + LY_ERR rc = LY_SUCCESS; + ly_bool length_restr = 0; + LY_DATA_TYPE basetype; + + /* compilation wthout any storage */ + if (substmt->stmt == LY_STMT_IF_FEATURE) { + ly_bool enabled; + + /* evaluate */ + LY_CHECK_GOTO(rc = lys_eval_iffeatures(ctx->ctx, parsed, &enabled), cleanup); + if (!enabled) { + /* it is disabled, remove the whole extension instance */ + rc = LY_ENOT; + } + } + + if (!substmt->storage || !parsed) { + /* nothing to store or nothing parsed to compile */ + goto cleanup; + } + + switch (substmt->stmt) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_USES: { + const uint16_t flags; + struct lysp_node *pnodes, *pnode; + struct lysc_node *node; + + lyplg_ext_get_storage(ext, LY_STMT_STATUS, sizeof flags, (const void **)&flags); + pnodes = (struct lysp_node *)parsed; + + /* compile nodes */ + LY_LIST_FOR(pnodes, pnode) { + if (pnode->nodetype & (LYS_INPUT | LYS_OUTPUT)) { + /* manual compile */ + node = calloc(1, sizeof(struct lysc_node_action_inout)); + LY_CHECK_ERR_GOTO(!node, LOGMEM(ctx->ctx); rc = LY_EMEM, cleanup); + LY_CHECK_GOTO(rc = lys_compile_node_action_inout(ctx, pnode, node), cleanup); + LY_CHECK_GOTO(rc = lys_compile_node_connect(ctx, NULL, node), cleanup); + } else { + /* ctx->ext substatement storage is used as the document root */ + LY_CHECK_GOTO(rc = lys_compile_node(ctx, pnode, NULL, flags, NULL), cleanup); + } + } + break; + } + case LY_STMT_ARGUMENT: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_KEY: + case LY_STMT_MODIFIER: + case LY_STMT_NAMESPACE: + case LY_STMT_ORGANIZATION: + case LY_STMT_PRESENCE: + case LY_STMT_REFERENCE: + case LY_STMT_UNITS: + /* just make a copy */ + LY_CHECK_GOTO(rc = lydict_insert(ctx->ctx, parsed, 0, substmt->storage), cleanup); + break; + + case LY_STMT_BIT: + basetype = LY_TYPE_BITS; + /* fallthrough */ + case LY_STMT_ENUM: + if (substmt->stmt == LY_STMT_ENUM) { + basetype = LY_TYPE_ENUM; + } + + /* compile */ + LY_CHECK_GOTO(rc = lys_compile_type_enums(ctx, parsed, basetype, NULL, substmt->storage), cleanup); + break; + + case LY_STMT_CONFIG: { + uint16_t flags; + + if (!(ctx->compile_opts & LYS_COMPILE_NO_CONFIG)) { + memcpy(&flags, &parsed, 2); + if (flags & LYS_CONFIG_MASK) { + /* explicitly set */ + flags |= LYS_SET_CONFIG; + } else if (ext->parent_stmt & LY_STMT_DATA_NODE_MASK) { + /* inherit */ + flags = ((struct lysc_node *)ext->parent)->flags & LYS_CONFIG_MASK; + } else { + /* default config */ + flags = LYS_CONFIG_W; + } + memcpy(substmt->storage, &flags, 2); + } /* else leave zero */ + break; + } + case LY_STMT_MUST: { + const struct lysp_restr *restrs = parsed; + + /* sized array */ + COMPILE_ARRAY_GOTO(ctx, restrs, *(struct lysc_must **)substmt->storage, lys_compile_must, rc, cleanup); + break; + } + case LY_STMT_WHEN: { + const uint16_t flags; + const struct lysp_when *when = parsed; + + /* read compiled status */ + lyplg_ext_get_storage(ext, LY_STMT_STATUS, sizeof flags, (const void **)&flags); + + /* compile */ + LY_CHECK_GOTO(rc = lys_compile_when(ctx, when, flags, NULL, NULL, NULL, substmt->storage), cleanup); + break; + } + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_REQUIRE_INSTANCE: + /* just make a copy */ + memcpy(substmt->storage, &parsed, 1); + break; + + case LY_STMT_MANDATORY: + case LY_STMT_ORDERED_BY: + case LY_STMT_STATUS: + /* just make a copy */ + memcpy(substmt->storage, &parsed, 2); + break; + + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + /* just make a copy */ + memcpy(substmt->storage, &parsed, 4); + break; + + case LY_STMT_POSITION: + case LY_STMT_VALUE: + /* just make a copy */ + memcpy(substmt->storage, &parsed, 8); + break; + + case LY_STMT_IDENTITY: + /* compile */ + LY_CHECK_GOTO(rc = lys_identity_precompile(ctx, NULL, NULL, parsed, substmt->storage), cleanup); + break; + + case LY_STMT_LENGTH: + length_restr = 1; + /* fallthrough */ + case LY_STMT_RANGE: + /* compile, use uint64 default range */ + LY_CHECK_GOTO(rc = lys_compile_type_range(ctx, parsed, LY_TYPE_UINT64, length_restr, 0, NULL, substmt->storage), + cleanup); + break; + + case LY_STMT_PATTERN: + /* compile */ + LY_CHECK_GOTO(rc = lys_compile_type_patterns(ctx, parsed, NULL, substmt->storage), cleanup); + break; + + case LY_STMT_TYPE: { + const uint16_t flags; + const char *units; + const struct lysp_type *ptype = parsed; + + /* read compiled info */ + lyplg_ext_get_storage(ext, LY_STMT_STATUS, sizeof flags, (const void **)&flags); + lyplg_ext_get_storage(ext, LY_STMT_UNITS, sizeof units, (const void **)&units); + + /* compile */ + LY_CHECK_GOTO(rc = lys_compile_type(ctx, NULL, flags, ext->def->name, ptype, substmt->storage, &units, NULL), cleanup); + break; + } + case LY_STMT_EXTENSION_INSTANCE: { + struct lysp_ext_instance *extps = (struct lysp_ext_instance *)parsed; + + /* compile sized array */ + COMPILE_EXTS_GOTO(ctx, extps, *(struct lysc_ext_instance **)substmt->storage, ext, rc, cleanup); + break; + } + case LY_STMT_AUGMENT: + case LY_STMT_GROUPING: + case LY_STMT_BASE: + case LY_STMT_BELONGS_TO: + case LY_STMT_DEFAULT: + case LY_STMT_DEVIATE: + case LY_STMT_DEVIATION: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_IF_FEATURE: + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + case LY_STMT_MODULE: + case LY_STMT_PATH: + case LY_STMT_PREFIX: + case LY_STMT_REFINE: + case LY_STMT_REVISION: + case LY_STMT_REVISION_DATE: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPEDEF: + case LY_STMT_UNIQUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Statement \"%s\" compilation is not supported.", lyplg_ext_stmt2str(substmt->stmt)); + rc = LY_EVALID; + goto cleanup; + + default: + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Statement \"%s\" is not supported as an extension " + "(found in \"%s%s%s\") substatement.", lyplg_ext_stmt2str(substmt->stmt), ext->def->name, + ext->argument ? " " : "", ext->argument ? ext->argument : ""); + rc = LY_EVALID; + goto cleanup; + } + +cleanup: + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_compile_extension_instance(struct lysc_ctx *ctx, const struct lysp_ext_instance *extp, + struct lysc_ext_instance *ext) +{ + LY_ERR rc = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + enum ly_stmt stmtp; + const void *storagep; + struct ly_set storagep_compiled = {0}; + + LY_CHECK_ARG_RET(ctx ? ctx->ctx : NULL, ctx, extp, ext, LY_EINVAL); + + /* note into the compile context that we are processing extension now */ + ctx->ext = ext; + + LY_ARRAY_FOR(extp->substmts, u) { + stmtp = extp->substmts[u].stmt; + storagep = *(void **)extp->substmts[u].storage; + + if (ly_set_contains(&storagep_compiled, storagep, NULL)) { + /* this parsed statement has already been compiled (for example, if it is a linked list of parsed nodes) */ + continue; + } + + LY_ARRAY_FOR(ext->substmts, v) { + if (stmtp != ext->substmts[v].stmt) { + continue; + } + + if ((rc = lys_compile_ext_instance_stmt(ctx, storagep, ext, &ext->substmts[v]))) { + goto cleanup; + } + + /* parsed substatement compiled */ + break; + } + + /* compiled */ + ly_set_add(&storagep_compiled, storagep, 1, NULL); + } + +cleanup: + ctx->ext = NULL; + ly_set_erase(&storagep_compiled, NULL); + return rc; +} + +LIBYANG_API_DEF struct ly_ctx * +lyplg_ext_compile_get_ctx(const struct lysc_ctx *ctx) +{ + return ctx->ctx; +} + +LIBYANG_API_DEF uint32_t * +lyplg_ext_compile_get_options(const struct lysc_ctx *ctx) +{ + return &((struct lysc_ctx *)ctx)->compile_opts; +} + +LIBYANG_API_DEF const struct lys_module * +lyplg_ext_compile_get_cur_mod(const struct lysc_ctx *ctx) +{ + return ctx->cur_mod; +} + +LIBYANG_API_DEF struct lysp_module * +lyplg_ext_compile_get_pmod(const struct lysc_ctx *ctx) +{ + return ctx->pmod; +} + +LIBYANG_API_DEF struct ly_out ** +lyplg_ext_print_get_out(const struct lyspr_ctx *ctx) +{ + return &((struct lyspr_ctx *)ctx)->out; +} + +LIBYANG_API_DEF uint32_t * +lyplg_ext_print_get_options(const struct lyspr_ctx *ctx) +{ + return &((struct lyspr_ctx *)ctx)->options; +} + +LIBYANG_API_DEF uint16_t * +lyplg_ext_print_get_level(const struct lyspr_ctx *ctx) +{ + return &((struct lyspr_ctx *)ctx)->level; +} + +LIBYANG_API_DECL LY_ERR +lyplg_ext_sprinter_ctree_add_ext_nodes(const struct lyspr_tree_ctx *ctx, struct lysc_ext_instance *ext, + lyplg_ext_sprinter_ctree_override_clb clb) +{ + LY_ERR rc = LY_SUCCESS; + uint32_t i; + struct lysc_node *schema; + + LY_CHECK_ARG_RET2(NULL, ctx, ext, LY_EINVAL); + + LY_ARRAY_FOR(ext->substmts, i) { + switch (ext->substmts[i].stmt) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + schema = *((struct lysc_node **)ext->substmts[i].storage); + if (schema) { + rc = lyplg_ext_sprinter_ctree_add_nodes(ctx, schema, clb); + return rc; + } + default: + break; + } + } + + return rc; +} + +LIBYANG_API_DECL LY_ERR +lyplg_ext_sprinter_ptree_add_ext_nodes(const struct lyspr_tree_ctx *ctx, struct lysp_ext_instance *ext, + lyplg_ext_sprinter_ptree_override_clb clb) +{ + LY_ERR rc = LY_SUCCESS; + uint32_t i; + struct lysp_node *schema; + + LY_CHECK_ARG_RET2(NULL, ctx, ext, LY_EINVAL); + + LY_ARRAY_FOR(ext->substmts, i) { + switch (ext->substmts[i].stmt) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + schema = *((struct lysp_node **)ext->substmts[i].storage); + if (schema) { + rc = lyplg_ext_sprinter_ptree_add_nodes(ctx, schema, clb); + return rc; + } + default: + break; + } + } + + return rc; +} + +LIBYANG_API_DECL LY_ERR +lyplg_ext_sprinter_ctree_add_nodes(const struct lyspr_tree_ctx *ctx, struct lysc_node *nodes, + lyplg_ext_sprinter_ctree_override_clb clb) +{ + struct lyspr_tree_schema *new; + + LY_CHECK_ARG_RET1(NULL, ctx, LY_EINVAL); + + if (!nodes) { + return LY_SUCCESS; + } + + LY_ARRAY_NEW_RET(NULL, ((struct lyspr_tree_ctx *)ctx)->schemas, new, LY_EMEM); + new->compiled = 1; + new->ctree = nodes; + new->cn_overr = clb; + + return LY_SUCCESS; +} + +LIBYANG_API_DECL LY_ERR +lyplg_ext_sprinter_ptree_add_nodes(const struct lyspr_tree_ctx *ctx, struct lysp_node *nodes, + lyplg_ext_sprinter_ptree_override_clb clb) +{ + struct lyspr_tree_schema *new; + + LY_CHECK_ARG_RET1(NULL, ctx, LY_EINVAL); + + if (!nodes) { + return LY_SUCCESS; + } + + LY_ARRAY_NEW_RET(NULL, ((struct lyspr_tree_ctx *)ctx)->schemas, new, LY_EMEM); + new->compiled = 0; + new->ptree = nodes; + new->pn_overr = clb; + + return LY_SUCCESS; +} + +LIBYANG_API_DECL LY_ERR +lyplg_ext_sprinter_tree_set_priv(const struct lyspr_tree_ctx *ctx, void *plugin_priv, void (*free_clb)(void *plugin_priv)) +{ + LY_CHECK_ARG_RET1(NULL, ctx, LY_EINVAL); + + ((struct lyspr_tree_ctx *)ctx)->plugin_priv = plugin_priv; + ((struct lyspr_tree_ctx *)ctx)->free_plugin_priv = free_clb; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF const char * +lyplg_ext_stmt2str(enum ly_stmt stmt) +{ + if (stmt == LY_STMT_EXTENSION_INSTANCE) { + return "extension instance"; + } else { + return lys_stmt_str(stmt); + } +} + +LIBYANG_API_DEF enum ly_stmt +lyplg_ext_nodetype2stmt(uint16_t nodetype) +{ + switch (nodetype) { + case LYS_CONTAINER: + return LY_STMT_CONTAINER; + case LYS_CHOICE: + return LY_STMT_CHOICE; + case LYS_LEAF: + return LY_STMT_LEAF; + case LYS_LEAFLIST: + return LY_STMT_LEAF_LIST; + case LYS_LIST: + return LY_STMT_LIST; + case LYS_ANYXML: + return LY_STMT_ANYXML; + case LYS_ANYDATA: + return LY_STMT_ANYDATA; + case LYS_CASE: + return LY_STMT_CASE; + case LYS_RPC: + return LY_STMT_RPC; + case LYS_ACTION: + return LY_STMT_ACTION; + case LYS_NOTIF: + return LY_STMT_NOTIFICATION; + case LYS_USES: + return LY_STMT_USES; + case LYS_INPUT: + return LY_STMT_INPUT; + case LYS_OUTPUT: + return LY_STMT_OUTPUT; + default: + return LY_STMT_NONE; + } +} + +LY_ERR +lyplg_ext_get_storage_p(const struct lysc_ext_instance *ext, int stmt, const void ***storage_p) +{ + LY_ARRAY_COUNT_TYPE u; + enum ly_stmt match = 0; + + *storage_p = NULL; + + if (!(stmt & LY_STMT_NODE_MASK)) { + /* matching a non-node statement */ + match = stmt; + } + + LY_ARRAY_FOR(ext->substmts, u) { + if ((match && (ext->substmts[u].stmt == match)) || (!match && (ext->substmts[u].stmt & stmt))) { + *storage_p = ext->substmts[u].storage; + return LY_SUCCESS; + } + } + + return LY_ENOT; +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_get_storage(const struct lysc_ext_instance *ext, int stmt, uint32_t storage_size, const void **storage) +{ + LY_ERR rc = LY_SUCCESS; + const void **s; + + /* get pointer to the storage, is set even on error */ + rc = lyplg_ext_get_storage_p(ext, stmt, &s); + + /* assign */ + if (s) { + memcpy(storage, s, storage_size); + } else { + memset(storage, 0, storage_size); + } + + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_parsed_get_storage(const struct lysc_ext_instance *ext, int stmt, uint32_t storage_size, const void **storage) +{ + LY_ARRAY_COUNT_TYPE u; + const struct lysp_ext_instance *extp = NULL; + enum ly_stmt match = 0; + const void **s = NULL; + + /* find the parsed ext instance */ + LY_ARRAY_FOR(ext->module->parsed->exts, u) { + extp = &ext->module->parsed->exts[u]; + + if (ext->def == extp->def->compiled) { + break; + } + extp = NULL; + } + assert(extp); + + if (!(stmt & LY_STMT_NODE_MASK)) { + /* matching a non-node statement */ + match = stmt; + } + + /* get the substatement */ + LY_ARRAY_FOR(extp->substmts, u) { + if ((match && (extp->substmts[u].stmt == match)) || (!match && (extp->substmts[u].stmt & stmt))) { + s = extp->substmts[u].storage; + break; + } + } + + /* assign */ + if (s) { + memcpy(storage, s, storage_size); + } else { + memset(storage, 0, storage_size); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_get_data(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, void **ext_data, ly_bool *ext_data_free) +{ + LY_ERR rc; + + if (!ctx->ext_clb) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_EINVAL, "Failed to get extension data, no callback set."); + return LY_EINVAL; + } + + if ((rc = ctx->ext_clb(ext, ctx->ext_clb_data, ext_data, ext_data_free))) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, rc, "Callback for getting ext data failed."); + } + return rc; +} diff --git a/src/plugins_exts.h b/src/plugins_exts.h new file mode 100644 index 0000000..f005391 --- /dev/null +++ b/src/plugins_exts.h @@ -0,0 +1,1048 @@ +/** + * @file plugins_exts.h + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang support for YANG extensions implementation. + * + * Copyright (c) 2015 - 2022 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_PLUGINS_EXTS_H_ +#define LY_PLUGINS_EXTS_H_ + +#include "log.h" +#include "parser_data.h" +#include "plugins.h" +#include "tree_data.h" +#include "tree_edit.h" +#include "tree_schema.h" + +struct ly_ctx; +struct ly_in; +struct lyd_node; +struct lysc_ctx; +struct lysc_ext_substmt; +struct lysp_ctx; +struct lyspr_ctx; +struct lyspr_tree_ctx; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page howtoPluginsExtensions Extension Plugins + * + * Note that the part of the libyang API here is available only by including a separated `` header + * file. Also note that the extension plugins API is versioned separately from libyang itself, so backward incompatible + * changes can come even without changing libyang major version. + * + * YANG extensions are very complex. Usually only its description specifies how it is supposed to behave, what are the + * allowed substatements, their cardinality or if the standard YANG statements placed inside the extension differs somehow + * in their meaning or behavior. libyang provides the Extension plugins API to implement such extensions and add its support + * into libyang itself. However we tried our best, the API is not (and it cannot be) so universal and complete to cover all + * possibilities. There are definitely use cases which cannot be simply implemented only with this API. + * + * libyang implements 3 important extensions: [NACM](https://tools.ietf.org/html/rfc8341), [Metadata](@ref howtoDataMetadata) + * and [yang-data](@ref howtoDataYangdata). Despite the core implementation in all three cases is done via extension plugin + * API, also other parts of the libyang code had to be extended to cover complete scope of the extensions. + * + * We believe, that the API is capable to allow implementation of very wide range of YANG extensions. However, if you see + * limitations for the particular YANG extension, don't hesitate to contact the project developers to discuss all the + * options, including updating the API. + * + * The plugin's functionality is provided to libyang via a set of callbacks specified as an array of ::lyplg_ext_record + * structures using the ::LYPLG_EXTENSIONS macro. + * + * The most important callbacks are ::lyplg_ext.parse and ::lyplg_ext.compile. They are responsible for parsing and + * compiling extension instance. This should include validating all the substatements, their values, or placement of + * the extension instance itself. If needed, the processed data can be stored in some form into the compiled schema + * representation of the extension instance. To make this task as easy as possible, libyang provides several + * [parsing](@ref pluginsExtensionsParse) and [compilation](@ref pluginsExtensionsCompile) helper functions to process + * known YANG statements exactly as if they were standard YANG statements. + * + * The data validation callback ::lyplg_ext.validate is used for additional validation of a data nodes that contains the + * connected extension instance directly (as a substatement) or indirectly in case of terminal nodes via their type (no + * matter if the extension instance is placed directly in the leaf's/leaf-list's type or in the type of the referenced + * typedef). + * + * The ::lyplg_ext.printer_info callback implement printing the compiled extension instance data when the schema (module) is + * being printed in the ::LYS_OUT_YANG_COMPILED (info) format. As for compile callback, there are also + * [helper functions](@ref pluginsExtensionsSprinterInfo) to access printer's context and to print standard YANG statements + * placed in the extension instance by libyang itself. + * + * The ::lyplg_ext.printer_ctree and ::lyplg_ext.printer_ptree callbacks implement printing of YANG tree diagrams + * (RFC 8340) for extension instance data. These callbacks are called for extension instances that have + * parents of type ::LY_STMT_MODULE, ::LY_STMT_SUBMODULE. Or these callbacks are called if the printer_tree finds + * a compiled/parsed data-node containing an extension instance. The callbacks should then decide which nodes + * should be printed within the extension instance. In addition, it is possible to register additional callbacks + * to the printer_tree context to override the form of the each node in the extension instance. + * + * The last callback, ::lyplg_ext.cfree, is supposed to free all the data allocated by the ::lyplg_ext.compile callback. + * To free the data created by helper function ::lyplg_ext_compile_extension_instance(), the plugin can used + * ::lyplg_ext_cfree_instance_substatements(). + * + * The plugin information contains also the plugin identifier (::lyplg_type.id). This string can serve to identify the + * specific plugin responsible to storing data value. In case the user can recognize the id string, it can access the + * plugin specific data with the appropriate knowledge of its structure. + * + * Logging information from an extension plugin is possible via ::lyplg_ext_parse_log() and ::lyplg_ext_compile_log() functions. + */ + +/** + * @defgroup pluginsExtensions Plugins: Extensions + * + * Structures and functions to for libyang plugins implementing specific YANG extensions defined in YANG modules. For more + * information, see @ref howtoPluginsTypes. + * + * This part of libyang API is available by including `` header file. + * + * @{ + */ + +/** + * @brief Extensions API version + */ +#define LYPLG_EXT_API_VERSION 6 + +/** + * @brief Mask for an operation statement. + * + * This mask matches action and RPC. + */ +#define LY_STMT_OP_MASK (LY_STMT_ACTION | LY_STMT_RPC) + +/** + * @brief Mask for a data node statement. + * + * This mask matches anydata, anyxml, case, choice, container, leaf, leaf-list, and list. + */ +#define LY_STMT_DATA_NODE_MASK (LY_STMT_ANYDATA | LY_STMT_ANYXML | LY_STMT_CASE | LY_STMT_CHOICE | LY_STMT_CONTAINER |\ + LY_STMT_LEAF | LY_STMT_LEAF_LIST | LY_STMT_LIST) + +/** + * @brief Mask for a node statement. + * + * This mask matches notification, input, output, action, RPC, anydata, anyxml, augment, case, choice, container, + * grouping, leaf, leaf-list, list, and uses. + */ +#define LY_STMT_NODE_MASK 0xFFFF + +/** + * @brief List of YANG statements + * + * Their description mentions what types are stored for each statement. Note that extension instance storage + * always stores a pointer to the type, not the type itself. + */ +enum ly_stmt { + LY_STMT_NONE = 0, + + LY_STMT_NOTIFICATION = 0x0001, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_notif *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_notif *` */ + LY_STMT_INPUT = 0x0002, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_action_inout *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_action_inout *` */ + LY_STMT_OUTPUT = 0x0004, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_action_inout *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_action_inout *` */ + LY_STMT_ACTION = 0x0008, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_action *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_action *` */ + LY_STMT_RPC = 0x0010, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_action *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_action *` */ + LY_STMT_ANYDATA = 0x0020, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_anydata *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_anydata *` */ + LY_STMT_ANYXML = 0x0040, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_anydata *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_anydata *` */ + LY_STMT_AUGMENT = 0x0080, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_augment *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_CASE = 0x0100, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_case *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_case *` */ + LY_STMT_CHOICE = 0x0200, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_choice *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_choice *` */ + LY_STMT_CONTAINER = 0x0400, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_container *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_container *` */ + LY_STMT_GROUPING = 0x0800, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_grp *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_LEAF = 0x1000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_leaf *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_leaf *` */ + LY_STMT_LEAF_LIST = 0x2000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_leaflist *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_leaflist *` */ + LY_STMT_LIST = 0x4000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_list *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node_list *` */ + LY_STMT_USES = 0x8000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_node_uses *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_node *` */ + + LY_STMT_ARGUMENT = 0x10000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_ext *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_ext *` */ + LY_STMT_BASE = 0x20000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char **`[] + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_ident *` */ + LY_STMT_BELONGS_TO = 0x30000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_submodule *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_BIT = 0x40000, /**< ::lysp_ext_substmt.storage - `struct lysp_type_enum *`[] + ::lysp_ext_instance.parent - `struct lysp_type_enum *` + ::lysc_ext_substmt.storage - `struct lysc_type_bitenum_item *`[] + ::lysc_ext_instance.parent - `struct lysc_type_bitenum_item *` */ + LY_STMT_CONFIG = 0x50000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `uint16_t *` + ::lysc_ext_substmt.storage - `uint16_t *` + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_CONTACT = 0x60000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_(sub)module *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_DEFAULT = 0x70000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_qname *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_node *`, `struct lysc_type *` (typedef) */ + LY_STMT_DESCRIPTION = 0x80000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - compiled parent statement */ + LY_STMT_DEVIATE = 0x90000, /**< ::lysp_ext_substmt.storage - `struct lysp_deviate *`[] + ::lysp_ext_instance.parent - `struct lysp_deviate *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - not compiled */ + LY_STMT_DEVIATION = 0xA0000, /**< ::lysp_ext_substmt.storage - `struct lysp_deviation *`[] + ::lysp_ext_instance.parent - `struct lysp_deviation *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_ENUM = 0xB0000, /**< ::lysp_ext_substmt.storage - `struct lysp_type_enum *`[] + ::lysp_ext_instance.parent - `struct lysp_type_enum *` + ::lysc_ext_substmt.storage - `struct lysc_type_bitenum_item *`[] + ::lysc_ext_instance.parent - `struct lysc_type_bitenum_item *` */ + LY_STMT_ERROR_APP_TAG = 0xC0000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - compiled restriction structure */ + LY_STMT_ERROR_MESSAGE = 0xD0000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - compiled restriction structure */ + LY_STMT_EXTENSION = 0xE0000, /**< ::lysp_ext_substmt.storage - `struct lysp_ext *`[] + ::lysp_ext_instance.parent - `struct lysp_ext *` + ::lysc_ext_substmt.storage - not compiled explicitly + ::lysc_ext_instance.parent - `struct lysc_ext *` */ + LY_STMT_EXTENSION_INSTANCE = 0xF0000, /**< ::lysp_ext_substmt.storage - `struct lysp_ext_instance *`[] + ::lysc_ext_substmt.storage - `struct lysc_ext_instance *`[] */ + LY_STMT_FEATURE = 0x100000, /**< ::lysp_ext_substmt.storage - `struct lysp_feature *`[] + ::lysp_ext_instance.parent - `struct lysp_feature *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - not compiled */ + LY_STMT_FRACTION_DIGITS = 0x110000, /**< ::lysp_ext_substmt.storage - `uint8_t *` + ::lysp_ext_instance.parent - `struct lysp_type *` + ::lysc_ext_substmt.storage - `uint8_t *` + ::lysc_ext_instance.parent - `struct lysc_type *` */ + LY_STMT_IDENTITY = 0x120000, /**< ::lysp_ext_substmt.storage - `struct lysp_ident *`[] + ::lysp_ext_instance.parent - `struct lysp_ident *` + ::lysc_ext_substmt.storage - `struct lysc_ident *`[] + ::lysc_ext_instance.parent - `struct lysc_ident *` */ + LY_STMT_IF_FEATURE = 0x130000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_qname *`[] + ::lysc_ext_substmt.storage - no storage, evaluated when compiled + ::lysc_ext_instance.parent - compiled parent statement */ + LY_STMT_IMPORT = 0x140000, /**< ::lysp_ext_substmt.storage - `struct lysp_import *`[] + ::lysp_ext_instance.parent - `struct lysp_import *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - not compiled */ + LY_STMT_INCLUDE = 0x150000, /**< ::lysp_ext_substmt.storage - `struct lysp_include *`[] + ::lysp_ext_instance.parent - `struct lysp_include *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - not compiled */ + LY_STMT_KEY = 0x160000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_node_list *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_node_list *` */ + LY_STMT_LENGTH = 0x170000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_range *` */ + LY_STMT_MANDATORY = 0x180000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `uint16_t *` + ::lysc_ext_substmt.storage - `uint16_t *` + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_MAX_ELEMENTS = 0x190000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `uint32_t *` + ::lysc_ext_substmt.storage - `uint32_t *` + ::lysc_ext_instance.parent - `struct lysc_node_list *` */ + LY_STMT_MIN_ELEMENTS = 0x1A0000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `uint32_t *` + ::lysc_ext_substmt.storage - `uint32_t *` + ::lysc_ext_instance.parent - `struct lysc_node_list *` */ + LY_STMT_MODIFIER = 0x1B0000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_pattern *` */ + LY_STMT_MODULE = 0x1C0000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_module *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_MUST = 0x1D0000, /**< ::lysp_ext_substmt.storage - `struct lysp_restr *`[] + ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage - `struct lysc_must *`[] + ::lysc_ext_instance.parent - `struct lysc_must *` */ + LY_STMT_NAMESPACE = 0x1E0000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_module *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_ORDERED_BY = 0x1F0000, /**< ::lysp_ext_substmt.storage - `uint16_t *` + ::lysp_ext_instance.parent - `struct lysp_node *` + ::lysc_ext_substmt.storage - `uint16_t *` + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_ORGANIZATION = 0x200000, /**< ::lysp_ext_substmt.storage - `const char *` + ::lysp_ext_instance.parent - `struct lysp_(sub)module *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_PATH = 0x210000, /**< ::lysp_ext_substmt.storage - `struct lyxp_expr *` + ::lysp_ext_instance.parent - `struct lysp_type *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_type *` */ + LY_STMT_PATTERN = 0x220000, /**< ::lysp_ext_substmt.storage - `struct lysp_restr *`[] + ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage - `struct lysc_pattern **`[] + ::lysc_ext_instance.parent - `struct lysc_pattern *` */ + LY_STMT_POSITION = 0x230000, /**< ::lysp_ext_substmt.storage - `int64_t *` + ::lysp_ext_instance.parent - `struct lysp_type_enum *` + ::lysc_ext_substmt.storage - `int64_t *` + ::lysc_ext_instance.parent - `struct lysc_type_bitenum_item *` */ + LY_STMT_PREFIX = 0x240000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_PRESENCE = 0x250000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_node_container *` */ + LY_STMT_RANGE = 0x260000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_restr *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_range *` */ + LY_STMT_REFERENCE = 0x270000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - compiled parent statement */ + LY_STMT_REFINE = 0x280000, /**< ::lysp_ext_substmt.storage - `struct lysp_refine *`[] + ::lysp_ext_instance.parent - `struct lysp_refine *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_node *` */ + LY_STMT_REQUIRE_INSTANCE = 0x290000, /**< ::lysp_ext_substmt.storage - `uint8_t *` + ::lysp_ext_instance.parent - `struct lysp_type *` + ::lysc_ext_substmt.storage - `uint8_t *` + ::lysc_ext_instance.parent - `struct lysc_type *` */ + LY_STMT_REVISION = 0x2A0000, /**< ::lysp_ext_substmt.storage - `struct lysp_revision *`[] + ::lysp_ext_instance.parent - `struct lysp_revision *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - not compiled */ + LY_STMT_REVISION_DATE = 0x2B0000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - not compiled */ + LY_STMT_STATUS = 0x2C0000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `uint16_t *` + ::lysc_ext_substmt.storage - `uint16_t *` + ::lysc_ext_instance.parent - compiled parent statement */ + LY_STMT_SUBMODULE = 0x2D0000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_submodule *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_TYPE = 0x2E0000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_type *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_type *` */ + LY_STMT_TYPEDEF = 0x2F0000, /**< ::lysp_ext_substmt.storage - `struct lysp_tpdf *`[] + ::lysp_ext_instance.parent - `struct lysp_tpdf *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_type *` */ + LY_STMT_UNIQUE = 0x300000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_qname *`[] + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_node_list *` */ + LY_STMT_UNITS = 0x310000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `const char *` + ::lysc_ext_substmt.storage - `const char *` + ::lysc_ext_instance.parent - `struct lysc_node *`, `struct lysc_type *` (typedef) */ + LY_STMT_VALUE = 0x320000, /**< ::lysp_ext_substmt.storage - `int64_t *` + ::lysp_ext_instance.parent - `struct lysp_type_enum *` + ::lysc_ext_substmt.storage - `int64_t *` + ::lysc_ext_instance.parent - `struct lysc_type_bitenum_item *` */ + LY_STMT_WHEN = 0x330000, /**< ::lysp_ext_substmt.storage and ::lysp_ext_instance.parent - `struct lysp_when *` + ::lysc_ext_substmt.storage and ::lysc_ext_instance.parent - `struct lysc_when *` */ + LY_STMT_YANG_VERSION = 0x340000, /**< ::lysp_ext_substmt.storage - `uint8_t *` + ::lysp_ext_instance.parent - `struct lysp_(sub)module *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_module *` */ + LY_STMT_YIN_ELEMENT = 0x350000, /**< ::lysp_ext_substmt.storage - `uint16_t *` + ::lysp_ext_instance.parent - `struct lysp_ext *` + ::lysc_ext_substmt.storage - not compiled + ::lysc_ext_instance.parent - `struct lysc_ext *` */ + + /* separated from the list of statements + * the following tokens are part of the syntax and parsers have to work + * with them, but they are not a standard YANG statements + */ + LY_STMT_SYNTAX_SEMICOLON, + LY_STMT_SYNTAX_LEFT_BRACE, + LY_STMT_SYNTAX_RIGHT_BRACE, + + /* + * YIN-specific tokens, still they are part of the syntax, but not the standard statements + */ + LY_STMT_ARG_TEXT, + LY_STMT_ARG_VALUE +}; + +/** + * @brief Structure representing a generic parsed YANG substatement in an extension instance. + */ +struct lysp_stmt { + const char *stmt; /**< identifier of the statement */ + const char *arg; /**< statement's argument */ + LY_VALUE_FORMAT format; /**< prefix format of the identifier/argument (::LY_VALUE_XML is YIN format) */ + void *prefix_data; /**< Format-specific data for prefix resolution (see ly_resolve_prefix()) */ + + struct lysp_stmt *next; /**< link to the next statement */ + struct lysp_stmt *child; /**< list of the statement's substatements (linked list) */ + uint16_t flags; /**< statement flags, can be set to LYS_YIN_ATTR */ + enum ly_stmt kw; /**< numeric respresentation of the stmt value */ +}; + +/** + * @brief Structure representing a parsed known YANG substatement in an extension instance. + */ +struct lysp_ext_substmt { + enum ly_stmt stmt; /**< parsed substatement */ + void *storage; /**< pointer to the parsed storage of the statement according to the specific + lys_ext_substmt::stmt */ +}; + +/** + * @brief YANG extension parsed instance. + */ +struct lysp_ext_instance { + const char *name; /**< extension identifier, including possible prefix */ + const char *argument; /**< optional value of the extension's argument */ + LY_VALUE_FORMAT format; /**< prefix format of the extension name/argument (::LY_VALUE_XML is YIN format) */ + void *prefix_data; /**< format-specific data for prefix resolution (see ly_resolve_prefix()) */ + struct lysp_ext *def; /**< pointer to the extension definition */ + + void *parent; /**< pointer to the parent statement holding the extension instance(s), use + ::lysp_ext_instance#parent_stmt to access the value/structure */ + enum ly_stmt parent_stmt; /**< type of the parent statement */ + LY_ARRAY_COUNT_TYPE parent_stmt_index; /**< index of the stamenet in case the parent does not point to the parent + statement directly and it is an array */ + uint16_t flags; /**< ::LYS_INTERNAL value (@ref snodeflags) */ + + const struct lyplg_ext_record *record; /**< extension definition plugin record, if any */ + struct lysp_ext_substmt *substmts; /**< list of supported known YANG statements with the pointer to their + parsed data ([sized array](@ref sizedarrays)) */ + void *parsed; /**< private plugin parsed data */ + struct lysp_stmt *child; /**< list of generic (unknown) YANG statements */ +}; + +/** + * @brief Structure representing a compiled known YANG substatement in an extension instance. + */ +struct lysc_ext_substmt { + enum ly_stmt stmt; /**< compiled substatement */ + void *storage; /**< pointer to the compiled storage of the statement according to the specific + lys_ext_substmt::stmt */ +}; + +/** + * @brief YANG extension compiled instance. + */ +struct lysc_ext_instance { + struct lysc_ext *def; /**< pointer to the extension definition */ + const char *argument; /**< optional value of the extension's argument */ + struct lys_module *module; /**< module where the extension instantiated is defined */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + + void *parent; /**< pointer to the parent element holding the extension instance(s), use + ::lysc_ext_instance#parent_stmt to access the value/structure */ + enum ly_stmt parent_stmt; /**< type of the parent statement */ + LY_ARRAY_COUNT_TYPE parent_stmt_index; /**< index of the stamenet in case the parent does not point to the parent + statement directly and it is an array */ + + struct lysc_ext_substmt *substmts; /**< list of supported known YANG statements with the pointer to their + compiled data ([sized array](@ref sizedarrays)) */ + void *compiled; /**< private plugin compiled data */ +}; + +/** + * @brief Macro to define plugin information in external plugins + * + * Use as follows: + * LYPLG_EXTENSIONS = {{}, ..., {0}}; + */ +#define LYPLG_EXTENSIONS \ + uint32_t plugins_extensions_apiver__ = LYPLG_EXT_API_VERSION; \ + const struct lyplg_ext_record plugins_extensions__[] + +/** + * @defgroup pluginsExtensionsParse Plugins: Extensions parsing support + * @ingroup pluginsExtensions + * + * Implementing extension plugin parse callback. + * + * @{ + */ + +/** + * @brief Callback for parsing extension instance substatements. + * + * All known YANG substatements can easily be parsed using ::lyplg_ext_parse_extension_instance. + * + * @param[in] pctx Parse context. + * @param[in,out] ext Parsed extension instance data. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the extension instance is not supported and should be removed. + * @return LY_ERR error on error. + */ +typedef LY_ERR (*lyplg_ext_parse_clb)(struct lysp_ctx *pctx, struct lysp_ext_instance *ext); + +/** + * @brief Log a message from an extension plugin using the parsed extension instance. + * + * @param[in] pctx Parse context to use. + * @param[in] ext Parsed extensiopn instance. + * @param[in] level Log message level (error, warning, etc.) + * @param[in] err_no Error type code. + * @param[in] format Format string to print. + * @param[in] ... Format variable parameters. + */ +LIBYANG_API_DECL void lyplg_ext_parse_log(const struct lysp_ctx *pctx, const struct lysp_ext_instance *ext, + LY_LOG_LEVEL level, LY_ERR err_no, const char *format, ...); + +/** + * @brief Get current parsed module from a parse context. + * + * @param[in] pctx Parse context. + * @return Current (local) parse mod. + */ +LIBYANG_API_DECL const struct lysp_module *lyplg_ext_parse_get_cur_pmod(const struct lysp_ctx *pctx); + +/** + * @brief Parse substatements of an extension instance. + * + * Uses standard libyang schema compiler to transform YANG statements into the parsed schema structures. The plugins are + * supposed to use this function when the extension instance's substatements can be parsed in a standard way. + * + * @param[in] pctx Parse context. + * @param[in,out] ext Parsed extension instance with the prepared ::lysp_ext_instance.substmts array, which will be + * updated by storing the parsed data. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_parse_extension_instance(struct lysp_ctx *pctx, struct lysp_ext_instance *ext); + +/** @} pluginsExtensionsParse */ + +/** + * @defgroup pluginsExtensionsCompile Plugins: Extensions compilation support + * @ingroup pluginsExtensions + * + * Implementing extension plugin compile callback. + * + * @{ + */ + +/** + * @defgroup scflags Schema compile flags + * + * Flags to modify schema compilation process and change the way how the particular statements are being compiled. * + * @{ + */ +#define LYS_COMPILE_GROUPING 0x01 /**< Compiling (validation) of a non-instantiated grouping. + In this case not all the restrictions are checked since they can + be valid only in the real placement of the grouping. This is + the case of any restriction that needs to look out of the statements + themselves, since the context is not known. */ +#define LYS_COMPILE_DISABLED 0x02 /**< Compiling a disabled subtree (by its if-features). Meaning + it will be removed at the end of compilation and should not be + added to any unres sets. */ +#define LYS_COMPILE_NO_CONFIG 0x04 /**< ignore config statements, neither inherit config value */ +#define LYS_COMPILE_NO_DISABLED 0x08 /**< ignore if-feature statements */ + +#define LYS_COMPILE_RPC_INPUT (LYS_IS_INPUT | LYS_COMPILE_NO_CONFIG) /**< Internal option when compiling schema tree of RPC/action input */ +#define LYS_COMPILE_RPC_OUTPUT (LYS_IS_OUTPUT | LYS_COMPILE_NO_CONFIG) /**< Internal option when compiling schema tree of RPC/action output */ +#define LYS_COMPILE_NOTIFICATION (LYS_IS_NOTIF | LYS_COMPILE_NO_CONFIG) /**< Internal option when compiling schema tree of Notification */ + +/** @} scflags */ + +/** + * @brief Callback to compile extension from the lysp_ext_instance to the lysc_ext_instance. The later structure is generally prepared + * and only the extension specific data are supposed to be added (if any). + * + * The parsed generic statements can be processed by the callback on its own or the ::lyplg_ext_compile_extension_instance() + * function can be used to let the compilation to libyang following the standard rules for processing the YANG statements. + * + * @param[in] cctx Current compile context. + * @param[in] extp Parsed extension instance data. + * @param[in,out] ext Prepared compiled extension instance structure where an addition, extension-specific, data are + * supposed to be placed for later use (data validation or use of external tool). + * @return LY_SUCCESS in case of success. + * @return LY_ENOT in case the extension instance is not supported and should be removed. + * @return LY_ERR error on error. + */ +typedef LY_ERR (*lyplg_ext_compile_clb)(struct lysc_ctx *cctx, const struct lysp_ext_instance *extp, + struct lysc_ext_instance *ext); + +/** + * @brief Log a message from an extension plugin using the compiled extension instance. + * + * @param[in] cctx Optional compile context to generate the path from. + * @param[in] ext Compiled extension instance. + * @param[in] level Log message level (error, warning, etc.) + * @param[in] err_no Error type code. + * @param[in] format Format string to print. + */ +LIBYANG_API_DECL void lyplg_ext_compile_log(const struct lysc_ctx *cctx, const struct lysc_ext_instance *ext, + LY_LOG_LEVEL level, LY_ERR err_no, const char *format, ...); + +/** + * @brief Log a message from an extension plugin using the compiled extension instance with an explicit error path. + * + * @param[in] path Log error path to use. + * @param[in] ext Compiled extension instance. + * @param[in] level Log message level (error, warning, etc.) + * @param[in] err_no Error type code. + * @param[in] format Format string to print. + */ +LIBYANG_API_DECL void lyplg_ext_compile_log_path(const char *path, const struct lysc_ext_instance *ext, + LY_LOG_LEVEL level, LY_ERR err_no, const char *format, ...); + +/** + * @brief YANG schema compilation context getter for libyang context. + * + * @param[in] ctx YANG schema compilation context. + * @return libyang context connected with the compilation context. + */ +LIBYANG_API_DECL struct ly_ctx *lyplg_ext_compile_get_ctx(const struct lysc_ctx *ctx); + +/** + * @brief YANG schema compilation context getter for compilation options. + * + * @param[in] ctx YANG schema compilation context. + * @return pointer to the compilation options to allow modifying them with @ref scflags values. + */ +LIBYANG_API_DECL uint32_t *lyplg_ext_compile_get_options(const struct lysc_ctx *ctx); + +/** + * @brief YANG schema compilation context getter for current module. + * + * @param[in] ctx YANG schema compilation context. + * @return current module. + */ +LIBYANG_API_DECL const struct lys_module *lyplg_ext_compile_get_cur_mod(const struct lysc_ctx *ctx); + +/** + * @brief YANG schema compilation context getter for currently processed module. + * + * @param[in] ctx YANG schema compilation context. + * @return Currently processed module. + */ +LIBYANG_API_DECL struct lysp_module *lyplg_ext_compile_get_pmod(const struct lysc_ctx *ctx); + +/** + * @brief Compile substatements of an extension instance. + * + * Uses standard libyang schema compiler to transform YANG statements into the compiled schema structures. The plugins are + * supposed to use this function when the extension instance's substatements are supposed to be compiled in a standard way + * (or if just the @ref scflags are enough to modify the compilation process). + * + * @param[in] ctx Compile context. + * @param[in] extp Parsed representation of the extension instance being processed. + * @param[in,out] ext Compiled extension instance with the prepared ::lysc_ext_instance.substmts array, which will be updated + * by storing the compiled data. + * @return LY_SUCCESS on success. + * @return LY_EVALID if compilation of the substatements fails. + * @return LY_ENOT if the extension is disabled (by if-feature) and should be ignored. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_compile_extension_instance(struct lysc_ctx *ctx, const struct lysp_ext_instance *extp, + struct lysc_ext_instance *ext); + +/** @} pluginsExtensionsCompile */ + +/** + * @defgroup pluginsExtensionsSprinterInfo Plugins: Extensions schema info printer support + * @ingroup pluginsExtensions + * + * Implementing extension plugin schema info printer callback. + * + * @{ + */ + +/** + * @brief Callback to print the compiled extension instance's private data in the INFO format. + * + * @param[in] ctx YANG printer context to provide output handler and other information for printing. + * @param[in] ext The compiled extension instance, mainly to access the extensions. + * @param[in,out] flag Flag to be shared with the caller regarding the opening brackets - 0 if the '{' not yet printed, + * 1 otherwise. + * @return LY_SUCCESS when everything was fine, other LY_ERR values in case of failure + */ +typedef LY_ERR (*lyplg_ext_sprinter_info_clb)(struct lyspr_ctx *ctx, struct lysc_ext_instance *ext, ly_bool *flag); + +/** + * @brief YANG printer context getter for output handler. + * + * @param[in] ctx YANG printer context. + * @return Output handler where the data are being printed. Note that the address of the handler pointer in the context is + * returned to allow to modify the handler. + */ +LIBYANG_API_DECL struct ly_out **lyplg_ext_print_get_out(const struct lyspr_ctx *ctx); + +/** + * @brief YANG printer context getter for printer options. + * + * @param[in] ctx YANG printer context. + * @return pointer to the printer options to allow modifying them with @ref schemaprinterflags values. + */ +LIBYANG_API_DECL uint32_t *lyplg_ext_print_get_options(const struct lyspr_ctx *ctx); + +/** + * @brief YANG printer context getter for printer indentation level. + * + * @param[in] ctx YANG printer context. + * @return pointer to the printer's indentation level to allow modifying its value. + */ +LIBYANG_API_DECL uint16_t *lyplg_ext_print_get_level(const struct lyspr_ctx *ctx); + +/** + * @brief Print substatements of an extension instance in info format (compiled YANG). + * + * Generic function to access YANG printer functions from the extension plugins (::lyplg_ext_sprinter_info_clb). + * + * @param[in] ctx YANG printer context to provide output handler and other information for printing. + * @param[in] ext The compiled extension instance to access the extensions and substatements data. + * @param[in,out] flag Flag to be shared with the caller regarding the opening brackets - 0 if the '{' not yet printed, + * 1 otherwise. + */ +LIBYANG_API_DECL void lyplg_ext_print_info_extension_instance(struct lyspr_ctx *ctx, const struct lysc_ext_instance *ext, + ly_bool *flag); + +/** @} pluginsExtensionsSprinterInfo */ + +/** + * @defgroup pluginsExtensionsSprinterTree Plugins: Extensions schema parsed and compiled tree printer support + * @ingroup pluginsExtensions + * + * Implementing extension plugin schema parsed and compiled tree printer callback. + * + * @{ + */ + +/** + * @brief Callback to print parent node of @p ext or to print the contents of the extension. + * + * Function is called in two different cases. If the printer_tree needs the tree-diagram form of a parent node, + * then @p ctx is set to NULL. In the second case, if printer_tree needs to print the contents of the extension, + * then @p ctx is set and function must prepare the nodes that should be printed using the + * lyplg_ext_sprinter_tree* functions. + * + * @param[in] ext Extension instance. + * @param[in,out] ctx Context for the tree printer. Extension contents can be inserted into it by functions + * lyplg_ext_sprinter_ctree_add_ext_nodes(), lyplg_ext_sprinter_ctree_add_nodes() or by their ptree alternatives. + * It parameter is set to NULL, then @p flags and @p add_opts are used by printer_tree. + * @param[out] flags Optional override tree-diagram \ in a parent node. If @p ctx is set, ignore this parameter. + * @param[out] add_opts Additional tree-diagram \ string in a parent node which is printed before \. If @p ctx + * is set, ignore this parameter. + * @return LY_ERR value. + */ +typedef LY_ERR (*lyplg_ext_sprinter_ctree_clb)(struct lysc_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **flags, const char **add_opts); + +/** + * @brief Callback for rewriting the tree-diagram form of a specific node. + * + * If this callback is set, then it is called for each node that belongs to the extension instance. + * + * @param[in] node Node whose tree-diagram form can be modified by the function. + * @param[in,out] plugin_priv Private context set by plugin. + * @param[out] skip Flag set to 1 removes the node from printed diagram. + * @param[out] flags Override tree-diagram \ string in the @p node. + * @param[out] add_opts Additional tree-diagram \ string in the @p node which is printed before \. + * @return LY_ERR value. + */ +typedef LY_ERR (*lyplg_ext_sprinter_ctree_override_clb)(const struct lysc_node *node, const void *plugin_priv, + ly_bool *skip, const char **flags, const char **add_opts); + +/** + * @brief Registration of printing a group of nodes, which is already in the extension. + * + * @param[in] ctx Context of printer_tree in which the group of nodes is saved and later printed. + * @param[in] ext Extension in which the group of nodes will be searched. + * @param[in] clb Override function that will be applied to each delivered node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_sprinter_ctree_add_ext_nodes(const struct lyspr_tree_ctx *ctx, + struct lysc_ext_instance *ext, lyplg_ext_sprinter_ctree_override_clb clb); + +/** + * @brief Registration of printing the group of nodes which were defined in the plugin. + * + * @param[in] ctx Context of printer_tree in which the group of nodes is saved and later printed. + * @param[in] nodes Points to the first node in group. + * @param[in] clb Override function that will be applied to each delivered node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_sprinter_ctree_add_nodes(const struct lyspr_tree_ctx *ctx, struct lysc_node *nodes, + lyplg_ext_sprinter_ctree_override_clb clb); + +/** + * @brief Registration of plugin-private data defined by the plugin that is shared between override_clb calls. + * + * @param[in] ctx Context of printer_tree in which plugin-private data will be saved. + * @param[in] plugin_priv Plugin-private data shared between oberride_clb calls. + * @param[in] free_clb Release function for @p plugin_priv. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_sprinter_tree_set_priv(const struct lyspr_tree_ctx *ctx, void *plugin_priv, + void (*free_clb)(void *plugin_priv)); + +/** + * @copydoc lyplg_ext_sprinter_ctree_clb + */ +typedef LY_ERR (*lyplg_ext_sprinter_ptree_clb)(struct lysp_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **flags, const char **add_opts); + +/** + * @copydoc lyplg_ext_sprinter_ctree_override_clb + */ +typedef LY_ERR (*lyplg_ext_sprinter_ptree_override_clb)(const struct lysp_node *node, const void *plugin_priv, + ly_bool *skip, const char **flags, const char **add_opts); + +/** + * @copydoc lyplg_ext_sprinter_ctree_add_ext_nodes + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_sprinter_ptree_add_ext_nodes(const struct lyspr_tree_ctx *ctx, + struct lysp_ext_instance *ext, lyplg_ext_sprinter_ptree_override_clb clb); + +/** + * @copydoc lyplg_ext_sprinter_ctree_add_nodes + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_sprinter_ptree_add_nodes(const struct lyspr_tree_ctx *ctx, struct lysp_node *nodes, + lyplg_ext_sprinter_ptree_override_clb clb); + +/** @} pluginsExtensionsSprinterTree */ + +/* + * data node + */ + +/** + * @brief Callback called for all data nodes connected to the extension instance. + * + * Can be used for additional data node validation. Is called only after the whole data tree is created and standard + * validation succeeds. Not called when parsing data and ::LYD_PARSE_ONLY is used. + * + * @param[in] ext Compiled extension instance. + * @param[in] node Data node to process. + * @param[in] validate_options Options used for the validation phase, see @ref datavalidationoptions. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +typedef LY_ERR (*lyplg_ext_data_node_clb)(struct lysc_ext_instance *ext, struct lyd_node *node, uint32_t validate_options); + +/* + * snode + */ + +/** + * @brief Callback for getting a schema node for a new YANG instance data described by an extension instance. + * Needed only if the extension instance supports some nested standard YANG data. + * + * @param[in] ext Compiled extension instance. + * @param[in] parent Parsed parent data node. Set if @p sparent is NULL. + * @param[in] sparent Schema parent node. Set if @p parent is NULL. + * @param[in] prefix Element prefix, if any. + * @param[in] prefix_len Length of @p prefix. + * @param[in] format Format of @p prefix. + * @param[in] prefix_data Format-specific prefix data. + * @param[in] name Element name. + * @param[in] name_len Length of @p name. + * @param[out] snode Schema node to use for parsing the node. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the data are not described by @p ext. + * @return LY_ERR on error. + */ +typedef LY_ERR (*lyplg_ext_data_snode_clb)(struct lysc_ext_instance *ext, const struct lyd_node *parent, + const struct lysc_node *sparent, const char *prefix, size_t prefix_len, LY_VALUE_FORMAT format, void *prefix_data, + const char *name, size_t name_len, const struct lysc_node **snode); + +/* + * validate + */ + +/** + * @brief Callback for validating parsed YANG instance data described by an extension instance. + * + * This callback is used only for nested data definition (with a standard YANG schema parent). + * + * @param[in] ext Compiled extension instance. + * @param[in] sibling First sibling with schema node returned by ::lyplg_ext_data_snode_clb. + * @param[in] dep_tree Tree to be used for validating references from the operation subtree, if operation. + * @param[in] data_type Validated data type, can be ::LYD_TYPE_DATA_YANG, ::LYD_TYPE_RPC_YANG, ::LYD_TYPE_NOTIF_YANG, + * or ::LYD_TYPE_REPLY_YANG. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[out] diff Optional diff with any changes made by the validation. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +typedef LY_ERR (*lyplg_ext_data_validate_clb)(struct lysc_ext_instance *ext, struct lyd_node *sibling, + const struct lyd_node *dep_tree, enum lyd_type data_type, uint32_t val_opts, struct lyd_node **diff); + +/* + * parse free + */ + +/** + * @brief Callback to free the extension-specific data created by its parsing. + * + * @param[in] ctx libyang context. + * @param[in,out] ext Parsed extension structure to free. + */ +typedef void (*lyplg_ext_parse_free_clb)(const struct ly_ctx *ctx, struct lysp_ext_instance *ext); + +/** + * @brief Free the extension instance's data parsed with ::lyplg_ext_parse_extension_instance(). + * + * @param[in] ctx libyang context + * @param[in] substmts Extension instance substatements to free. + */ +LIBYANG_API_DECL void lyplg_ext_pfree_instance_substatements(const struct ly_ctx *ctx, struct lysp_ext_substmt *substmts); + +/* + * compile free + */ + +/** + * @brief Callback to free the extension-specific data created by its compilation. + * + * @param[in] ctx libyang context. + * @param[in,out] ext Compiled extension structure to free. + */ +typedef void (*lyplg_ext_compile_free_clb)(const struct ly_ctx *ctx, struct lysc_ext_instance *ext); + +/** + * @brief Free the extension instance's data compiled with ::lyplg_ext_compile_extension_instance(). + * + * @param[in] ctx libyang context + * @param[in] substmts Extension instance substatements to free. + */ +LIBYANG_API_DECL void lyplg_ext_cfree_instance_substatements(const struct ly_ctx *ctx, struct lysc_ext_substmt *substmts); + +/** + * @brief Extension plugin implementing various aspects of a YANG extension + */ +struct lyplg_ext { + const char *id; /**< plugin identification (mainly for distinguish incompatible versions + of the plugins for external tools) */ + lyplg_ext_parse_clb parse; /**< callback to parse the extension instance substatements */ + lyplg_ext_compile_clb compile; /**< callback to compile extension instance from the parsed data */ + lyplg_ext_sprinter_info_clb printer_info; /**< callback to print the compiled content (info format) of the extension + instance */ + lyplg_ext_sprinter_ctree_clb printer_ctree; /**< callback to print tree format of compiled node containing the + compiled content of the extension instance */ + lyplg_ext_sprinter_ptree_clb printer_ptree; /**< callback to print tree format of parsed node containing the + parsed content of the extension instance */ + lyplg_ext_data_node_clb node; /**< callback to validate most relevant data instance for the extension + instance */ + lyplg_ext_data_snode_clb snode; /**< callback to get schema node for nested YANG data */ + lyplg_ext_data_validate_clb validate; /**< callback to validate parsed data instances according to the extension + definition */ + + lyplg_ext_parse_free_clb pfree; /**< free the extension-specific data created by its parsing */ + lyplg_ext_compile_free_clb cfree; /**< free the extension-specific data created by its compilation */ +}; + +struct lyplg_ext_record { + /* plugin identification */ + const char *module; /**< name of the module where the extension is defined */ + const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision, + which is not an optimal approach due to a possible future revisions of the module. + Instead, there should be defined multiple items in the plugins list, each with the + different revision, but all with the same pointer to the plugin functions. The + only valid use case for the NULL revision is the case the module has no revision. */ + const char *name; /**< YANG name of the extension */ + + /* runtime data */ + struct lyplg_ext plugin; /**< data to utilize plugin implementation */ +}; + +/** + * @brief Stringify statement identifier. + * + * @param[in] stmt The statement identifier to stringify. + * @return Constant string representation of the given @p stmt. + */ +LIBYANG_API_DECL const char *lyplg_ext_stmt2str(enum ly_stmt stmt); + +/** + * @brief Convert nodetype to statement identifier + * + * @param[in] nodetype Nodetype to convert. + * @return Statement identifier representing the given @p nodetype. + */ +LIBYANG_API_DECL enum ly_stmt lyplg_ext_nodetype2stmt(uint16_t nodetype); + +/** + * @brief Get compiled ext instance storage for a specific statement. + * + * @param[in] ext Compiled ext instance. + * @param[in] stmt Compiled statement. Can be a mask when the first match is returned, it is expected the storage is + * the same for all the masked statements. + * @param[in] storage_size Size of the value at @p storage address (dereferenced). + * @param[out] storage Compiled ext instance substatement storage, NULL if was not compiled. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the substatement is not supported. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_get_storage(const struct lysc_ext_instance *ext, int stmt, uint32_t storage_size, + const void **storage); + +/** + * @brief Get parsed ext instance storage for a specific statement. + * + * @param[in] ext Compiled ext instance. + * @param[in] stmt Parsed statement. Can be a mask when the first match is returned, it is expected the storage is + * the same for all the masked statements. + * @param[in] storage_size Size of the value at @p storage address (dereferenced). + * @param[out] storage Parsed ext instance substatement storage, NULL if was not parsed. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the substatement is not supported. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_parsed_get_storage(const struct lysc_ext_instance *ext, int stmt, + uint32_t storage_size, const void **storage); + +/** + * @brief Get specific run-time extension instance data from a callback set by ::ly_ctx_set_ext_data_clb(). + * + * @param[in] ctx Context with the callback. + * @param[in] ext Compiled extension instance. + * @param[out] ext_data Provided extension instance data. + * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_get_data(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, void **ext_data, + ly_bool *ext_data_free); + +/** + * @brief Insert extension instance data into a parent. + * + * @param[in] parent Parent node to insert into. + * @param[in] first First top-level sibling node to insert. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_insert(struct lyd_node *parent, struct lyd_node *first); + +/** + * @brief Expand parent-reference xpath expressions + * + * @param[in] ext Context allocated for extension. + * @param[out] refs Set of schema node matching parent-reference XPaths. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_schema_mount_get_parent_ref(const struct lysc_ext_instance *ext, struct ly_set **refs); + +/** + * @brief Allocate a new context for a particular instance of the yangmnt:mount-point extension. + * Caller is responsible for destroying the resulting context. + * + * @param[in] ext Compiled extension instance. + * @param[out] ctx A context with modules loaded from the list found in + * the extension data. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_ext_schema_mount_create_context(const struct lysc_ext_instance *ext, struct ly_ctx **ctx); + +/** @} pluginsExtensions */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PLUGINS_EXTS_H_ */ diff --git a/src/plugins_exts/metadata.c b/src/plugins_exts/metadata.c new file mode 100644 index 0000000..9567e07 --- /dev/null +++ b/src/plugins_exts/metadata.c @@ -0,0 +1,243 @@ +/** + * @file metadata.c + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang extension plugin - Metadata (RFC 7952) + * + * Copyright (c) 2019 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "metadata.h" + +#include +#include +#include + +#include "libyang.h" +#include "plugins_exts.h" + +struct lysp_ext_metadata { + struct lysp_type *type; /**< type of the metadata (mandatory) */ + const char *units; /**< units of the leaf's type */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_* values are allowed */ +}; + +struct lysc_ext_metadata { + struct lysc_type *type; /**< type of the metadata (mandatory) */ + const char *units; /**< units of the leaf's type */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_* values are allowed */ +}; + +/** + * @brief Parse annotation extension instances. + * + * Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse. + */ +static LY_ERR +annotation_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + LY_ERR r; + struct lysp_ext_metadata *ann_pdata; + struct lysp_module *pmod; + LY_ARRAY_COUNT_TYPE u; + + /* annotations can appear only at the top level of a YANG module or submodule */ + if ((ext->parent_stmt != LY_STMT_MODULE) && (ext->parent_stmt != LY_STMT_SUBMODULE)) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension %s is allowed only at the top level of a YANG module or " + "submodule, but it is placed in \"%s\" statement.", ext->name, lyplg_ext_stmt2str(ext->parent_stmt)); + return LY_EVALID; + } + + pmod = ext->parent; + + /* check for duplication */ + LY_ARRAY_FOR(pmod->exts, u) { + if ((&pmod->exts[u] != ext) && (pmod->exts[u].name == ext->name) && !strcmp(pmod->exts[u].argument, ext->argument)) { + /* duplication of the same annotation extension in a single module */ + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension %s is instantiated multiple times.", ext->name); + return LY_EVALID; + } + } + + /* parse annotation substatements */ + ext->parsed = ann_pdata = calloc(1, sizeof *ann_pdata); + if (!ann_pdata) { + goto emem; + } + LY_ARRAY_CREATE_GOTO(lyplg_ext_parse_get_cur_pmod(pctx)->mod->ctx, ext->substmts, 6, r, emem); + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_IF_FEATURE; + ext->substmts[0].storage = &ann_pdata->iffeatures; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_UNITS; + ext->substmts[1].storage = &ann_pdata->units; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_STATUS; + ext->substmts[2].storage = &ann_pdata->flags; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[3].stmt = LY_STMT_TYPE; + ext->substmts[3].storage = &ann_pdata->type; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[4].stmt = LY_STMT_DESCRIPTION; + ext->substmts[4].storage = &ann_pdata->dsc; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[5].stmt = LY_STMT_REFERENCE; + ext->substmts[5].storage = &ann_pdata->ref; + + if ((r = lyplg_ext_parse_extension_instance(pctx, ext))) { + return r; + } + + /* check for mandatory substatements */ + if (!ann_pdata->type) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Missing mandatory keyword \"type\" as a child of \"%s %s\".", + ext->name, ext->argument); + return LY_EVALID; + } + + return LY_SUCCESS; + +emem: + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +/** + * @brief Compile annotation extension instances. + * + * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile. + */ +static LY_ERR +annotation_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *extp, struct lysc_ext_instance *ext) +{ + LY_ERR ret; + struct lysc_ext_metadata *ann_cdata; + + /* compile annotation substatements */ + ext->compiled = ann_cdata = calloc(1, sizeof *ann_cdata); + if (!ann_cdata) { + goto emem; + } + LY_ARRAY_CREATE_GOTO(lysc_ctx_get_ctx(cctx), ext->substmts, 6, ret, emem); + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_IF_FEATURE; + ext->substmts[0].storage = NULL; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_UNITS; + ext->substmts[1].storage = &ann_cdata->units; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_STATUS; + ext->substmts[2].storage = &ann_cdata->flags; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[3].stmt = LY_STMT_TYPE; + ext->substmts[3].storage = &ann_cdata->type; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[4].stmt = LY_STMT_DESCRIPTION; + ext->substmts[4].storage = &ann_cdata->dsc; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[5].stmt = LY_STMT_REFERENCE; + ext->substmts[5].storage = &ann_cdata->ref; + + ret = lyplg_ext_compile_extension_instance(cctx, extp, ext); + return ret; + +emem: + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +/** + * @brief INFO printer + * + * Implementation of ::lyplg_ext_sprinter_info_clb set as ::lyext_plugin::printer_info + */ +static LY_ERR +annotation_printer_info(struct lyspr_ctx *ctx, struct lysc_ext_instance *ext, ly_bool *flag) +{ + lyplg_ext_print_info_extension_instance(ctx, ext, flag); + + return LY_SUCCESS; +} + +/** + * @brief Free parsed annotation extension instance data. + * + * Implementation of ::lyplg_ext_parse_free_clb callback set as ::lyext_plugin::pfree. + */ +static void +annotation_pfree(const struct ly_ctx *ctx, struct lysp_ext_instance *ext) +{ + if (!ext->substmts) { + return; + } + + lyplg_ext_pfree_instance_substatements(ctx, ext->substmts); + free(ext->parsed); +} + +/** + * @brief Free compiled annotation extension instance data. + * + * Implementation of ::lyplg_ext_compile_free_clb callback set as ::lyext_plugin::cfree. + */ +static void +annotation_cfree(const struct ly_ctx *ctx, struct lysc_ext_instance *ext) +{ + if (!ext->substmts) { + return; + } + + lyplg_ext_cfree_instance_substatements(ctx, ext->substmts); + free(ext->compiled); +} + +/** + * @brief Plugin descriptions for the Metadata's annotation extension + * + * Note that external plugins are supposed to use: + * + * LYPLG_EXTENSIONS = { + */ +const struct lyplg_ext_record plugins_metadata[] = { + { + .module = "ietf-yang-metadata", + .revision = "2016-08-05", + .name = "annotation", + + .plugin.id = "ly2 metadata v1", + .plugin.parse = annotation_parse, + .plugin.compile = annotation_compile, + .plugin.printer_info = annotation_printer_info, + .plugin.printer_ctree = NULL, + .plugin.printer_ptree = NULL, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = annotation_pfree, + .plugin.cfree = annotation_cfree, + }, + {0} /* terminating zeroed record */ +}; diff --git a/src/plugins_exts/metadata.h b/src/plugins_exts/metadata.h new file mode 100644 index 0000000..59ea2bf --- /dev/null +++ b/src/plugins_exts/metadata.h @@ -0,0 +1,66 @@ +/** + * @file metadata.h + * @author Radek Krejci + * @author Michal Vasko + * @brief ietf-yang-metadata API + * + * Copyright (c) 2019 - 2022 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_PLUGINS_EXTS_METADATA_H_ +#define LY_PLUGINS_EXTS_METADATA_H_ + +#include "plugins_exts.h" +#include "tree_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Metadata structure. + * + * The structure provides information about metadata of a data element. Such attributes must map to + * annotations as specified in RFC 7952. The only exception is the filter type (in NETCONF get operations) + * and edit-config's operation attributes. In XML, they are represented as standard XML attributes. In JSON, + * they are represented as JSON elements starting with the '@' character (for more information, see the + * YANG metadata RFC. + * + */ +struct lyd_meta { + struct lyd_node *parent; /**< data node where the metadata is placed */ + struct lyd_meta *next; /**< pointer to the next metadata of the same element */ + struct lysc_ext_instance *annotation; /**< pointer to the annotation's definition */ + const char *name; /**< metadata name */ + struct lyd_value value; /**< metadata value representation */ +}; + +/** + * @brief Get the (canonical) value of a metadata node. + * + * @param[in] meta Metadata node to use. + * @return Canonical value. + */ +static inline const char * +lyd_get_meta_value(const struct lyd_meta *meta) +{ + if (meta) { + const struct lyd_value *value = &meta->value; + + return value->_canonical ? value->_canonical : lyd_value_get_canonical(meta->annotation->module->ctx, value); + } + + return NULL; +} + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PLUGINS_EXTS_METADATA_H_ */ diff --git a/src/plugins_exts/nacm.c b/src/plugins_exts/nacm.c new file mode 100644 index 0000000..5ab8daa --- /dev/null +++ b/src/plugins_exts/nacm.c @@ -0,0 +1,223 @@ +/** + * @file nacm.c + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang extension plugin - NACM (RFC 6536) + * + * Copyright (c) 2019 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include + +#include "compat.h" +#include "libyang.h" +#include "plugins_exts.h" + +struct nacm_dfs_arg { + struct lysc_ext_instance *ext; + struct lysc_node *parent; +}; + +/** + * @brief DFS callback implementation for inheriting the NACM extension. + */ +static LY_ERR +nacm_inherit_clb(struct lysc_node *node, void *data, ly_bool *dfs_continue) +{ + LY_ERR ret; + struct nacm_dfs_arg *arg = data; + struct lysc_ext_instance *inherited; + LY_ARRAY_COUNT_TYPE u; + + /* ignore the parent from which we inherit and input/output nodes */ + if ((node != arg->parent) && !(node->nodetype & (LYS_INPUT | LYS_OUTPUT))) { + /* check that the node does not have its own NACM extension instance */ + LY_ARRAY_FOR(node->exts, u) { + if (node->exts[u].def == arg->ext->def) { + /* the child already have its own NACM flag, so skip the subtree */ + *dfs_continue = 1; + return LY_SUCCESS; + } + } + + /* duplicate this one to inherit it to the child */ + LY_ARRAY_NEW_GOTO(node->module->ctx, node->exts, inherited, ret, emem); + + inherited->def = arg->ext->def; + inherited->parent = node; + inherited->parent_stmt = lyplg_ext_nodetype2stmt(node->nodetype); + if (arg->ext->argument) { + if ((ret = lydict_insert(node->module->ctx, arg->ext->argument, 0, &inherited->argument))) { + return ret; + } + } + /* copy the pointer to the static variables */ + inherited->compiled = arg->ext->compiled; + } + + return LY_SUCCESS; + +emem: + lyplg_ext_compile_log(NULL, arg->ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return ret; +} + +/** + * @brief Parse NACM extension instances. + * + * Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse. + */ +static LY_ERR +nacm_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + struct lysp_node *parent = NULL; + LY_ARRAY_COUNT_TYPE u; + + /* check that the extension is instantiated at an allowed place - data node */ + if (!(ext->parent_stmt & LY_STMT_NODE_MASK)) { + lyplg_ext_parse_log(pctx, ext, LY_LLWRN, 0, "Extension %s is allowed only in a data nodes, but it is placed in " + "\"%s\" statement.", ext->name, lyplg_ext_stmt2str(ext->parent_stmt)); + return LY_ENOT; + } + + parent = ext->parent; + if (!(parent->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_CHOICE | LYS_ANYDATA | + LYS_CASE | LYS_RPC | LYS_ACTION | LYS_NOTIF)) || (!strcmp(strchr(ext->name, ':') + 1, "default-deny-write") && + (parent->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)))) { + /* note LYS_AUGMENT and LYS_USES is not in the list since they are not present in the compiled tree. Instead, libyang + * passes all their extensions to their children nodes */ + lyplg_ext_parse_log(pctx, ext, LY_LLWRN, 0, "Extension %s is not allowed in %s statement.", ext->name, + lys_nodetype2str(parent->nodetype)); + return LY_ENOT; + } + + /* check for duplication */ + LY_ARRAY_FOR(parent->exts, u) { + if ((&parent->exts[u] != ext) && parent->exts[u].record && (parent->exts[u].record->plugin.id == ext->record->plugin.id)) { + /* duplication of a NACM extension on a single node + * We check for all NACM plugins since we want to catch even the situation that there is default-deny-all + * AND default-deny-write */ + if (parent->exts[u].name == ext->name) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension %s is instantiated multiple times.", ext->name); + } else { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, + "Extension nacm:default-deny-write is mixed with nacm:default-deny-all."); + } + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Compile NACM extension instances. + * + * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile. + */ +static LY_ERR +nacm_compile(struct lysc_ctx *UNUSED(cctx), const struct lysp_ext_instance *UNUSED(extp), struct lysc_ext_instance *ext) +{ + struct nacm_dfs_arg dfs_arg; + + static const uint8_t nacm_deny_all = 1; + static const uint8_t nacm_deny_write = 2; + + /* store the NACM flag */ + if (!strcmp(ext->def->name, "default-deny-write")) { + ext->compiled = (void *)&nacm_deny_write; + } else if (!strcmp(ext->def->name, "default-deny-all")) { + ext->compiled = (void *)&nacm_deny_all; + } else { + return LY_EINT; + } + + /* inherit the extension instance to all the children nodes */ + dfs_arg.ext = ext; + dfs_arg.parent = ext->parent; + return lysc_tree_dfs_full(ext->parent, nacm_inherit_clb, &dfs_arg); +} + +/** + * @brief Plugin descriptions for the NACM's default-deny-write and default-deny-all extensions + * + * Note that external plugins are supposed to use: + * + * LYPLG_EXTENSIONS = { + */ +const struct lyplg_ext_record plugins_nacm[] = { + { + .module = "ietf-netconf-acm", + .revision = "2012-02-22", + .name = "default-deny-write", + + .plugin.id = "ly2 NACM v1", + .plugin.parse = nacm_parse, + .plugin.compile = nacm_compile, + .plugin.printer_info = NULL, + .plugin.printer_ctree = NULL, + .plugin.printer_ptree = NULL, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = NULL, + .plugin.cfree = NULL + }, { + .module = "ietf-netconf-acm", + .revision = "2018-02-14", + .name = "default-deny-write", + + .plugin.id = "ly2 NACM v1", + .plugin.parse = nacm_parse, + .plugin.compile = nacm_compile, + .plugin.printer_info = NULL, + .plugin.printer_ctree = NULL, + .plugin.printer_ptree = NULL, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = NULL, + .plugin.cfree = NULL + }, { + .module = "ietf-netconf-acm", + .revision = "2012-02-22", + .name = "default-deny-all", + + .plugin.id = "ly2 NACM v1", + .plugin.parse = nacm_parse, + .plugin.compile = nacm_compile, + .plugin.printer_info = NULL, + .plugin.printer_ctree = NULL, + .plugin.printer_ptree = NULL, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = NULL, + .plugin.cfree = NULL + }, { + .module = "ietf-netconf-acm", + .revision = "2018-02-14", + .name = "default-deny-all", + + .plugin.id = "ly2 NACM v1", + .plugin.parse = nacm_parse, + .plugin.compile = nacm_compile, + .plugin.printer_info = NULL, + .plugin.printer_ctree = NULL, + .plugin.printer_ptree = NULL, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = NULL, + .plugin.cfree = NULL + }, + {0} /* terminating zeroed item */ +}; diff --git a/src/plugins_exts/schema_mount.c b/src/plugins_exts/schema_mount.c new file mode 100644 index 0000000..9800760 --- /dev/null +++ b/src/plugins_exts/schema_mount.c @@ -0,0 +1,1332 @@ +/** + * @file schema_mount.c + * @author Tadeas Vintrlik + * @brief libyang extension plugin - Schema Mount (RFC 8528) + * + * Copyright (c) 2021 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 + +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "dict.h" +#include "libyang.h" +#include "log.h" +#include "parser_data.h" +#include "plugins_exts.h" +#include "plugins_types.h" +#include "tree_data.h" +#include "tree_schema.h" + +/** + * @brief Internal schema mount data structure for holding all the contexts of parsed data. + */ +struct lyplg_ext_sm { + pthread_mutex_t lock; /**< lock for accessing this shared structure */ + + struct lyplg_ext_sm_shared { + struct { + struct ly_ctx *ctx; /**< context shared between all data of this mount point */ + const char *mount_point; /**< mount point name */ + const char *content_id; /**< yang-library content-id (alternatively module-set-id), + stored in the dictionary of the ext instance context */ + } *schemas; /**< array of shared schema schemas */ + uint32_t schema_count; /**< length of schemas array */ + uint32_t ref_count; /**< number of references to this structure (mount-points with the same name + in the module) */ + } *shared; /**< shared schema mount points */ + + struct lyplg_ext_sm_inln { + struct { + struct ly_ctx *ctx; /**< context created for inline schema data, may be reused if possible */ + } *schemas; /**< array of inline schemas */ + uint32_t schema_count; /**< length of schemas array */ + } inln; /**< inline mount points */ +}; + +struct sprinter_tree_priv { + struct ly_ctx *ext_ctx; + struct ly_set *refs; +}; + +#define EXT_LOGERR_MEM_RET(cctx, ext) \ + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s:%d).", __FILE__, __LINE__); \ + return LY_EMEM + +#define EXT_LOGERR_MEM_GOTO(cctx, ext, rc, label) \ + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s:%d).", __FILE__, __LINE__); \ + rc = LY_EMEM; \ + goto label + +#define EXT_LOGERR_INT_RET(cctx, ext) \ + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EINT, "Internal error (%s:%d).", __FILE__, __LINE__); \ + return LY_EINT + +/** + * @brief Check if given mount point is unique among its siblings + * + * @param[in] pctx Parse context. + * @param[in] ext Parsed extension instance. + * @return LY_SUCCESS if is unique; + * @return LY_EINVAL otherwise. + */ +static LY_ERR +schema_mount_parse_unique_mp(struct lysp_ctx *pctx, const struct lysp_ext_instance *ext) +{ + struct lysp_ext_instance *exts; + LY_ARRAY_COUNT_TYPE u; + struct lysp_node *parent; + + /* check if it is the only instance of the mount-point among its siblings */ + parent = ext->parent; + exts = parent->exts; + LY_ARRAY_FOR(exts, u) { + if (&exts[u] == ext) { + continue; + } + + if (!strcmp(exts[u].name, ext->name)) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Multiple extension \"%s\" instances.", ext->name); + return LY_EINVAL; + } + } + return LY_SUCCESS; +} + +/** + * @brief Schema mount parse. + * Checks if it can be a valid extension instance for yang schema mount. + * + * Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse. + */ +static LY_ERR +schema_mount_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + /* check YANG version 1.1 */ + if (lyplg_ext_parse_get_cur_pmod(pctx)->version != LYS_VERSION_1_1) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension \"%s\" instance not allowed in YANG version 1 module.", + ext->name); + return LY_EINVAL; + } + + /* check parent nodetype */ + if ((ext->parent_stmt != LY_STMT_CONTAINER) && (ext->parent_stmt != LY_STMT_LIST)) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension \"%s\" instance allowed only in container or list statement.", + ext->name); + return LY_EINVAL; + } + + /* check uniqueness */ + if (schema_mount_parse_unique_mp(pctx, ext)) { + return LY_EINVAL; + } + + /* nothing to actually parse */ + return LY_SUCCESS; +} + +struct lyplg_ext_sm_shared_cb_data { + const struct lysc_ext_instance *ext; + struct lyplg_ext_sm_shared *sm_shared; +}; + +static LY_ERR +schema_mount_compile_mod_dfs_cb(struct lysc_node *node, void *data, ly_bool *UNUSED(dfs_continue)) +{ + struct lyplg_ext_sm_shared_cb_data *cb_data = data; + struct lyplg_ext_sm *sm_data; + struct lysc_ext_instance *exts; + LY_ARRAY_COUNT_TYPE u; + + if (node == cb_data->ext->parent) { + /* parent of the current compiled extension, skip */ + return LY_SUCCESS; + } + + /* find the same mount point */ + exts = node->exts; + LY_ARRAY_FOR(exts, u) { + if (!strcmp(exts[u].def->module->name, "ietf-yang-schema-mount") && !strcmp(exts[u].def->name, "mount-point") && + (exts[u].argument == cb_data->ext->argument)) { + /* same mount point, break the DFS search */ + sm_data = exts[u].compiled; + cb_data->sm_shared = sm_data->shared; + return LY_EEXIST; + } + } + + /* not found, continue search */ + return LY_SUCCESS; +} + +static struct lyplg_ext_sm_shared * +schema_mount_compile_find_shared(const struct lys_module *mod, const struct lysc_ext_instance *ext) +{ + struct lyplg_ext_sm_shared_cb_data cb_data; + LY_ERR r; + + /* prepare cb_data */ + cb_data.ext = ext; + cb_data.sm_shared = NULL; + + /* try to find the same mount point */ + r = lysc_module_dfs_full(mod, schema_mount_compile_mod_dfs_cb, &cb_data); + (void)r; + assert((!r && !cb_data.sm_shared) || ((r == LY_EEXIST) && cb_data.sm_shared)); + + return cb_data.sm_shared; +} + +/** + * @brief Schema mount compile. + * Checks if it can be a valid extension instance for yang schema mount. + * + * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile. + */ +static LY_ERR +schema_mount_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *UNUSED(extp), struct lysc_ext_instance *ext) +{ + const struct lysc_node *node; + struct lyplg_ext_sm *sm_data; + + /* init internal data */ + sm_data = calloc(1, sizeof *sm_data); + if (!sm_data) { + EXT_LOGERR_MEM_RET(cctx, ext); + } + pthread_mutex_init(&sm_data->lock, NULL); + ext->compiled = sm_data; + + /* find the owner module */ + node = ext->parent; + while (node->parent) { + node = node->parent; + } + + /* reuse/init shared schema */ + sm_data->shared = schema_mount_compile_find_shared(node->module, ext); + if (sm_data->shared) { + ++sm_data->shared->ref_count; + } else { + sm_data->shared = calloc(1, sizeof *sm_data->shared); + if (!sm_data->shared) { + free(sm_data); + EXT_LOGERR_MEM_RET(cctx, ext); + } + sm_data->shared->ref_count = 1; + } + + return LY_SUCCESS; +} + +/** + * @brief Learn details about the current mount point. + * + * @param[in] ext Compiled extension instance. + * @param[in] ext_data Extension data retrieved by the callback. + * @param[out] config Whether the whole schema should keep its config or be set to false. + * @param[out] shared Optional flag whether the schema is shared or inline. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_get_smount(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool *config, + ly_bool *shared) +{ + struct lyd_node *mpoint, *node; + char *path = NULL; + LY_ERR r; + + /* find the mount point */ + if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']", ext->module->name, + ext->argument) == -1) { + EXT_LOGERR_MEM_RET(NULL, ext); + } + r = ext_data ? lyd_find_path(ext_data, path, 0, &mpoint) : LY_ENOTFOUND; + free(path); + if (r) { + /* missing mount-point, cannot be data for this extension (https://datatracker.ietf.org/doc/html/rfc8528#page-10) */ + return LY_ENOT; + } + + /* check config */ + *config = 1; + if (!lyd_find_path(mpoint, "config", 0, &node) && !strcmp(lyd_get_value(node), "false")) { + *config = 0; + } + assert((ext->parent_stmt == LY_STMT_CONTAINER) || (ext->parent_stmt == LY_STMT_LIST)); + if (((struct lysc_node *)ext->parent)->flags & LYS_CONFIG_R) { + *config = 0; + } + + if (shared) { + /* check schema-ref */ + if (lyd_find_path(mpoint, "shared-schema", 0, NULL)) { + if (lyd_find_path(mpoint, "inline", 0, NULL)) { + EXT_LOGERR_INT_RET(NULL, ext); + } + *shared = 0; + } else { + *shared = 1; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Create schema (context) based on retrieved extension data. + * + * @param[in] ext Compiled extension instance. + * @param[in] ext_data Extension data retrieved by the callback. + * @param[in] config Whether the whole schema should keep its config or be set to false. + * @param[out] ext_ctx Schema to use for parsing the data. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_create_ctx(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config, + struct ly_ctx **ext_ctx) +{ + LY_ERR rc = LY_SUCCESS; + const char * const *searchdirs; + char *sdirs = NULL; + const struct lys_module *mod; + struct lysc_node *root, *node; + uint32_t i, idx = 0; + + /* get searchdirs from the current context */ + searchdirs = ly_ctx_get_searchdirs(ext->module->ctx); + + if (searchdirs) { + /* append them all into a single string */ + for (i = 0; searchdirs[i]; ++i) { + if ((rc = ly_strcat(&sdirs, "%s" PATH_SEPARATOR, searchdirs[i]))) { + goto cleanup; + } + } + } + + /* create the context based on the data */ + if ((rc = ly_ctx_new_yldata(sdirs, ext_data, ly_ctx_get_options(ext->module->ctx), ext_ctx))) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, rc, "Failed to create context for the schema-mount data."); + goto cleanup; + } + + if (!config) { + /* manually change the config of all schema nodes in all the modules */ + while ((mod = ly_ctx_get_module_iter(*ext_ctx, &idx))) { + if (!mod->implemented) { + continue; + } + + LY_LIST_FOR(mod->compiled->data, root) { + LYSC_TREE_DFS_BEGIN(root, node) { + node->flags &= ~LYS_CONFIG_W; + node->flags |= LYS_CONFIG_R; + + LYSC_TREE_DFS_END(root, node); + } + } + } + } + +cleanup: + free(sdirs); + return rc; +} + +/** + * @brief Get ietf-yang-library context-id from its data. + * + * @param[in] ext Compiled extension instance for logging. + * @param[in] ext_data Extension data retrieved by the callback with the yang-library data. + * @param[out] content_id Content ID in @p ext_data. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_get_content_id(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, const char **content_id) +{ + struct lyd_node *node = NULL; + + *content_id = NULL; + + /* get yang-library content-id or module-set-id */ + if (ext_data) { + lyd_find_path(ext_data, "/ietf-yang-library:yang-library/content-id", 0, &node); + if (!node) { + lyd_find_path(ext_data, "/ietf-yang-library:modules-state/module-set-id", 0, &node); + } + if (node) { + *content_id = lyd_get_value(node); + } + } + + if (!*content_id) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_EVALID, + "Missing \"content-id\" or \"module-set-id\" in ietf-yang-library data."); + return LY_EVALID; + } + return LY_SUCCESS; +} + +/** + * @brief Get schema (context) for a shared-schema mount point. + * + * @param[in] ext Compiled extension instance. + * @param[in] ext_data Extension data retrieved by the callback. + * @param[in] config Whether the whole schema should keep its config or be set to false. + * @param[out] ext_ctx Schema to use for parsing the data. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_get_ctx_shared(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config, + const struct ly_ctx **ext_ctx) +{ + struct lyplg_ext_sm *sm_data = ext->compiled; + LY_ERR rc = LY_SUCCESS, r; + struct ly_ctx *new_ctx = NULL; + uint32_t i; + const char *content_id; + void *mem; + + assert(sm_data && sm_data->shared); + + /* get yang-library content-id or module-set-id */ + if ((r = schema_mount_get_content_id(ext, ext_data, &content_id))) { + return r; + } + + /* LOCK */ + if ((r = pthread_mutex_lock(&sm_data->lock))) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_ESYS, "Mutex lock failed (%s).", strerror(r)); + return LY_ESYS; + } + + /* try to find this mount point */ + for (i = 0; i < sm_data->shared->schema_count; ++i) { + if (ext->argument == sm_data->shared->schemas[i].mount_point) { + break; + } + } + + if (i < sm_data->shared->schema_count) { + /* schema exists already */ + if (strcmp(content_id, sm_data->shared->schemas[i].content_id)) { + lyplg_ext_compile_log_path("/ietf-yang-library:yang-library/content-id", ext, LY_LLERR, LY_EVALID, + "Shared-schema yang-library content-id \"%s\" differs from \"%s\" used previously.", + content_id, sm_data->shared->schemas[i].content_id); + rc = LY_EVALID; + goto cleanup; + } + } else { + /* no schema found, create it */ + if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) { + rc = r; + goto cleanup; + } + + /* new entry */ + mem = realloc(sm_data->shared->schemas, (i + 1) * sizeof *sm_data->shared->schemas); + if (!mem) { + ly_ctx_destroy(new_ctx); + EXT_LOGERR_MEM_GOTO(NULL, ext, rc, cleanup); + } + sm_data->shared->schemas = mem; + ++sm_data->shared->schema_count; + + /* fill entry */ + sm_data->shared->schemas[i].ctx = new_ctx; + sm_data->shared->schemas[i].mount_point = ext->argument; + lydict_insert(ext->module->ctx, content_id, 0, &sm_data->shared->schemas[i].content_id); + } + + /* use the context */ + *ext_ctx = sm_data->shared->schemas[i].ctx; + +cleanup: + /* UNLOCK */ + pthread_mutex_unlock(&sm_data->lock); + + return rc; +} + +/** + * @brief Check whether ietf-yang-library data describe an existing context meaning whether it includes + * at least exactly all the mentioned modules. + * + * @param[in] ext Compiled extension instance for logging. + * @param[in] ext_data Extension data retrieved by the callback with the yang-library data. + * @param[in] ctx Context to consider. + * @return LY_SUCCESS if the context matches. + * @return LY_ENOT if the context differs. + * @return LY_ERR on error. + */ +static LY_ERR +schema_mount_ctx_match(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, const struct ly_ctx *ctx) +{ + struct ly_set *impl_mods = NULL, *imp_mods = NULL; + struct lyd_node *node; + const struct lys_module *mod; + const char *name, *revision; + LY_ERR rc = LY_ENOT, r; + uint32_t i; + + /* collect all the implemented and imported modules, we do not really care about content-id */ + if (!lyd_find_path(ext_data, "/ietf-yang-library:yang-library/content-id", 0, NULL)) { + if ((r = lyd_find_xpath(ext_data, "/ietf-yang-library:yang-library/module-set[1]/module", &impl_mods))) { + rc = r; + goto cleanup; + } + if ((r = lyd_find_xpath(ext_data, "/ietf-yang-library:yang-library/module-set[1]/import-only-module", &imp_mods))) { + rc = r; + goto cleanup; + } + } else { + if ((r = lyd_find_xpath(ext_data, "/ietf-yang-library:modules-state/module[conformance-type='implement']", &impl_mods))) { + rc = r; + goto cleanup; + } + if ((r = lyd_find_xpath(ext_data, "/ietf-yang-library:modules-state/module[conformance-type='import']", &imp_mods))) { + rc = r; + goto cleanup; + } + } + + if (!impl_mods->count) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_EVALID, "No implemented modules included in ietf-yang-library data."); + rc = LY_EVALID; + goto cleanup; + } + + /* check all the implemented modules */ + for (i = 0; i < impl_mods->count; ++i) { + lyd_find_path(impl_mods->dnodes[i], "name", 0, &node); + name = lyd_get_value(node); + + lyd_find_path(impl_mods->dnodes[i], "revision", 0, &node); + if (node && strlen(lyd_get_value(node))) { + revision = lyd_get_value(node); + } else { + revision = NULL; + } + + if (!(mod = ly_ctx_get_module(ctx, name, revision)) || !mod->implemented) { + /* unsuitable module */ + goto cleanup; + } + } + + /* check all the imported modules */ + for (i = 0; i < imp_mods->count; ++i) { + lyd_find_path(imp_mods->dnodes[i], "name", 0, &node); + name = lyd_get_value(node); + + lyd_find_path(imp_mods->dnodes[i], "revision", 0, &node); + if (node && strlen(lyd_get_value(node))) { + revision = lyd_get_value(node); + } else { + revision = NULL; + } + + if (!ly_ctx_get_module(ctx, name, revision)) { + /* unsuitable module */ + goto cleanup; + } + } + + /* context matches and can be reused */ + rc = LY_SUCCESS; + +cleanup: + ly_set_free(impl_mods, NULL); + ly_set_free(imp_mods, NULL); + return rc; +} + +/** + * @brief Get schema (context) for an inline mount point. + * + * @param[in] ext Compiled extension instance. + * @param[in] ext_data Extension data retrieved by the callback. + * @param[in] config Whether the whole schema should keep its config or be set to false. + * @param[out] ext_ctx Schema to use for parsing the data. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_get_ctx_inline(struct lysc_ext_instance *ext, const struct lyd_node *ext_data, ly_bool config, + const struct ly_ctx **ext_ctx) +{ + struct lyplg_ext_sm *sm_data = ext->compiled; + struct ly_ctx *new_ctx = NULL; + uint32_t i; + void *mem; + LY_ERR rc = LY_SUCCESS, r; + + assert(sm_data && sm_data->shared); + + /* LOCK */ + if ((r = pthread_mutex_lock(&sm_data->lock))) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_ESYS, "Mutex lock failed (%s).", strerror(r)); + return LY_ESYS; + } + + /* try to find a context we can reuse */ + for (i = 0; i < sm_data->inln.schema_count; ++i) { + r = schema_mount_ctx_match(ext, ext_data, sm_data->inln.schemas[i].ctx); + if (!r) { + /* match */ + *ext_ctx = sm_data->inln.schemas[i].ctx; + goto cleanup; + } else if (r != LY_ENOT) { + /* error */ + rc = r; + goto cleanup; + } + } + + /* new schema required, create context */ + if ((r = schema_mount_create_ctx(ext, ext_data, config, &new_ctx))) { + rc = r; + goto cleanup; + } + + /* new entry */ + mem = realloc(sm_data->inln.schemas, (i + 1) * sizeof *sm_data->inln.schemas); + if (!mem) { + ly_ctx_destroy(new_ctx); + EXT_LOGERR_MEM_GOTO(NULL, ext, rc, cleanup); + } + sm_data->inln.schemas = mem; + ++sm_data->inln.schema_count; + + /* fill entry */ + sm_data->inln.schemas[i].ctx = new_ctx; + + /* use the context */ + *ext_ctx = sm_data->inln.schemas[i].ctx; + +cleanup: + /* UNLOCK */ + pthread_mutex_unlock(&sm_data->lock); + + return rc; +} + +/** + * @brief Get schema (context) for a mount point. + * + * @param[in] ext Compiled extension instance. + * @param[out] ext_ctx Schema to use for parsing the data. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_get_ctx(struct lysc_ext_instance *ext, const struct ly_ctx **ext_ctx) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lyd_node *iter, *ext_data = NULL; + ly_bool ext_data_free = 0, config, shared; + + *ext_ctx = NULL; + + /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */ + if ((r = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) { + ret = r; + goto cleanup; + } + + LY_LIST_FOR(ext_data, iter) { + if (iter->flags & LYD_NEW) { + /* must be validated for the parent-reference prefix data to be stored */ + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_EINVAL, "Provided ext data have not been validated."); + ret = LY_EINVAL; + goto cleanup; + } + } + + /* learn about this mount point */ + if ((r = schema_mount_get_smount(ext, ext_data, &config, &shared))) { + ret = r; + goto cleanup; + } + + /* create/get the context for parsing the data */ + if (shared) { + r = schema_mount_get_ctx_shared(ext, ext_data, config, ext_ctx); + } else { + r = schema_mount_get_ctx_inline(ext, ext_data, config, ext_ctx); + } + if (r) { + ret = r; + goto cleanup; + } + +cleanup: + if (ext_data_free) { + lyd_free_all(ext_data); + } + return ret; +} + +/** + * @brief Snode callback for schema mount. + * Check if data are valid for schema mount and returns their schema node. + */ +static LY_ERR +schema_mount_snode(struct lysc_ext_instance *ext, const struct lyd_node *parent, const struct lysc_node *sparent, + const char *prefix, size_t prefix_len, LY_VALUE_FORMAT format, void *prefix_data, const char *name, size_t name_len, + const struct lysc_node **snode) +{ + LY_ERR r; + const struct lys_module *mod; + const struct ly_ctx *ext_ctx = NULL; + + /* get context based on ietf-yang-library data */ + if ((r = schema_mount_get_ctx(ext, &ext_ctx))) { + return r; + } + + /* get the module */ + mod = lyplg_type_identity_module(ext_ctx, parent ? parent->schema : sparent, prefix, prefix_len, format, prefix_data); + if (!mod) { + return LY_ENOT; + } + + /* get the top-level schema node */ + *snode = lys_find_child(NULL, mod, name, name_len, 0, 0); + return *snode ? LY_SUCCESS : LY_ENOT; +} + +static LY_ERR +schema_mount_get_parent_ref(const struct lysc_ext_instance *ext, const struct lyd_node *ext_data, + struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + char *path = NULL; + + /* get all parent references of this mount point */ + if (asprintf(&path, "/ietf-yang-schema-mount:schema-mounts/mount-point[module='%s'][label='%s']" + "/shared-schema/parent-reference", ext->module->name, ext->argument) == -1) { + EXT_LOGERR_MEM_GOTO(NULL, ext, ret, cleanup); + } + if ((ret = lyd_find_xpath(ext_data, path, set))) { + goto cleanup; + } + +cleanup: + free(path); + return ret; +} + +/** + * @brief Duplicate all accessible parent references for a shared-schema mount point. + * + * @param[in] ext Compiled extension instance. + * @param[in] ctx_node Context node for evaluating the parent-reference XPath expressions. + * @param[in] ext_data Extension data retrieved by the callback. + * @param[in] trg_ctx Mounted data context to use for duplication. + * @param[out] ref_set Set of all top-level parent-ref subtrees connected to each other, may be empty. + * @return LY_ERR value. + */ +static LY_ERR +schema_mount_dup_parent_ref(const struct lysc_ext_instance *ext, const struct lyd_node *ctx_node, + const struct lyd_node *ext_data, const struct ly_ctx *trg_ctx, struct ly_set **ref_set) +{ + LY_ERR ret = LY_SUCCESS; + char *path = NULL; + struct ly_set *set = NULL, *par_set = NULL; + struct lyd_node_term *term; + struct lyd_node *dup = NULL, *top_node, *first; + struct lyd_value_xpath10 *xp_val; + uint32_t i, j; + + *ref_set = NULL; + + if (!ext_data) { + /* we expect the same ext data as before and there must be some for data to be parsed */ + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_EINVAL, "No ext data provided."); + ret = LY_EINVAL; + goto cleanup; + } + + if ((ret = schema_mount_get_parent_ref(ext, ext_data, &set))) { + goto cleanup; + } + + /* prepare result set */ + if ((ret = ly_set_new(ref_set))) { + goto cleanup; + } + + first = NULL; + for (i = 0; i < set->count; ++i) { + term = set->objs[i]; + + /* get the referenced nodes (subtrees) */ + LYD_VALUE_GET(&term->value, xp_val); + if ((ret = lyd_find_xpath4(ctx_node, ctx_node, lyxp_get_expr(xp_val->exp), xp_val->format, xp_val->prefix_data, + NULL, &par_set))) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, ret, "Parent reference \"%s\" evaluation failed.", + lyxp_get_expr(xp_val->exp)); + goto cleanup; + } + + for (j = 0; j < par_set->count; ++j) { + /* duplicate with parents in the context of the mounted data */ + if ((ret = lyd_dup_single_to_ctx(par_set->dnodes[j], trg_ctx, NULL, + LYD_DUP_RECURSIVE | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS | LYD_DUP_NO_EXT, &dup))) { + goto cleanup; + } + + /* go top-level */ + while (dup->parent) { + dup = lyd_parent(dup); + } + + /* check whether the top-level node exists */ + if (first) { + if ((ret = lyd_find_sibling_first(first, dup, &top_node)) && (ret != LY_ENOTFOUND)) { + goto cleanup; + } + } else { + top_node = NULL; + } + + if (top_node) { + /* merge */ + ret = lyd_merge_tree(&first, dup, LYD_MERGE_DESTRUCT); + dup = NULL; + if (ret) { + goto cleanup; + } + } else { + /* insert */ + if ((ret = lyd_insert_sibling(first, dup, &first))) { + goto cleanup; + } + + /* add into the result set because a new top-level node was added */ + if ((ret = ly_set_add(*ref_set, dup, 1, NULL))) { + goto cleanup; + } + dup = NULL; + } + } + } + +cleanup: + free(path); + ly_set_free(set, NULL); + ly_set_free(par_set, NULL); + lyd_free_tree(dup); + if (ret && *ref_set) { + if ((*ref_set)->count) { + lyd_free_siblings((*ref_set)->dnodes[0]); + } + ly_set_free(*ref_set, NULL); + *ref_set = NULL; + } + return ret; +} + +LY_ERR +lyplg_ext_schema_mount_get_parent_ref(const struct lysc_ext_instance *ext, struct ly_set **refs) +{ + LY_ERR rc; + struct ly_set *pref_set = NULL; + struct ly_set *snode_set = NULL; + struct ly_set *results_set = NULL; + struct lyd_node *ext_data; + ly_bool ext_data_free; + + /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */ + if ((rc = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) { + return rc; + } + + LY_CHECK_GOTO(rc = schema_mount_get_parent_ref(ext, ext_data, &pref_set), cleanup); + if (pref_set->count == 0) { + goto cleanup; + } + + LY_CHECK_GOTO(rc = ly_set_new(&results_set), cleanup); + + for (uint32_t i = 0; i < pref_set->count; ++i) { + struct lyd_node_term *term; + struct lyd_value_xpath10 *xp_val; + char *value; + struct ly_err_item *err; + + term = (struct lyd_node_term *)pref_set->dnodes[i]; + LYD_VALUE_GET(&term->value, xp_val); + LY_CHECK_GOTO(rc = lyplg_type_print_xpath10_value(xp_val, LY_VALUE_JSON, NULL, &value, &err), cleanup); + LY_CHECK_ERR_GOTO(rc = lys_find_xpath(ext->module->ctx, NULL, value, 0, &snode_set), free(value), cleanup); + free(value); + for (uint32_t sn = 0; sn < snode_set->count; sn++) { + LY_CHECK_GOTO(rc = ly_set_add(results_set, snode_set->snodes[sn], 0, NULL), cleanup); + } + ly_set_free(snode_set, NULL); + snode_set = NULL; + } + + *refs = results_set; + +cleanup: + if (rc) { + ly_set_free(results_set, NULL); + } + ly_set_free(snode_set, NULL); + if (ext_data_free) { + lyd_free_all(ext_data); + } + ly_set_free(pref_set, NULL); + + return rc; +} + +/** + * @brief Validate callback for schema mount. + */ +static LY_ERR +schema_mount_validate(struct lysc_ext_instance *ext, struct lyd_node *sibling, const struct lyd_node *dep_tree, + enum lyd_type data_type, uint32_t val_opts, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t temp_lo = LY_LOSTORE_LAST, i; + struct ly_err_item *err; + struct lyd_node *iter, *ext_data = NULL, *ref_first = NULL, *orig_parent = lyd_parent(sibling), *op_tree; + struct lyd_node *ext_diff = NULL, *diff_parent = NULL; + ly_bool ext_data_free = 0; + struct ly_set *ref_set = NULL; + + if (!sibling) { + /* some data had to be parsed for this callback to be called */ + EXT_LOGERR_INT_RET(NULL, ext); + } + + /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */ + if ((ret = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) { + goto cleanup; + } + + LY_LIST_FOR(ext_data, iter) { + if (iter->flags & LYD_NEW) { + /* must be validated for the parent-reference prefix data to be stored */ + lyplg_ext_compile_log(NULL, ext, LY_LLERR, LY_EINVAL, "Provided ext data have not been validated."); + ret = LY_EINVAL; + goto cleanup; + } + } + + /* duplicate the referenced parent nodes into ext context */ + if ((ret = schema_mount_dup_parent_ref(ext, orig_parent, ext_data, LYD_CTX(sibling), &ref_set))) { + goto cleanup; + } + + if (data_type != LYD_TYPE_DATA_YANG) { + /* remember the operation data tree, it may be moved */ + op_tree = sibling; + } + + /* create accessible tree, remove LYD_EXT to not call this callback recursively */ + lyd_unlink_siblings(sibling); + LY_LIST_FOR(sibling, iter) { + iter->flags &= ~LYD_EXT; + } + if (ref_set->count) { + if ((ret = lyd_insert_sibling(sibling, ref_set->dnodes[0], &sibling))) { + goto cleanup; + } + } + + /* only store messages in the context, log as an extension */ + ly_temp_log_options(&temp_lo); + + if (data_type == LYD_TYPE_DATA_YANG) { + /* validate all the modules with data */ + ret = lyd_validate_all(&sibling, NULL, val_opts | LYD_VALIDATE_PRESENT, diff ? &ext_diff : NULL); + } else { + /* validate the operation */ + ret = lyd_validate_op(op_tree, dep_tree, data_type, diff ? &ext_diff : NULL); + } + + /* restore logging */ + ly_temp_log_options(NULL); + + /* restore sibling tree */ + for (i = 0; i < ref_set->count; ++i) { + if (ref_set->dnodes[i] == sibling) { + sibling = sibling->next; + } + lyd_free_tree(ref_set->dnodes[i]); + } + LY_LIST_FOR(sibling, iter) { + iter->flags |= LYD_EXT; + } + lyplg_ext_insert(orig_parent, sibling); + + if (ret) { + /* log the error in the original context */ + err = ly_err_first(LYD_CTX(sibling)); + if (!err) { + lyplg_ext_compile_log(NULL, ext, LY_LLERR, ret, "Unknown validation error (err code %d).", ret); + } else { + lyplg_ext_compile_log_path(err->path, ext, LY_LLERR, err->no, "%s", err->msg); + } + goto cleanup; + } + + /* create proper diff */ + if (diff && ext_diff) { + /* diff nodes from an extension instance */ + LY_LIST_FOR(ext_diff, iter) { + iter->flags |= LYD_EXT; + } + + /* create the parent and insert the diff */ + if ((ret = lyd_dup_single(lyd_parent(sibling), NULL, LYD_DUP_WITH_PARENTS | LYD_DUP_NO_META, &diff_parent))) { + goto cleanup; + } + if ((ret = lyplg_ext_insert(diff_parent, ext_diff))) { + goto cleanup; + } + ext_diff = NULL; + + /* go top-level and set the operation */ + while (lyd_parent(diff_parent)) { + diff_parent = lyd_parent(diff_parent); + } + if ((ret = lyd_new_meta(LYD_CTX(diff_parent), diff_parent, NULL, "yang:operation", "none", 0, NULL))) { + goto cleanup; + } + + /* finally merge into the global diff */ + if ((ret = lyd_diff_merge_all(diff, diff_parent, LYD_DIFF_MERGE_DEFAULTS))) { + goto cleanup; + } + } + +cleanup: + ly_set_free(ref_set, NULL); + lyd_free_siblings(ref_first); + lyd_free_tree(ext_diff); + lyd_free_all(diff_parent); + if (ext_data_free) { + lyd_free_all(ext_data); + } + return ret; +} + +/** + * @brief Schema mount compile free. + * + * Implementation of ::lyplg_ext_compile_free_clb callback set as ::lyext_plugin::cfree. + */ +static void +schema_mount_cfree(const struct ly_ctx *ctx, struct lysc_ext_instance *ext) +{ + struct lyplg_ext_sm *sm_data = ext->compiled; + uint32_t i; + + if (!sm_data) { + return; + } + + if (!--sm_data->shared->ref_count) { + for (i = 0; i < sm_data->shared->schema_count; ++i) { + ly_ctx_destroy(sm_data->shared->schemas[i].ctx); + lydict_remove(ctx, sm_data->shared->schemas[i].content_id); + } + free(sm_data->shared->schemas); + free(sm_data->shared); + } + + for (i = 0; i < sm_data->inln.schema_count; ++i) { + ly_ctx_destroy(sm_data->inln.schemas[i].ctx); + } + free(sm_data->inln.schemas); + + pthread_mutex_destroy(&sm_data->lock); + free(sm_data); +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_schema_mount_create_context(const struct lysc_ext_instance *ext, struct ly_ctx **ctx) +{ + struct lyd_node *ext_data = NULL; + ly_bool ext_data_free = 0, config; + LY_ERR rc = LY_SUCCESS; + + if (!ext->module->ctx->ext_clb) { + return LY_EINVAL; + } + + if (strcmp(ext->def->module->name, "ietf-yang-schema-mount") || strcmp(ext->def->name, "mount-point")) { + return LY_EINVAL; + } + + /* get operational data with ietf-yang-library and ietf-yang-schema-mount data */ + if ((rc = lyplg_ext_get_data(ext->module->ctx, ext, (void **)&ext_data, &ext_data_free))) { + return rc; + } + + /* learn about this mount point */ + if ((rc = schema_mount_get_smount(ext, ext_data, &config, NULL))) { + goto cleanup; + } + + /* create the context */ + rc = schema_mount_create_ctx(ext, ext_data, config, ctx); + +cleanup: + if (ext_data_free) { + lyd_free_all(ext_data); + } + return rc; +} + +static void +schema_mount_spriter_tree_free(void *priv) +{ + struct sprinter_tree_priv *st_priv; + + st_priv = priv; + ly_set_free(st_priv->refs, NULL); + ly_ctx_destroy(st_priv->ext_ctx); + free(st_priv); +} + +static LY_ERR +schema_mount_sprinter_tree_cnode_override_mounted(const struct lysc_node *node, const void *UNUSED(plugin_priv), + ly_bool *UNUSED(skip), const char **UNUSED(flags), const char **add_opts) +{ + if (!node->parent) { + *add_opts = "/"; + } + + return LY_SUCCESS; +} + +static LY_ERR +schema_mount_sprinter_tree_pnode_override_mounted(const struct lysp_node *node, const void *UNUSED(plugin_priv), + ly_bool *UNUSED(skip), const char **UNUSED(flags), const char **add_opts) +{ + if (!node->parent) { + *add_opts = "/"; + } + + return LY_SUCCESS; +} + +static LY_ERR +schema_mount_sprinter_tree_node_override_parent_refs(const struct lysc_node *node, const void *plugin_priv, + ly_bool *skip, const char **UNUSED(flags), const char **add_opts) +{ + uint32_t i; + const struct ly_set *refs; + const struct lysc_module *mod; + struct lysc_node *ref, *iter; + + refs = ((struct sprinter_tree_priv *)plugin_priv)->refs; + mod = node->module->compiled; + + /* Assume the @p node will be skipped. */ + *skip = 1; + for (i = 0; (i < refs->count) && *skip; i++) { + ref = refs->snodes[i]; + if (ref->module->compiled != mod) { + /* parent-reference points to different module */ + continue; + } + + for (iter = ref; iter; iter = iter->parent) { + if (iter == node) { + /* @p node is not skipped because it is parent-rererence node or his parent */ + *skip = 0; + break; + } + } + } + + if (!*skip && !node->parent) { + /* top-node has additional opts */ + *add_opts = "@"; + } + + return LY_SUCCESS; +} + +/** + * @brief Schema mount schema parsed tree printer. + * + * Implementation of ::lyplg_ext_sprinter_ptree_clb callback set as lyext_plugin::printer_ptree. + */ +static LY_ERR +schema_mount_sprinter_ptree(struct lysp_ext_instance *UNUSED(ext), const struct lyspr_tree_ctx *ctx, + const char **flags, const char **UNUSED(add_opts)) +{ + if (!ctx) { + *flags = "mp"; + } + + return LY_SUCCESS; +} + +/** + * @brief Schema mount schema compiled tree printer. + * + * Implementation of ::lyplg_ext_sprinter_ctree_clb callback set as lyext_plugin::printer_ctree. + */ +static LY_ERR +schema_mount_sprinter_ctree(struct lysc_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **flags, const char **UNUSED(add_opts)) +{ + LY_ERR rc = LY_SUCCESS; + struct ly_ctx *ext_ctx = NULL; + const struct lys_module *mod; + struct ly_set *refs = NULL; + struct lysc_node *tree1, *tree2; + uint32_t i, j; + ly_bool from_parent_ref, is_first; + struct sprinter_tree_priv *st_priv; + + if (!ctx) { + *flags = "mp"; + return LY_SUCCESS; + } + + if (lyplg_ext_schema_mount_create_context(ext, &ext_ctx)) { + /* Void mount point */ + return LY_SUCCESS; + } + + rc = lyplg_ext_schema_mount_get_parent_ref(ext, &refs); + LY_CHECK_GOTO(rc, cleanup); + + /* build new list of modules to print. This list will omit internal + * modules, modules with no nodes (e.g., iana-if-types) and modules + * that were loaded as the result of a parent-reference. + */ + i = ly_ctx_internal_modules_count(ext_ctx); + while ((mod = ly_ctx_get_module_iter(ext_ctx, &i))) { + from_parent_ref = 0; + + for (j = 0; refs && j < refs->count; j++) { + if (!strcmp(mod->ns, refs->snodes[j]->module->ns)) { + from_parent_ref = 1; + break; + } + } + if (from_parent_ref) { + /* Modules loaded as the result of a parent-reference are added later. */ + continue; + } + + /* Add data nodes, rpcs and notifications. */ + if ((ext_ctx->flags & LY_CTX_SET_PRIV_PARSED) && mod->compiled) { + /* For compiled module. */ + rc = lyplg_ext_sprinter_ctree_add_nodes(ctx, mod->compiled->data, + schema_mount_sprinter_tree_cnode_override_mounted); + LY_CHECK_GOTO(rc, cleanup); + if (mod->compiled->rpcs) { + rc = lyplg_ext_sprinter_ctree_add_nodes(ctx, &mod->compiled->rpcs->node, + schema_mount_sprinter_tree_cnode_override_mounted); + } + LY_CHECK_GOTO(rc, cleanup); + if (mod->compiled->notifs) { + rc = lyplg_ext_sprinter_ctree_add_nodes(ctx, &mod->compiled->notifs->node, + schema_mount_sprinter_tree_cnode_override_mounted); + } + LY_CHECK_GOTO(rc, cleanup); + } else { + /* For parsed module. */ + rc = lyplg_ext_sprinter_ptree_add_nodes(ctx, mod->parsed->data, + schema_mount_sprinter_tree_pnode_override_mounted); + LY_CHECK_GOTO(rc, cleanup); + if (mod->parsed->rpcs) { + rc = lyplg_ext_sprinter_ptree_add_nodes(ctx, &mod->parsed->rpcs->node, + schema_mount_sprinter_tree_pnode_override_mounted); + } + LY_CHECK_GOTO(rc, cleanup); + if (mod->parsed->notifs) { + rc = lyplg_ext_sprinter_ptree_add_nodes(ctx, &mod->parsed->notifs->node, + schema_mount_sprinter_tree_pnode_override_mounted); + } + LY_CHECK_GOTO(rc, cleanup); + } + } + + /* Add modules loaded as the result of a parent-reference. */ + for (i = 0; refs && (i < refs->count); i++) { + tree1 = refs->snodes[i]->module->compiled->data; + + /* Add data nodes from the module only once. */ + is_first = 1; + for (j = 0; j < i; j++) { + tree2 = refs->snodes[j]->module->compiled->data; + if (tree1 == tree2) { + is_first = 0; + break; + } + } + if (is_first) { + /* Add all data nodes but unavailable nodes are skipped in the callback. */ + rc = lyplg_ext_sprinter_ctree_add_nodes(ctx, tree1, schema_mount_sprinter_tree_node_override_parent_refs); + LY_CHECK_GOTO(rc, cleanup); + } + } + + /* add private plugin data */ + st_priv = calloc(1, sizeof(*st_priv)); + LY_CHECK_ERR_GOTO(!st_priv, rc = LY_EMEM, cleanup); + st_priv->ext_ctx = ext_ctx; + st_priv->refs = refs; + rc = lyplg_ext_sprinter_tree_set_priv(ctx, st_priv, schema_mount_spriter_tree_free); + +cleanup: + if (rc) { + ly_set_free(refs, NULL); + ly_ctx_destroy(ext_ctx); + } + + return rc; +} + +/** + * @brief Plugin descriptions for the Yang Schema Mount extension. + * + * Note that external plugins are supposed to use: + * + * LYPLG_EXTENSIONS = { + */ +const struct lyplg_ext_record plugins_schema_mount[] = { + { + .module = "ietf-yang-schema-mount", + .revision = "2019-01-14", + .name = "mount-point", + + .plugin.id = "ly2 schema mount v1", + .plugin.parse = schema_mount_parse, + .plugin.compile = schema_mount_compile, + .plugin.printer_info = NULL, + .plugin.printer_ctree = schema_mount_sprinter_ctree, + .plugin.printer_ptree = schema_mount_sprinter_ptree, + .plugin.node = NULL, + .plugin.snode = schema_mount_snode, + .plugin.validate = schema_mount_validate, + .plugin.pfree = NULL, + .plugin.cfree = schema_mount_cfree + }, + {0} /* terminating zeroed item */ +}; diff --git a/src/plugins_exts/structure.c b/src/plugins_exts/structure.c new file mode 100644 index 0000000..ee7a52e --- /dev/null +++ b/src/plugins_exts/structure.c @@ -0,0 +1,558 @@ +/** + * @file structure.c + * @author Michal Vasko + * @brief libyang extension plugin - structure (RFC 8791) + * + * Copyright (c) 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include +#include + +#include "compat.h" +#include "libyang.h" +#include "plugins_exts.h" + +struct lysp_ext_instance_structure { + struct lysp_restr *musts; + uint16_t flags; + const char *dsc; + const char *ref; + struct lysp_tpdf *typedefs; + struct lysp_node_grp *groupings; + struct lysp_node *child; +}; + +struct lysc_ext_instance_structure { + struct lysc_must *musts; + uint16_t flags; + const char *dsc; + const char *ref; + struct lysc_node *child; +}; + +struct lysp_ext_instance_augment_structure { + uint16_t flags; + const char *dsc; + const char *ref; + struct lysp_node *child; + struct lysp_node_augment *aug; +}; + +/** + * @brief Parse structure extension instances. + * + * Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse. + */ +static LY_ERR +structure_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + LY_ERR rc; + LY_ARRAY_COUNT_TYPE u; + struct lysp_module *pmod; + struct lysp_ext_instance_structure *struct_pdata; + + /* structure can appear only at the top level of a YANG module or submodule */ + if ((ext->parent_stmt != LY_STMT_MODULE) && (ext->parent_stmt != LY_STMT_SUBMODULE)) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, + "Extension %s must not be used as a non top-level statement in \"%s\" statement.", ext->name, + lyplg_ext_stmt2str(ext->parent_stmt)); + return LY_EVALID; + } + + pmod = ext->parent; + + /* check for duplication */ + LY_ARRAY_FOR(pmod->exts, u) { + if ((&pmod->exts[u] != ext) && (pmod->exts[u].name == ext->name) && !strcmp(pmod->exts[u].argument, ext->argument)) { + /* duplication of the same structure extension in a single module */ + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension %s is instantiated multiple times.", ext->name); + return LY_EVALID; + } + } + + /* allocate the storage */ + struct_pdata = calloc(1, sizeof *struct_pdata); + if (!struct_pdata) { + goto emem; + } + ext->parsed = struct_pdata; + LY_ARRAY_CREATE_GOTO(lyplg_ext_parse_get_cur_pmod(pctx)->mod->ctx, ext->substmts, 14, rc, emem); + + /* parse substatements */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_MUST; + ext->substmts[0].storage = &struct_pdata->musts; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_STATUS; + ext->substmts[1].storage = &struct_pdata->flags; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_DESCRIPTION; + ext->substmts[2].storage = &struct_pdata->dsc; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[3].stmt = LY_STMT_REFERENCE; + ext->substmts[3].storage = &struct_pdata->ref; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[4].stmt = LY_STMT_TYPEDEF; + ext->substmts[4].storage = &struct_pdata->typedefs; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[5].stmt = LY_STMT_GROUPING; + ext->substmts[5].storage = &struct_pdata->groupings; + + /* data-def-stmt */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[6].stmt = LY_STMT_CONTAINER; + ext->substmts[6].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[7].stmt = LY_STMT_LEAF; + ext->substmts[7].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[8].stmt = LY_STMT_LEAF_LIST; + ext->substmts[8].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[9].stmt = LY_STMT_LIST; + ext->substmts[9].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[10].stmt = LY_STMT_CHOICE; + ext->substmts[10].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[11].stmt = LY_STMT_ANYDATA; + ext->substmts[11].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[12].stmt = LY_STMT_ANYXML; + ext->substmts[12].storage = &struct_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[13].stmt = LY_STMT_USES; + ext->substmts[13].storage = &struct_pdata->child; + + rc = lyplg_ext_parse_extension_instance(pctx, ext); + return rc; + +emem: + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +/** + * @brief Compile structure extension instances. + * + * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile. + */ +static LY_ERR +structure_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *extp, struct lysc_ext_instance *ext) +{ + LY_ERR rc; + struct lysc_module *mod_c; + const struct lysc_node *child; + struct lysc_ext_instance_structure *struct_cdata; + uint32_t prev_options = *lyplg_ext_compile_get_options(cctx); + + mod_c = ext->parent; + + /* check identifier namespace with the compiled nodes */ + LY_LIST_FOR(mod_c->data, child) { + if (!strcmp(child->name, ext->argument)) { + /* identifier collision */ + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EVALID, "Extension %s collides with a %s with the same identifier.", + extp->name, lys_nodetype2str(child->nodetype)); + return LY_EVALID; + } + } + + /* allocate the storage */ + struct_cdata = calloc(1, sizeof *struct_cdata); + if (!struct_cdata) { + goto emem; + } + ext->compiled = struct_cdata; + + /* compile substatements */ + LY_ARRAY_CREATE_GOTO(cctx->ctx, ext->substmts, 14, rc, emem); + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_MUST; + ext->substmts[0].storage = &struct_cdata->musts; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_STATUS; + ext->substmts[1].storage = &struct_cdata->flags; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_DESCRIPTION; + ext->substmts[2].storage = &struct_cdata->dsc; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[3].stmt = LY_STMT_REFERENCE; + ext->substmts[3].storage = &struct_cdata->ref; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[4].stmt = LY_STMT_TYPEDEF; + ext->substmts[4].storage = NULL; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[5].stmt = LY_STMT_GROUPING; + ext->substmts[5].storage = NULL; + + /* data-def-stmt */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[6].stmt = LY_STMT_CONTAINER; + ext->substmts[6].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[7].stmt = LY_STMT_LEAF; + ext->substmts[7].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[8].stmt = LY_STMT_LEAF_LIST; + ext->substmts[8].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[9].stmt = LY_STMT_LIST; + ext->substmts[9].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[10].stmt = LY_STMT_CHOICE; + ext->substmts[10].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[11].stmt = LY_STMT_ANYDATA; + ext->substmts[11].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[12].stmt = LY_STMT_ANYXML; + ext->substmts[12].storage = &struct_cdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[13].stmt = LY_STMT_USES; + ext->substmts[13].storage = &struct_cdata->child; + + *lyplg_ext_compile_get_options(cctx) |= LYS_COMPILE_NO_CONFIG | LYS_COMPILE_NO_DISABLED; + rc = lyplg_ext_compile_extension_instance(cctx, extp, ext); + *lyplg_ext_compile_get_options(cctx) = prev_options; + if (rc) { + return rc; + } + + return LY_SUCCESS; + +emem: + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +/** + * @brief Structure schema info printer. + * + * Implementation of ::lyplg_ext_sprinter_info_clb set as ::lyext_plugin::printer_info + */ +static LY_ERR +structure_printer_info(struct lyspr_ctx *ctx, struct lysc_ext_instance *ext, ly_bool *flag) +{ + lyplg_ext_print_info_extension_instance(ctx, ext, flag); + return LY_SUCCESS; +} + +/** + * @brief Free parsed structure extension instance data. + * + * Implementation of ::lyplg_clb_parse_free_clb callback set as lyext_plugin::pfree. + */ +static void +structure_pfree(const struct ly_ctx *ctx, struct lysp_ext_instance *ext) +{ + lyplg_ext_pfree_instance_substatements(ctx, ext->substmts); + free(ext->parsed); +} + +/** + * @brief Free compiled structure extension instance data. + * + * Implementation of ::lyplg_clb_compile_free_clb callback set as lyext_plugin::cfree. + */ +static void +structure_cfree(const struct ly_ctx *ctx, struct lysc_ext_instance *ext) +{ + lyplg_ext_cfree_instance_substatements(ctx, ext->substmts); + free(ext->compiled); +} + +/** + * @brief Parse augment-structure extension instances. + * + * Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse. + */ +static LY_ERR +structure_aug_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + LY_ERR rc; + struct lysp_stmt *stmt; + struct lysp_ext_instance_augment_structure *aug_pdata; + const struct ly_ctx *ctx = lyplg_ext_parse_get_cur_pmod(pctx)->mod->ctx; + + /* augment-structure can appear only at the top level of a YANG module or submodule */ + if ((ext->parent_stmt != LY_STMT_MODULE) && (ext->parent_stmt != LY_STMT_SUBMODULE)) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, + "Extension %s must not be used as a non top-level statement in \"%s\" statement.", ext->name, + lyplg_ext_stmt2str(ext->parent_stmt)); + return LY_EVALID; + } + + /* augment-structure must define some data-def-stmt */ + LY_LIST_FOR(ext->child, stmt) { + if (stmt->kw & LY_STMT_DATA_NODE_MASK) { + break; + } + } + if (!stmt) { + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension %s does not define any data-def-stmt statements.", + ext->name); + return LY_EVALID; + } + + /* allocate the storage */ + aug_pdata = calloc(1, sizeof *aug_pdata); + if (!aug_pdata) { + goto emem; + } + ext->parsed = aug_pdata; + LY_ARRAY_CREATE_GOTO(ctx, ext->substmts, 13, rc, emem); + + /* parse substatements */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_STATUS; + ext->substmts[0].storage = &aug_pdata->flags; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_DESCRIPTION; + ext->substmts[1].storage = &aug_pdata->dsc; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_REFERENCE; + ext->substmts[2].storage = &aug_pdata->ref; + + /* data-def-stmt */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[3].stmt = LY_STMT_CONTAINER; + ext->substmts[3].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[4].stmt = LY_STMT_LEAF; + ext->substmts[4].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[5].stmt = LY_STMT_LEAF_LIST; + ext->substmts[5].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[6].stmt = LY_STMT_LIST; + ext->substmts[6].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[7].stmt = LY_STMT_CHOICE; + ext->substmts[7].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[8].stmt = LY_STMT_ANYDATA; + ext->substmts[8].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[9].stmt = LY_STMT_ANYXML; + ext->substmts[9].storage = &aug_pdata->child; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[10].stmt = LY_STMT_USES; + ext->substmts[10].storage = &aug_pdata->child; + + /* case */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[11].stmt = LY_STMT_CASE; + ext->substmts[11].storage = &aug_pdata->child; + + if ((rc = lyplg_ext_parse_extension_instance(pctx, ext))) { + return rc; + } + + /* add fake parsed augment node */ + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[12].stmt = LY_STMT_AUGMENT; + ext->substmts[12].storage = &aug_pdata->aug; + + aug_pdata->aug = calloc(1, sizeof *aug_pdata->aug); + if (!aug_pdata->aug) { + goto emem; + } + aug_pdata->aug->nodetype = LYS_AUGMENT; + aug_pdata->aug->flags = aug_pdata->flags; + if (lydict_insert(ctx, ext->argument, 0, &aug_pdata->aug->nodeid)) { + goto emem; + } + aug_pdata->aug->child = aug_pdata->child; + /* avoid double free */ + aug_pdata->child = NULL; + + return LY_SUCCESS; + +emem: + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +static LY_ERR +structure_sprinter_pnode(const struct lysp_node *UNUSED(node), const void *UNUSED(plugin_priv), + ly_bool *UNUSED(skip), const char **flags, const char **UNUSED(add_opts)) +{ + *flags = ""; + return LY_SUCCESS; +} + +static LY_ERR +structure_sprinter_cnode(const struct lysc_node *UNUSED(node), const void *UNUSED(plugin_priv), + ly_bool *UNUSED(skip), const char **flags, const char **UNUSED(add_opts)) +{ + *flags = ""; + return LY_SUCCESS; +} + +/** + * @brief Structure schema compiled tree printer. + * + * Implementation of ::lyplg_ext_sprinter_ctree_clb callback set as lyext_plugin::printer_ctree. + */ +static LY_ERR +structure_sprinter_ctree(struct lysc_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **UNUSED(flags), const char **UNUSED(add_opts)) +{ + LY_ERR rc; + + rc = lyplg_ext_sprinter_ctree_add_ext_nodes(ctx, ext, structure_sprinter_cnode); + return rc; +} + +/** + * @brief Structure schema parsed tree printer. + * + * Implementation of ::lyplg_ext_sprinter_ptree_clb callback set as lyext_plugin::printer_ptree. + */ +static LY_ERR +structure_sprinter_ptree(struct lysp_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **UNUSED(flags), const char **UNUSED(add_opts)) +{ + LY_ERR rc; + + rc = lyplg_ext_sprinter_ptree_add_ext_nodes(ctx, ext, structure_sprinter_pnode); + return rc; +} + +/** + * @brief Augment structure schema parsed tree printer. + * + * Implementation of ::lyplg_ext_sprinter_ptree_clb callback set as lyext_plugin::printer_ptree. + */ +static LY_ERR +structure_aug_sprinter_ptree(struct lysp_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **UNUSED(flags), const char **UNUSED(add_opts)) +{ + LY_ERR rc = LY_SUCCESS; + struct lysp_node_augment **aug; + + assert(ctx); + + aug = ext->substmts[12].storage; + rc = lyplg_ext_sprinter_ptree_add_nodes(ctx, (*aug)->child, structure_sprinter_pnode); + + return rc; +} + +/** + * @brief Augment structure schema compiled tree printer. + * + * Implementation of ::lyplg_ext_sprinter_ctree_clb callback set as lyext_plugin::printer_ctree. + */ +static LY_ERR +structure_aug_sprinter_ctree(struct lysc_ext_instance *ext, const struct lyspr_tree_ctx *ctx, const char **flags, + const char **add_opts) +{ + LY_ERR rc = LY_SUCCESS; + + LY_ARRAY_COUNT_TYPE i; + struct lysp_ext_instance *parsed_ext; + + assert(ctx); + + /* find the parsed ext structure */ + parsed_ext = ext->module->parsed->exts; + LY_ARRAY_FOR(parsed_ext, i) { + if (!strcmp(parsed_ext[i].name, "sx:augment-structure") && !strcmp(parsed_ext[i].argument, ext->argument)) { + break; + } + } + assert(i < LY_ARRAY_COUNT(parsed_ext)); + + /* for augments print the parsed tree */ + rc = structure_aug_sprinter_ptree(parsed_ext, ctx, flags, add_opts); + return rc; +} + +/** + * @brief Plugin descriptions for the structure extension + * + * Note that external plugins are supposed to use: + * + * LYPLG_EXTENSIONS = { + */ +const struct lyplg_ext_record plugins_structure[] = { + { + .module = "ietf-yang-structure-ext", + .revision = "2020-06-17", + .name = "structure", + + .plugin.id = "ly2 structure v1", + .plugin.parse = structure_parse, + .plugin.compile = structure_compile, + .plugin.printer_info = structure_printer_info, + .plugin.printer_ctree = structure_sprinter_ctree, + .plugin.printer_ptree = structure_sprinter_ptree, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = structure_pfree, + .plugin.cfree = structure_cfree + }, + { + .module = "ietf-yang-structure-ext", + .revision = "2020-06-17", + .name = "augment-structure", + + .plugin.id = "ly2 structure v1", + .plugin.parse = structure_aug_parse, + .plugin.compile = NULL, + .plugin.printer_info = NULL, + .plugin.printer_ctree = structure_aug_sprinter_ctree, + .plugin.printer_ptree = structure_aug_sprinter_ptree, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = structure_pfree, + .plugin.cfree = NULL + }, + {0} /* terminating zeroed record */ +}; diff --git a/src/plugins_exts/yangdata.c b/src/plugins_exts/yangdata.c new file mode 100644 index 0000000..0c8f37b --- /dev/null +++ b/src/plugins_exts/yangdata.c @@ -0,0 +1,277 @@ +/** + * @file yangdata.c + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang extension plugin - yang-data (RFC 8040) + * + * Copyright (c) 2021 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include +#include + +#include "compat.h" +#include "libyang.h" +#include "plugins_exts.h" + +static void yangdata_cfree(const struct ly_ctx *ctx, struct lysc_ext_instance *ext); + +/** + * @brief Parse yang-data extension instances. + * + * Implementation of ::lyplg_ext_parse_clb callback set as lyext_plugin::parse. + */ +static LY_ERR +yangdata_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) +{ + LY_ERR ret; + LY_ARRAY_COUNT_TYPE u; + struct lysp_module *pmod; + + /* yang-data can appear only at the top level of a YANG module or submodule */ + if ((ext->parent_stmt != LY_STMT_MODULE) && (ext->parent_stmt != LY_STMT_SUBMODULE)) { + lyplg_ext_parse_log(pctx, ext, LY_LLWRN, 0, "Extension %s is ignored since it appears as a non top-level statement " + "in \"%s\" statement.", ext->name, lyplg_ext_stmt2str(ext->parent_stmt)); + return LY_ENOT; + } + + pmod = ext->parent; + + /* check for duplication */ + LY_ARRAY_FOR(pmod->exts, u) { + if ((&pmod->exts[u] != ext) && (pmod->exts[u].name == ext->name) && !strcmp(pmod->exts[u].argument, ext->argument)) { + /* duplication of the same yang-data extension in a single module */ + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EVALID, "Extension %s is instantiated multiple times.", ext->name); + return LY_EVALID; + } + } + + /* parse yang-data substatements */ + LY_ARRAY_CREATE_GOTO(lyplg_ext_parse_get_cur_pmod(pctx)->mod->ctx, ext->substmts, 3, ret, emem); + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_CONTAINER; + ext->substmts[0].storage = &ext->parsed; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_CHOICE; + ext->substmts[1].storage = &ext->parsed; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_USES; + ext->substmts[2].storage = &ext->parsed; + + if ((ret = lyplg_ext_parse_extension_instance(pctx, ext))) { + return ret; + } + + return LY_SUCCESS; + +emem: + lyplg_ext_parse_log(pctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +/** + * @brief Compile yang-data extension instances. + * + * Implementation of ::lyplg_ext_compile_clb callback set as lyext_plugin::compile. + */ +static LY_ERR +yangdata_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *extp, struct lysc_ext_instance *ext) +{ + LY_ERR ret; + const struct lysc_node *child; + ly_bool valid = 1; + uint32_t prev_options = *lyplg_ext_compile_get_options(cctx); + + /* compile yangg-data substatements */ + LY_ARRAY_CREATE_GOTO(cctx->ctx, ext->substmts, 3, ret, emem); + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[0].stmt = LY_STMT_CONTAINER; + ext->substmts[0].storage = &ext->compiled; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[1].stmt = LY_STMT_CHOICE; + ext->substmts[1].storage = &ext->compiled; + + LY_ARRAY_INCREMENT(ext->substmts); + ext->substmts[2].stmt = LY_STMT_USES; + ext->substmts[2].storage = &ext->compiled; + + *lyplg_ext_compile_get_options(cctx) |= LYS_COMPILE_NO_CONFIG | LYS_COMPILE_NO_DISABLED; + ret = lyplg_ext_compile_extension_instance(cctx, extp, ext); + *lyplg_ext_compile_get_options(cctx) = prev_options; + if (ret) { + return ret; + } + + /* check that we have really just a single container data definition in the top */ + child = ext->compiled; + if (!child) { + valid = 0; + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EVALID, + "Extension %s is instantiated without any top level data node, but exactly one container data node is expected.", + extp->name); + } else if (child->next) { + valid = 0; + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EVALID, + "Extension %s is instantiated with multiple top level data nodes, but only a single container data node is allowed.", + extp->name); + } else if (child->nodetype == LYS_CHOICE) { + /* all the choice's case are expected to result to a single container node */ + struct lysc_module *mod_c = ext->parent; + const struct lysc_node *snode = NULL; + + while ((snode = lys_getnext(snode, child, mod_c, 0))) { + if (snode->next) { + valid = 0; + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EVALID, + "Extension %s is instantiated with multiple top level data nodes (inside a single choice's case), " + "but only a single container data node is allowed.", extp->name); + break; + } else if (snode->nodetype != LYS_CONTAINER) { + valid = 0; + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EVALID, + "Extension %s is instantiated with %s top level data node (inside a choice), " + "but only a single container data node is allowed.", extp->name, lys_nodetype2str(snode->nodetype)); + break; + } + } + } else if (child->nodetype != LYS_CONTAINER) { + /* via uses */ + valid = 0; + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EVALID, + "Extension %s is instantiated with %s top level data node, but only a single container data node is allowed.", + extp->name, lys_nodetype2str(child->nodetype)); + } + + if (!valid) { + yangdata_cfree(lyplg_ext_compile_get_ctx(cctx), ext); + ext->compiled = ext->substmts = NULL; + return LY_EVALID; + } + + return LY_SUCCESS; + +emem: + lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EMEM, "Memory allocation failed (%s()).", __func__); + return LY_EMEM; +} + +/** + * @brief INFO printer + * + * Implementation of ::lyplg_ext_sprinter_info_clb set as ::lyext_plugin::printer_info + */ +static LY_ERR +yangdata_printer_info(struct lyspr_ctx *ctx, struct lysc_ext_instance *ext, ly_bool *flag) +{ + lyplg_ext_print_info_extension_instance(ctx, ext, flag); + return LY_SUCCESS; +} + +/** + * @brief Free parsed yang-data extension instance data. + * + * Implementation of ::lyplg_clb_parse_free_clb callback set as lyext_plugin::pfree. + */ +static void +yangdata_pfree(const struct ly_ctx *ctx, struct lysp_ext_instance *ext) +{ + lyplg_ext_pfree_instance_substatements(ctx, ext->substmts); +} + +/** + * @brief Free compiled yang-data extension instance data. + * + * Implementation of ::lyplg_clb_compile_free_clb callback set as lyext_plugin::cfree. + */ +static void +yangdata_cfree(const struct ly_ctx *ctx, struct lysc_ext_instance *ext) +{ + lyplg_ext_cfree_instance_substatements(ctx, ext->substmts); +} + +static void +yangdata_sprinter_node(uint16_t nodetype, const char **flags) +{ + if (nodetype & LYS_USES) { + *flags = "-u"; + } else { + *flags = "--"; + } +} + +static LY_ERR +yangdata_sprinter_cnode(const struct lysc_node *node, const void *UNUSED(plugin_priv), ly_bool *UNUSED(skip), + const char **flags, const char **UNUSED(add_opts)) +{ + yangdata_sprinter_node(node->nodetype, flags); + return LY_SUCCESS; +} + +static LY_ERR +yangdata_sprinter_pnode(const struct lysp_node *node, const void *UNUSED(plugin_priv), ly_bool *UNUSED(skip), + const char **flags, const char **UNUSED(add_opts)) +{ + yangdata_sprinter_node(node->nodetype, flags); + return LY_SUCCESS; +} + +static LY_ERR +yangdata_sprinter_ctree(struct lysc_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **UNUSED(flags), const char **UNUSED(add_opts)) +{ + LY_ERR rc = LY_SUCCESS; + + assert(ctx); + rc = lyplg_ext_sprinter_ctree_add_ext_nodes(ctx, ext, yangdata_sprinter_cnode); + return rc; +} + +static LY_ERR +yangdata_sprinter_ptree(struct lysp_ext_instance *ext, const struct lyspr_tree_ctx *ctx, + const char **UNUSED(flags), const char **UNUSED(add_opts)) +{ + LY_ERR rc = LY_SUCCESS; + + assert(ctx); + rc = lyplg_ext_sprinter_ptree_add_ext_nodes(ctx, ext, yangdata_sprinter_pnode); + return rc; +} + +/** + * @brief Plugin descriptions for the yang-data extension + * + * Note that external plugins are supposed to use: + * + * LYPLG_EXTENSIONS = { + */ +const struct lyplg_ext_record plugins_yangdata[] = { + { + .module = "ietf-restconf", + .revision = "2017-01-26", + .name = "yang-data", + + .plugin.id = "ly2 yang-data v1", + .plugin.parse = yangdata_parse, + .plugin.compile = yangdata_compile, + .plugin.printer_info = yangdata_printer_info, + .plugin.printer_ctree = yangdata_sprinter_ctree, + .plugin.printer_ptree = yangdata_sprinter_ptree, + .plugin.node = NULL, + .plugin.snode = NULL, + .plugin.validate = NULL, + .plugin.pfree = yangdata_pfree, + .plugin.cfree = yangdata_cfree + }, + {0} /* terminating zeroed record */ +}; diff --git a/src/plugins_internal.h b/src/plugins_internal.h new file mode 100644 index 0000000..d13db16 --- /dev/null +++ b/src/plugins_internal.h @@ -0,0 +1,85 @@ +/** + * @file plugins_internal.h + * @author Radek Krejci + * @author Michal Vasko + * @brief internal functions to support extension and type plugins. + * + * Copyright (c) 2019-2022 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_PLUGINS_INTERNAL_H_ +#define LY_PLUGINS_INTERNAL_H_ + +#include + +#include "plugins.h" +#include "plugins_exts.h" +#include "plugins_types.h" + +#define LY_TYPE_UNKNOWN_STR "unknown" /**< text representation of ::LY_TYPE_UNKNOWN */ +#define LY_TYPE_BINARY_STR "binary" /**< text representation of ::LY_TYPE_BINARY */ +#define LY_TYPE_UINT8_STR "8bit unsigned integer" /**< text representation of ::LY_TYPE_UINT8 */ +#define LY_TYPE_UINT16_STR "16bit unsigned integer" /**< text representation of ::LY_TYPE_UINT16 */ +#define LY_TYPE_UINT32_STR "32bit unsigned integer" /**< text representation of ::LY_TYPE_UINT32 */ +#define LY_TYPE_UINT64_STR "64bit unsigned integer" /**< text representation of ::LY_TYPE_UINT64 */ +#define LY_TYPE_STRING_STR "string" /**< text representation of ::LY_TYPE_STRING */ +#define LY_TYPE_BITS_STR "bits" /**< text representation of ::LY_TYPE_BITS */ +#define LY_TYPE_BOOL_STR "boolean" /**< text representation of ::LY_TYPE_BOOL */ +#define LY_TYPE_DEC64_STR "decimal64" /**< text representation of ::LY_TYPE_DEC64 */ +#define LY_TYPE_EMPTY_STR "empty" /**< text representation of ::LY_TYPE_EMPTY */ +#define LY_TYPE_ENUM_STR "enumeration" /**< text representation of ::LY_TYPE_ENUM */ +#define LY_TYPE_IDENT_STR "identityref" /**< text representation of ::LY_TYPE_IDENT */ +#define LY_TYPE_INST_STR "instance-identifier" /**< text representation of ::LY_TYPE_INST */ +#define LY_TYPE_LEAFREF_STR "leafref" /**< text representation of ::LY_TYPE_LEAFREF */ +#define LY_TYPE_UNION_STR "union" /**< text representation of ::LY_TYPE_UNION */ +#define LY_TYPE_INT8_STR "8bit integer" /**< text representation of ::LY_TYPE_INT8 */ +#define LY_TYPE_INT16_STR "16bit integer" /**< text representation of ::LY_TYPE_INT16 */ +#define LY_TYPE_INT32_STR "32bit integer" /**< text representation of ::LY_TYPE_INT32 */ +#define LY_TYPE_INT64_STR "64bit integer" /**< text representation of ::LY_TYPE_INT64 */ + +/** + * @brief Initiate libyang plugins. + * + * Covers both the types and extensions plugins. + * + * @return LY_SUCCESS in case of success + * @return LY_EINT in case of internal error + * @return LY_EMEM in case of memory allocation failure. + */ +LY_ERR lyplg_init(void); + +/** + * @brief Remove (unload) all the plugins currently available. + */ +void lyplg_clean(void); + +/** + * @brief Find a type plugin. + * + * @param[in] module Name of the module where the type is defined. Must not be NULL, in case of plugins for + * built-in types, the module is "". + * @param[in] revision Revision of the module for which the plugin is implemented. NULL is not a wildcard, it matches + * only the plugins with NULL revision specified. + * @param[in] name Name of the type which the plugin implements. + * @return Found type plugin, NULL if none found. + */ +struct lyplg_type *lyplg_type_plugin_find(const char *module, const char *revision, const char *name); + +/** + * @brief Find an extension plugin. + * + * @param[in] module Name of the module where the extension is defined. + * @param[in] revision Revision of the module for which the plugin is implemented. NULL is not a wildcard, it matches + * only the plugins with NULL revision specified. + * @param[in] name Name of the extension which the plugin implements. + * @return Found extension record, NULL if none found. + */ +struct lyplg_ext_record *lyplg_ext_record_find(const char *module, const char *revision, const char *name); + +#endif /* LY_PLUGINS_INTERNAL_H_ */ diff --git a/src/plugins_types.c b/src/plugins_types.c new file mode 100644 index 0000000..cb4b896 --- /dev/null +++ b/src/plugins_types.c @@ -0,0 +1,1043 @@ +/** + * @file plugins_types.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Built-in types plugins and interface for user types plugins. + * + * Copyright (c) 2019 - 2022 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 /* asprintf, strdup */ + +#include "plugins_types.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "path.h" +#include "schema_compile.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xml.h" +#include "xpath.h" + +/** + * @brief Find import prefix in imports. + */ +static const struct lys_module * +ly_schema_resolve_prefix(const struct ly_ctx *UNUSED(ctx), const char *prefix, size_t prefix_len, const void *prefix_data) +{ + const struct lysp_module *prefix_mod = prefix_data; + struct lys_module *m = NULL; + LY_ARRAY_COUNT_TYPE u; + const char *local_prefix; + + local_prefix = prefix_mod->is_submod ? ((struct lysp_submodule *)prefix_mod)->prefix : prefix_mod->mod->prefix; + if (!prefix_len || !ly_strncmp(local_prefix, prefix, prefix_len)) { + /* it is the prefix of the module itself */ + m = prefix_mod->mod; + } + + /* search in imports */ + if (!m) { + LY_ARRAY_FOR(prefix_mod->imports, u) { + if (!ly_strncmp(prefix_mod->imports[u].prefix, prefix, prefix_len)) { + m = prefix_mod->imports[u].module; + break; + } + } + } + + return m; +} + +/** + * @brief Find resolved module for a prefix in prefix - module pairs. + */ +static const struct lys_module * +ly_schema_resolved_resolve_prefix(const struct ly_ctx *UNUSED(ctx), const char *prefix, size_t prefix_len, + const void *prefix_data) +{ + const struct lysc_prefix *prefixes = prefix_data; + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(prefixes, u) { + if ((!prefixes[u].prefix && !prefix_len) || (prefixes[u].prefix && !ly_strncmp(prefixes[u].prefix, prefix, prefix_len))) { + return prefixes[u].mod; + } + } + + return NULL; +} + +/** + * @brief Find XML namespace prefix in XML namespaces, which are then mapped to modules. + */ +static const struct lys_module * +ly_xml_resolve_prefix(const struct ly_ctx *ctx, const char *prefix, size_t prefix_len, const void *prefix_data) +{ + const struct lys_module *mod; + const struct lyxml_ns *ns; + const struct ly_set *ns_set = prefix_data; + + ns = lyxml_ns_get(ns_set, prefix, prefix_len); + if (!ns) { + return NULL; + } + + mod = ly_ctx_get_module_implemented_ns(ctx, ns->uri); + if (!mod) { + /* for YIN extension prefix resolution */ + mod = ly_ctx_get_module_latest_ns(ctx, ns->uri); + } + return mod; +} + +/** + * @brief Find module name. + */ +static const struct lys_module * +ly_json_resolve_prefix(const struct ly_ctx *ctx, const char *prefix, size_t prefix_len, const void *UNUSED(prefix_data)) +{ + return ly_ctx_get_module_implemented2(ctx, prefix, prefix_len); +} + +const struct lys_module * +ly_resolve_prefix(const struct ly_ctx *ctx, const void *prefix, size_t prefix_len, LY_VALUE_FORMAT format, + const void *prefix_data) +{ + const struct lys_module *mod = NULL; + + LY_CHECK_ARG_RET(ctx, prefix, prefix_len, NULL); + + switch (format) { + case LY_VALUE_SCHEMA: + mod = ly_schema_resolve_prefix(ctx, prefix, prefix_len, prefix_data); + break; + case LY_VALUE_SCHEMA_RESOLVED: + mod = ly_schema_resolved_resolve_prefix(ctx, prefix, prefix_len, prefix_data); + break; + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + mod = ly_xml_resolve_prefix(ctx, prefix, prefix_len, prefix_data); + break; + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + mod = ly_json_resolve_prefix(ctx, prefix, prefix_len, prefix_data); + break; + } + + return mod; +} + +LIBYANG_API_DEF const struct lys_module * +lyplg_type_identity_module(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *prefix, + size_t prefix_len, LY_VALUE_FORMAT format, const void *prefix_data) +{ + if (prefix_len) { + return ly_resolve_prefix(ctx, prefix, prefix_len, format, prefix_data); + } else { + switch (format) { + case LY_VALUE_SCHEMA: + /* use local module */ + return ly_schema_resolve_prefix(ctx, prefix, prefix_len, prefix_data); + case LY_VALUE_SCHEMA_RESOLVED: + /* use local module */ + return ly_schema_resolved_resolve_prefix(ctx, prefix, prefix_len, prefix_data); + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* use context node module (as specified) */ + return ctx_node ? ctx_node->module : NULL; + case LY_VALUE_XML: + /* use the default namespace */ + return ly_xml_resolve_prefix(ctx, NULL, 0, prefix_data); + } + } + + return NULL; +} + +/** + * @brief Find module in import prefixes. + */ +static const char * +ly_schema_get_prefix(const struct lys_module *mod, void *prefix_data) +{ + const struct lysp_module *pmod = prefix_data; + LY_ARRAY_COUNT_TYPE u; + + if (pmod->mod == mod) { + if (pmod->is_submod) { + return ((struct lysp_submodule *)pmod)->prefix; + } else { + return pmod->mod->prefix; + } + } + + LY_ARRAY_FOR(pmod->imports, u) { + if (pmod->imports[u].module == mod) { + /* match */ + return pmod->imports[u].prefix; + } + } + + return NULL; +} + +/** + * @brief Find prefix in prefix - module pairs. + */ +static const char * +ly_schema_resolved_get_prefix(const struct lys_module *mod, void *prefix_data) +{ + struct lysc_prefix *prefixes = prefix_data; + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(prefixes, u) { + if (prefixes[u].mod == mod) { + return prefixes[u].prefix; + } + } + + return NULL; +} + +/** + * @brief Simply return module local prefix. Also, store the module in a set. + */ +static const char * +ly_xml_get_prefix(const struct lys_module *mod, void *prefix_data) +{ + struct ly_set *ns_list = prefix_data; + + LY_CHECK_RET(ly_set_add(ns_list, (void *)mod, 0, NULL), NULL); + return mod->prefix; +} + +/** + * @brief Simply return module name. + */ +static const char * +ly_json_get_prefix(const struct lys_module *mod, void *UNUSED(prefix_data)) +{ + return mod->name; +} + +const char * +ly_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data) +{ + const char *prefix = NULL; + + switch (format) { + case LY_VALUE_SCHEMA: + prefix = ly_schema_get_prefix(mod, prefix_data); + break; + case LY_VALUE_SCHEMA_RESOLVED: + prefix = ly_schema_resolved_get_prefix(mod, prefix_data); + break; + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + prefix = ly_xml_get_prefix(mod, prefix_data); + break; + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + prefix = ly_json_get_prefix(mod, prefix_data); + break; + } + + return prefix; +} + +LIBYANG_API_DEF const char * +lyplg_type_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data) +{ + return ly_get_prefix(mod, format, prefix_data); +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_simple(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + if (val1->_canonical == val2->_canonical) { + return LY_SUCCESS; + } + + return LY_ENOT; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_simple(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT UNUSED(format), + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = ly_strlen(value->_canonical); + } + return value->_canonical; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_simple(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + memset(dup, 0, sizeof *dup); + LY_CHECK_RET(lydict_insert(ctx, original->_canonical, 0, &dup->_canonical)); + memcpy(dup->fixed_mem, original->fixed_mem, sizeof dup->fixed_mem); + dup->realtype = original->realtype; + return LY_SUCCESS; +} + +LIBYANG_API_DEF void +lyplg_type_free_simple(const struct ly_ctx *ctx, struct lyd_value *value) +{ + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_parse_int(const char *datatype, int base, int64_t min, int64_t max, const char *value, size_t value_len, + int64_t *ret, struct ly_err_item **err) +{ + LY_CHECK_ARG_RET(NULL, err, datatype, LY_EINVAL); + + *err = NULL; + + /* consume leading whitespaces */ + for ( ; value_len && isspace(*value); ++value, --value_len) {} + + if (!value || !value_len || !value[0]) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid type %s empty value.", datatype); + } + + switch (ly_parse_int(value, value_len, min, max, base, ret)) { + case LY_EDENIED: + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Value \"%.*s\" is out of type %s min/max bounds.", (int)value_len, value, datatype); + case LY_SUCCESS: + return LY_SUCCESS; + default: + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid type %s value \"%.*s\".", datatype, (int)value_len, value); + } +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_parse_uint(const char *datatype, int base, uint64_t max, const char *value, size_t value_len, uint64_t *ret, + struct ly_err_item **err) +{ + LY_CHECK_ARG_RET(NULL, err, datatype, LY_EINVAL); + + *err = NULL; + + /* consume leading whitespaces */ + for ( ; value_len && isspace(*value); ++value, --value_len) {} + + if (!value || !value_len || !value[0]) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid type %s empty value.", datatype); + } + + *err = NULL; + switch (ly_parse_uint(value, value_len, max, base, ret)) { + case LY_EDENIED: + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Value \"%.*s\" is out of type %s min/max bounds.", (int)value_len, value, datatype); + case LY_SUCCESS: + return LY_SUCCESS; + default: + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid type %s value \"%.*s\".", datatype, (int)value_len, value); + } +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_parse_dec64(uint8_t fraction_digits, const char *value, size_t value_len, int64_t *ret, struct ly_err_item **err) +{ + LY_ERR ret_val; + char *valcopy = NULL; + size_t fraction = 0, size, len = 0, trailing_zeros; + int64_t d; + + *err = NULL; + + /* consume leading whitespaces */ + for ( ; value_len && isspace(*value); ++value, --value_len) {} + + /* parse value */ + if (!value_len) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid empty decimal64 value."); + } else if (!isdigit(value[len]) && (value[len] != '-') && (value[len] != '+')) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid %zu. character of decimal64 value \"%.*s\".", + len + 1, (int)value_len, value); + } + + if ((value[len] == '-') || (value[len] == '+')) { + ++len; + } + + while (len < value_len && isdigit(value[len])) { + ++len; + } + + trailing_zeros = 0; + if ((len < value_len) && ((value[len] != '.') || !isdigit(value[len + 1]))) { + goto decimal; + } + fraction = len; + ++len; + while (len < value_len && isdigit(value[len])) { + if (value[len] == '0') { + ++trailing_zeros; + } else { + trailing_zeros = 0; + } + ++len; + } + len = len - trailing_zeros; + +decimal: + if (fraction && (len - 1 - fraction > fraction_digits)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Value \"%.*s\" of decimal64 type exceeds defined number (%u) of fraction digits.", + (int)len, value, fraction_digits); + } + if (fraction) { + size = len + (fraction_digits - (len - 1 - fraction)); + } else { + size = len + fraction_digits + 1; + } + + if (len + trailing_zeros < value_len) { + /* consume trailing whitespaces to check that there is nothing after it */ + uint64_t u; + + for (u = len + trailing_zeros; u < value_len && isspace(value[u]); ++u) {} + if (u != value_len) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid %" PRIu64 ". character of decimal64 value \"%.*s\".", u + 1, (int)value_len, value); + } + } + + /* prepare value string without decimal point to easily parse using standard functions */ + valcopy = malloc(size * sizeof *valcopy); + if (!valcopy) { + return ly_err_new(err, LY_EMEM, 0, NULL, NULL, LY_EMEM_MSG); + } + + valcopy[size - 1] = '\0'; + if (fraction) { + memcpy(&valcopy[0], &value[0], fraction); + memcpy(&valcopy[fraction], &value[fraction + 1], len - 1 - (fraction)); + /* add trailing zero characters */ + memset(&valcopy[len - 1], '0', fraction_digits - (len - 1 - fraction)); + } else { + memcpy(&valcopy[0], &value[0], len); + /* add trailing zero characters */ + memset(&valcopy[len], '0', fraction_digits); + } + + ret_val = lyplg_type_parse_int("decimal64", LY_BASE_DEC, INT64_C(-9223372036854775807) - INT64_C(1), + INT64_C(9223372036854775807), valcopy, size - 1, &d, err); + if (!ret_val && ret) { + *ret = d; + } + free(valcopy); + + return ret_val; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_validate_patterns(struct lysc_pattern **patterns, const char *str, size_t str_len, struct ly_err_item **err) +{ + int rc, match_opts; + LY_ARRAY_COUNT_TYPE u; + pcre2_match_data *match_data = NULL; + + LY_CHECK_ARG_RET(NULL, str, err, LY_EINVAL); + + *err = NULL; + + LY_ARRAY_FOR(patterns, u) { + /* match_data needs to be allocated each time because of possible multi-threaded evaluation */ + match_data = pcre2_match_data_create_from_pattern(patterns[u]->code, NULL); + if (!match_data) { + return ly_err_new(err, LY_EMEM, 0, NULL, NULL, LY_EMEM_MSG); + } + + match_opts = PCRE2_ANCHORED; +#ifdef PCRE2_ENDANCHORED + /* PCRE2_ENDANCHORED was added in PCRE2 version 10.30 */ + match_opts |= PCRE2_ENDANCHORED; +#endif + rc = pcre2_match(patterns[u]->code, (PCRE2_SPTR)str, str_len, 0, match_opts, match_data, NULL); + pcre2_match_data_free(match_data); + + if ((rc != PCRE2_ERROR_NOMATCH) && (rc < 0)) { + PCRE2_UCHAR pcre2_errmsg[LY_PCRE2_MSG_LIMIT] = {0}; + + pcre2_get_error_message(rc, pcre2_errmsg, LY_PCRE2_MSG_LIMIT); + + return ly_err_new(err, LY_ESYS, 0, NULL, NULL, "%s", (const char *)pcre2_errmsg); + } else if (((rc == PCRE2_ERROR_NOMATCH) && !patterns[u]->inverted) || + ((rc != PCRE2_ERROR_NOMATCH) && patterns[u]->inverted)) { + char *eapptag = patterns[u]->eapptag ? strdup(patterns[u]->eapptag) : NULL; + + if (patterns[u]->emsg) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", patterns[u]->emsg); + } else { + const char *inverted = patterns[u]->inverted ? "inverted " : ""; + + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, + LY_ERRMSG_NOPATTERN, (int)str_len, str, inverted, patterns[u]->expr); + } + } + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_validate_range(LY_DATA_TYPE basetype, struct lysc_range *range, int64_t value, const char *strval, + size_t strval_len, struct ly_err_item **err) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool is_length; /* length or range */ + + *err = NULL; + is_length = (basetype == LY_TYPE_BINARY || basetype == LY_TYPE_STRING) ? 1 : 0; + + LY_ARRAY_FOR(range->parts, u) { + if (basetype < LY_TYPE_DEC64) { + /* unsigned */ + if ((uint64_t)value < range->parts[u].min_u64) { + char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL; + + if (range->emsg) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg); + } else { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, + is_length ? LY_ERRMSG_NOLENGTH : LY_ERRMSG_NORANGE, (int)strval_len, strval); + } + } else if ((uint64_t)value <= range->parts[u].max_u64) { + /* inside the range */ + return LY_SUCCESS; + } else if (u == LY_ARRAY_COUNT(range->parts) - 1) { + /* we have the last range part, so the value is out of bounds */ + char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL; + + if (range->emsg) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg); + } else { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, + is_length ? LY_ERRMSG_NOLENGTH : LY_ERRMSG_NORANGE, (int)strval_len, strval); + } + } + } else { + /* signed */ + if (value < range->parts[u].min_64) { + char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL; + + if (range->emsg) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg); + } else { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, LY_ERRMSG_NORANGE, (int)strval_len, strval); + } + } else if (value <= range->parts[u].max_64) { + /* inside the range */ + return LY_SUCCESS; + } else if (u == LY_ARRAY_COUNT(range->parts) - 1) { + /* we have the last range part, so the value is out of bounds */ + char *eapptag = range->eapptag ? strdup(range->eapptag) : NULL; + + if (range->emsg) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, "%s", range->emsg); + } else { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, eapptag, LY_ERRMSG_NORANGE, (int)strval_len, strval); + } + } + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_prefix_data_new(const struct ly_ctx *ctx, const void *value, size_t value_len, LY_VALUE_FORMAT format, + const void *prefix_data, LY_VALUE_FORMAT *format_p, void **prefix_data_p) +{ + LY_CHECK_ARG_RET(ctx, value, format_p, prefix_data_p, LY_EINVAL); + + *prefix_data_p = NULL; + return ly_store_prefix_data(ctx, value, value_len, format, prefix_data, format_p, (void **)prefix_data_p); +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_prefix_data_dup(const struct ly_ctx *ctx, LY_VALUE_FORMAT format, const void *orig, void **dup) +{ + LY_CHECK_ARG_RET(NULL, dup, LY_EINVAL); + + *dup = NULL; + if (!orig) { + return LY_SUCCESS; + } + + return ly_dup_prefix_data(ctx, format, orig, (void **)dup); +} + +LIBYANG_API_DEF void +lyplg_type_prefix_data_free(LY_VALUE_FORMAT format, void *prefix_data) +{ + ly_free_prefix_data(format, prefix_data); +} + +static int +type_get_hints_base(uint32_t hints) +{ + /* set allowed base */ + switch (hints & (LYD_VALHINT_DECNUM | LYD_VALHINT_OCTNUM | LYD_VALHINT_HEXNUM)) { + case LYD_VALHINT_DECNUM: + return LY_BASE_DEC; + case LYD_VALHINT_OCTNUM: + return LY_BASE_OCT; + case LYD_VALHINT_HEXNUM: + return LY_BASE_HEX; + default: + /* generic base - decimal by default, hexa if prexed by 0x/0X and octal otherwise if prefixed by 0 */ + return 0; + } +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_check_hints(uint32_t hints, const char *value, size_t value_len, LY_DATA_TYPE type, int *base, + struct ly_err_item **err) +{ + LY_CHECK_ARG_RET(NULL, value || !value_len, err, LY_EINVAL); + + *err = NULL; + if (!value) { + value = ""; + } + + switch (type) { + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: + case LY_TYPE_INT8: + case LY_TYPE_INT16: + case LY_TYPE_INT32: + LY_CHECK_ARG_RET(NULL, base, LY_EINVAL); + + if (!(hints & (LYD_VALHINT_DECNUM | LYD_VALHINT_OCTNUM | LYD_VALHINT_HEXNUM))) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid non-number-encoded %s value \"%.*s\".", + lys_datatype2str(type), (int)value_len, value); + } + *base = type_get_hints_base(hints); + break; + case LY_TYPE_UINT64: + case LY_TYPE_INT64: + LY_CHECK_ARG_RET(NULL, base, LY_EINVAL); + + if (!(hints & LYD_VALHINT_NUM64)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid non-num64-encoded %s value \"%.*s\".", + lys_datatype2str(type), (int)value_len, value); + } + *base = type_get_hints_base(hints); + break; + case LY_TYPE_STRING: + case LY_TYPE_DEC64: + case LY_TYPE_ENUM: + case LY_TYPE_BITS: + case LY_TYPE_BINARY: + case LY_TYPE_IDENT: + case LY_TYPE_INST: + if (!(hints & LYD_VALHINT_STRING)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid non-string-encoded %s value \"%.*s\".", + lys_datatype2str(type), (int)value_len, value); + } + break; + case LY_TYPE_BOOL: + if (!(hints & LYD_VALHINT_BOOLEAN)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid non-boolean-encoded %s value \"%.*s\".", + lys_datatype2str(type), (int)value_len, value); + } + break; + case LY_TYPE_EMPTY: + if (!(hints & LYD_VALHINT_EMPTY)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid non-empty-encoded %s value \"%.*s\".", + lys_datatype2str(type), (int)value_len, value); + } + break; + case LY_TYPE_UNKNOWN: + case LY_TYPE_LEAFREF: + case LY_TYPE_UNION: + LOGINT_RET(NULL); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_check_status(const struct lysc_node *ctx_node, uint16_t val_flags, LY_VALUE_FORMAT format, void *prefix_data, + const char *val_name, struct ly_err_item **err) +{ + LY_ERR ret; + const struct lys_module *mod2; + uint16_t flg1, flg2; + + if (format != LY_VALUE_SCHEMA) { + /* nothing/unable to check */ + return LY_SUCCESS; + } + + mod2 = ((struct lysp_module *)prefix_data)->mod; + + if (mod2 == ctx_node->module) { + /* use flags of the context node since the definition is local */ + flg1 = (ctx_node->flags & LYS_STATUS_MASK) ? (ctx_node->flags & LYS_STATUS_MASK) : LYS_STATUS_CURR; + } else { + /* definition is foreign (deviation, refine), always current */ + flg1 = LYS_STATUS_CURR; + } + flg2 = (val_flags & LYS_STATUS_MASK) ? (val_flags & LYS_STATUS_MASK) : LYS_STATUS_CURR; + + if ((flg1 < flg2) && (ctx_node->module == mod2)) { + ret = ly_err_new(err, LY_EVALID, LYVE_REFERENCE, NULL, NULL, + "A %s definition \"%s\" is not allowed to reference %s value \"%s\".", + flg1 == LYS_STATUS_CURR ? "current" : "deprecated", ctx_node->name, + flg2 == LYS_STATUS_OBSLT ? "obsolete" : "deprecated", val_name); + return ret; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_lypath_check_status(const struct lysc_node *ctx_node, const struct ly_path *path, LY_VALUE_FORMAT format, + void *prefix_data, struct ly_err_item **err) +{ + LY_ERR ret; + LY_ARRAY_COUNT_TYPE u; + const struct lys_module *val_mod; + const struct lysc_node *node; + uint16_t flg1, flg2; + + if (format != LY_VALUE_SCHEMA) { + /* nothing to check */ + return LY_SUCCESS; + } + + val_mod = ((struct lysp_module *)prefix_data)->mod; + if (val_mod == ctx_node->module) { + /* use flags of the context node since the definition is local */ + flg1 = (ctx_node->flags & LYS_STATUS_MASK) ? (ctx_node->flags & LYS_STATUS_MASK) : LYS_STATUS_CURR; + } else { + /* definition is foreign (deviation, refine), always current */ + flg1 = LYS_STATUS_CURR; + } + + LY_ARRAY_FOR(path, u) { + node = path[u].node; + + flg2 = (node->flags & LYS_STATUS_MASK) ? (node->flags & LYS_STATUS_MASK) : LYS_STATUS_CURR; + if ((flg1 < flg2) && (val_mod == node->module)) { + ret = ly_err_new(err, LY_EVALID, LYVE_REFERENCE, NULL, NULL, + "A %s definition \"%s\" is not allowed to reference %s value \"%s\".", + flg1 == LYS_STATUS_CURR ? "current" : "deprecated", ctx_node->name, + flg2 == LYS_STATUS_OBSLT ? "obsolete" : "deprecated", node->name); + return ret; + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_lypath_new(const struct ly_ctx *ctx, const char *value, size_t value_len, uint32_t options, + LY_VALUE_FORMAT format, void *prefix_data, const struct lysc_node *ctx_node, struct lys_glob_unres *unres, + struct ly_path **path, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + uint32_t prefix_opt = 0; + + LY_CHECK_ARG_RET(ctx, ctx, value, ctx_node, path, err, LY_EINVAL); + + *path = NULL; + *err = NULL; + + switch (format) { + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + case LY_VALUE_XML: + prefix_opt = LY_PATH_PREFIX_MANDATORY; + break; + case LY_VALUE_CANON: + case LY_VALUE_LYB: + case LY_VALUE_JSON: + case LY_VALUE_STR_NS: + prefix_opt = LY_PATH_PREFIX_STRICT_INHERIT; + break; + } + + /* parse the value */ + ret = ly_path_parse(ctx, ctx_node, value, value_len, 0, LY_PATH_BEGIN_ABSOLUTE, prefix_opt, LY_PATH_PRED_SIMPLE, &exp); + if (ret) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid instance-identifier \"%.*s\" value - syntax error.", (int)value_len, value); + goto cleanup; + } + + if (options & LYPLG_TYPE_STORE_IMPLEMENT) { + /* implement all prefixes */ + LY_CHECK_GOTO(ret = lys_compile_expr_implement(ctx, exp, format, prefix_data, 1, unres, NULL), cleanup); + } + + /* resolve it on schema tree */ + ret = ly_path_compile(ctx, NULL, ctx_node, NULL, exp, (ctx_node->flags & LYS_IS_OUTPUT) ? + LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_SINGLE, 1, format, prefix_data, path); + if (ret) { + ret = ly_err_new(err, ret, LYVE_DATA, NULL, NULL, + "Invalid instance-identifier \"%.*s\" value - semantic error.", (int)value_len, value); + goto cleanup; + } + +cleanup: + lyxp_expr_free(ctx, exp); + if (ret) { + ly_path_free(ctx, *path); + *path = NULL; + } + + return ret; +} + +LIBYANG_API_DEF void +lyplg_type_lypath_free(const struct ly_ctx *ctx, struct ly_path *path) +{ + ly_path_free(ctx, path); +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_make_implemented(struct lys_module *mod, const char **features, struct lys_glob_unres *unres) +{ + if (mod->implemented) { + return LY_SUCCESS; + } + + LY_CHECK_RET(lys_implement(mod, features, unres)); + LY_CHECK_RET(lys_compile(mod, &unres->ds_unres)); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_identity_isderived(const struct lysc_ident *base, const struct lysc_ident *der) +{ + LY_ARRAY_COUNT_TYPE u; + + assert(base->module->ctx == der->module->ctx); + + LY_ARRAY_FOR(base->derived, u) { + if (der == base->derived[u]) { + return LY_SUCCESS; + } + if (!lyplg_type_identity_isderived(base->derived[u], der)) { + return LY_SUCCESS; + } + } + return LY_ENOTFOUND; +} + +/** + * @brief Try to generate a path to the leafref target with its value to enable the use of hash-based search. + * + * @param[in] path Leafref path. + * @param[in] ctx_node Leafref context node. + * @param[in] format Format of @p path. + * @param[in] prefix_data Prefix data of @p path. + * @param[in] target_val Leafref target value. + * @param[out] target_path Generated path with the target value. + * @return LY_SUCCESS on success. + * @return LY_ENOT if no matching target exists. + * @return LY_ERR on error. + */ +static LY_ERR +lyplg_type_resolve_leafref_get_target_path(const struct lyxp_expr *path, const struct lysc_node *ctx_node, + LY_VALUE_FORMAT format, void *prefix_data, const char *target_val, struct lyxp_expr **target_path) +{ + LY_ERR rc = LY_SUCCESS; + uint8_t oper; + struct ly_path *p = NULL; + char *str_path = NULL, quot; + int len; + ly_bool list_key = 0; + + *target_path = NULL; + + /* compile, has already been so it must succeed */ + oper = (ctx_node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT; + if (ly_path_compile_leafref(ctx_node->module->ctx, ctx_node, NULL, path, oper, LY_PATH_TARGET_MANY, format, + prefix_data, &p)) { + /* the target was found before but is disabled so it was removed */ + return LY_ENOT; + } + + /* check whether we can search for a list instance with a specific key value */ + if (lysc_is_key(p[LY_ARRAY_COUNT(p) - 1].node)) { + if ((LY_ARRAY_COUNT(p) >= 2) && (p[LY_ARRAY_COUNT(p) - 2].node->nodetype == LYS_LIST)) { + if ((path->tokens[path->used - 1] == LYXP_TOKEN_NAMETEST) && + (path->tokens[path->used - 2] == LYXP_TOKEN_OPER_PATH) && + (path->tokens[path->used - 3] == LYXP_TOKEN_NAMETEST)) { + list_key = 1; + } /* else again, should be possible but does not make sense */ + } /* else allowed despite not making sense */ + } + + if (list_key) { + /* get the length of the orig expression without the last "/" and the key node */ + len = path->tok_pos[path->used - 3] + path->tok_len[path->used - 3]; + + /* generate the string path evaluated using hashes */ + quot = strchr(target_val, '\'') ? '\"' : '\''; + if (asprintf(&str_path, "%.*s[%s=%c%s%c]/%s", len, path->expr, path->expr + path->tok_pos[path->used - 1], + quot, target_val, quot, path->expr + path->tok_pos[path->used - 1]) == -1) { + LOGMEM(ctx_node->module->ctx); + rc = LY_EMEM; + goto cleanup; + } + + } else { + /* leaf will not be found using hashes, but generate the path just to unify it */ + assert(p[LY_ARRAY_COUNT(p) - 1].node->nodetype & LYD_NODE_TERM); + + /* generate the string path evaluated using hashes */ + quot = strchr(target_val, '\'') ? '\"' : '\''; + if (asprintf(&str_path, "%s[.=%c%s%c]", path->expr, quot, target_val, quot) == -1) { + LOGMEM(ctx_node->module->ctx); + rc = LY_EMEM; + goto cleanup; + } + } + + /* parse into an expression */ + LY_CHECK_GOTO(lyxp_expr_parse(ctx_node->module->ctx, str_path, 0, 1, target_path), cleanup); + +cleanup: + ly_path_free(ctx_node->module->ctx, p); + free(str_path); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_resolve_leafref(const struct lysc_type_leafref *lref, const struct lyd_node *node, struct lyd_value *value, + const struct lyd_node *tree, struct lyd_node **target, char **errmsg) +{ + LY_ERR rc = LY_SUCCESS; + struct lyxp_expr *target_path = NULL; + struct lyxp_set set = {0}; + const char *val_str, *xp_err_msg; + uint32_t i; + int r; + + LY_CHECK_ARG_RET(NULL, lref, node, value, errmsg, LY_EINVAL); + + if (target) { + *target = NULL; + } + + /* get the canonical value */ + val_str = lyd_value_get_canonical(LYD_CTX(node), value); + + if (!strchr(val_str, '\"') || !strchr(val_str, '\'')) { + /* get the path with the value */ + r = lyplg_type_resolve_leafref_get_target_path(lref->path, node->schema, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, + val_str, &target_path); + if (r == LY_ENOT) { + goto cleanup; + } else if (r) { + rc = r; + goto cleanup; + } + } /* else value with both ' and ", XPath does not support that */ + + /* find the target data instance(s) */ + rc = lyxp_eval(LYD_CTX(node), target_path ? target_path : lref->path, node->schema->module, + LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, node, node, tree, NULL, &set, LYXP_IGNORE_WHEN); + if (rc) { + if (ly_errcode(LYD_CTX(node)) == rc) { + xp_err_msg = ly_errmsg(LYD_CTX(node)); + } else { + xp_err_msg = NULL; + } + + if (xp_err_msg) { + r = asprintf(errmsg, "Invalid leafref value \"%s\" - XPath evaluation error (%s).", val_str, xp_err_msg); + } else { + r = asprintf(errmsg, "Invalid leafref value \"%s\" - XPath evaluation error.", val_str); + } + if (r == -1) { + *errmsg = NULL; + rc = LY_EMEM; + } + goto cleanup; + } + + /* check the result */ + if (target_path) { + /* no or exact match(es) */ + i = 0; + } else { + /* check whether any matches */ + for (i = 0; i < set.used; ++i) { + if (set.val.nodes[i].type != LYXP_NODE_ELEM) { + continue; + } + + if (!lref->plugin->compare(&((struct lyd_node_term *)set.val.nodes[i].node)->value, value)) { + break; + } + } + } + + if (i == set.used) { + /* no match found */ + rc = LY_ENOTFOUND; + if (asprintf(errmsg, LY_ERRMSG_NOLREF_VAL, val_str, lref->path->expr) == -1) { + *errmsg = NULL; + rc = LY_EMEM; + } + goto cleanup; + } + if (target) { + *target = set.val.nodes[i].node; + } + +cleanup: + lyxp_expr_free(LYD_CTX(node), target_path); + lyxp_set_free_content(&set); + return rc; +} diff --git a/src/plugins_types.h b/src/plugins_types.h new file mode 100644 index 0000000..3ec1d3b --- /dev/null +++ b/src/plugins_types.h @@ -0,0 +1,1214 @@ +/** + * @file plugins_types.h + * @author Radek Krejci + * @brief API for (user) types plugins + * + * Copyright (c) 2019 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_PLUGINS_TYPES_H_ +#define LY_PLUGINS_TYPES_H_ + +#include +#include + +#include "config.h" +#include "log.h" +#include "plugins.h" +#include "tree.h" + +#include "tree_edit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_ctx; +struct ly_path; +struct lyd_node; +struct lyd_value; +struct lyd_value_xpath10; +struct lys_module; +struct lys_glob_unres; +struct lysc_ident; +struct lysc_node; +struct lysc_pattern; +struct lysc_range; +struct lysc_type; +struct lysc_type_bits; +struct lysc_type_leafref; + +/** + * @page howtoPluginsTypes Type Plugins + * + * Note that the part of the libyang API here is available only by including a separated `` header + * file. Also note that the type plugins API is versioned separately from libyang itself, so backward incompatible changes + * can come even without changing libyang major version. + * + * YANG allows to define new data types via *typedef* statements or even in leaf's/leaf-list's *type* statements. + * Such types are derived (directly or indirectly) from a set of [YANG built-in types](https://tools.ietf.org/html/rfc7950#section-4.2.4). + * libyang implements all handling of the data values of the YANG types via the Type Plugins API. Internally, there is + * implementation of the built-in types and others can be added as an external plugin (see @ref howtoPlugins). + * + * Type plugin is supposed to + * - store (and canonize) data value, + * - validate it according to the type's restrictions, + * - compare two values (::lyd_value) of the same type, + * - duplicate value (::lyd_value), + * - print it and + * - free the specific data inserted into ::lyd_value. + * + * These tasks are implemented as callbacks provided to libyang via ::lyplg_type_record structures defined as array using + * ::LYPLG_TYPES macro. + * + * All the callbacks are supposed to do not log directly via libyang logger. Instead, they return ::LY_ERR value and + * ::ly_err_item error structure(s) describing the detected error(s) (helper functions ::ly_err_new() and ::ly_err_free() + * are available). + * + * The main functionality is provided via ::lyplg_type_store_clb callback responsible for canonizing and storing + * provided string representation of the value in specified format (XML and JSON supported). Valid value is stored in + * ::lyd_value structure - its union allows to store data as one of the predefined type or in a custom form behind + * the void *ptr member of ::lyd_value structure. The callback is also responsible for storing canonized string + * representation of the value as ::lyd_value._canonical. If the type does not define canonical representation, the original + * representation is stored. In case there are any differences between the representation in specific input types, the plugin + * is supposed to store the value in JSON representation - typically, the difference is in prefix representation and JSON + * format uses directly the module names as prefixes. + * + * Usually, all the validation according to the type's restrictions is done in the store callback. However, in case the type + * requires some validation referencing other entities in the data tree, the optional validation callback + * ::lyplg_type_validate_clb can be implemented. + * + * The stored values can be compared in a specific way by providing ::lyplg_type_compare_clb. In case the best way to compare + * the values is to compare their canonical string representations, the ::lyplg_type_compare_simple() function can be used. + * + * Data duplication is done with ::lyplg_type_dup_clb callbacks. Note that the callback is responsible even for duplicating + * the ::lyd_value._canonical, so the callback must be always present (the canonical value is always present). If there is + * nothing else to duplicate, the plugin can use the generic ::lyplg_type_dup_simple(). + * + * The stored value can be printed into the required format via ::lyplg_type_print_clb implementation. Simple printing + * canonical representation of the value is implemented by ::lyplg_type_print_simple(). + * + * And finally freeing any data stored in the ::lyd_value by the plugin is done by implementation of ::lyplg_type_free_clb. + * Freeing only the canonical string is implemented by ::lyplg_type_free_simple(). + * + * The plugin information contains also the plugin identifier (::lyplg_type.id). This string can serve to identify the + * specific plugin responsible to storing data value. In case the user can recognize the id string, it can access the + * plugin specific data with the appropriate knowledge of its structure. + * + * Besides the mentioned `_simple` functions, libyang provides, as part of the type plugins API, all the callbacks + * implementing the built-in types in the internal plugins: + * + * - [simple callbacks](@ref pluginsTypesSimple) handling only the canonical strings in the value, + * - [binary built-in type](@ref pluginsTypesBinary) + * - [bits built-in type](@ref pluginsTypesBits) + * - [boolean built-in type](@ref pluginsTypesBoolean) + * - [decimal64 built-in type](@ref pluginsTypesDecimal64) + * - [empty built-in type](@ref pluginsTypesEmpty) + * - [enumeration built-in type](@ref pluginsTypesEnumeration) + * - [identityref built-in type](@ref pluginsTypesIdentityref) + * - [instance-identifier built-in type](@ref pluginsTypesInstanceid) + * - [integer built-in types](@ref pluginsTypesInteger) + * - [leafref built-in type](@ref pluginsTypesLeafref) + * - [string built-in type](@ref pluginsTypesString) + * - [union built-in type](@ref pluginsTypesUnion) + * + * And one derived type: + * + * - [xpath1.0 `ietf-yang-types` type](@ref pluginsTypesXpath10) + * + * In addition to these callbacks, the API also provides several functions which can help to implement your own plugin for the + * derived YANG types: + * + * - ::ly_err_new() + * - ::ly_err_free() + * + * - ::lyplg_type_lypath_new() + * - ::lyplg_type_lypath_free() + * + * - ::lyplg_type_prefix_data_new() + * - ::lyplg_type_prefix_data_dup() + * - ::lyplg_type_prefix_data_free() + * - ::lyplg_type_get_prefix() + * + * - ::lyplg_type_check_hints() + * - ::lyplg_type_check_status() + * - ::lyplg_type_lypath_check_status() + * - ::lyplg_type_identity_isderived() + * - ::lyplg_type_identity_module() + * - ::lyplg_type_make_implemented() + * - ::lyplg_type_parse_dec64() + * - ::lyplg_type_parse_int() + * - ::lyplg_type_parse_uint() + * - ::lyplg_type_resolve_leafref() + */ + +/** + * @defgroup pluginsTypes Plugins: Types + * @{ + * + * Structures and functions to for libyang plugins implementing specific YANG types defined in YANG modules. For more + * information, see @ref howtoPluginsTypes. + * + * This part of libyang API is available by including `` header file. + */ + +/** + * @brief Type API version + */ +#define LYPLG_TYPE_API_VERSION 1 + +/** + * @brief Macro to define plugin information in external plugins + * + * Use as follows: + * LYPLG_TYPES = {{}, ..., {0}}; + */ +#define LYPLG_TYPES \ + uint32_t plugins_types_apiver__ = LYPLG_TYPE_API_VERSION; \ + const struct lyplg_type_record plugins_types__[] + +/** + * @brief Check whether specific type value needs to be allocated dynamically. + * + * @param[in] type_val Pointer to specific type value storage. + */ +#define LYPLG_TYPE_VAL_IS_DYN(type_val) \ + (sizeof *(type_val) > LYD_VALUE_FIXED_MEM_SIZE) + +/** + * @brief Prepare value memory for storing a specific type value, may be allocated dynamically. + * + * Must be called for values larger than 8 bytes. + * To be used in ::lyplg_type_store_clb. + * + * @param[in] storage Pointer to the value storage to use (struct ::lyd_value *). + * @param[in,out] type_val Pointer to specific type value structure. + */ +#define LYPLG_TYPE_VAL_INLINE_PREPARE(storage, type_val) \ + (LYPLG_TYPE_VAL_IS_DYN(type_val) \ + ? ((type_val) = ((storage)->dyn_mem = calloc(1, sizeof *(type_val)))) \ + : ((type_val) = memset((storage)->fixed_mem, 0, sizeof *(type_val)))) + +/** + * @brief Destroy a prepared value. + * + * Must be called for values prepared with ::LYPLG_TYPE_VAL_INLINE_PREPARE. + * + * @param[in] type_val Pointer to specific type value structure. + */ +#define LYPLG_TYPE_VAL_INLINE_DESTROY(type_val) \ + do { if (LYPLG_TYPE_VAL_IS_DYN(type_val)) free(type_val); } while(0) + +/** + * @brief Create and fill error structure. + * + * Helper function for various plugin functions to generate error information structure. + * + * @param[in, out] err Pointer to store a new error structure filled according to the input parameters. If the storage + * already contains error information, the new record is appended into the errors list. + * @param[in] ecode Code of the error to fill. In case LY_SUCCESS value, nothing is done and LY_SUCCESS is returned. + * @param[in] vecode Validity error code in case of LY_EVALID error code. + * @param[in] path Path to the node causing the error. + * @param[in] apptag Error-app-tag value. + * @param[in] err_format Format string (same like at printf) or string literal. + * If you want to print just an unknown string, use "%s" for the @p err_format, otherwise undefined behavior may occur + * because the unknown string may contain the % character, which is interpreted as conversion specifier. + * @return The given @p ecode value if the @p err is successfully created. The structure can be freed using ::ly_err_free() + * or passed back from callback into libyang. + * @return LY_EMEM If there is not enough memory for allocating error record, the @p err is not touched in that case. + * @return LY_SUCCESS if @p ecode is LY_SUCCESS, the @p err is not touched in this case. + */ +LIBYANG_API_DECL LY_ERR ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, char *apptag, + const char *err_format, ...) _FORMAT_PRINTF(6, 7); + +/** + * @brief Destructor for the error records created with ::ly_err_new(). + * + * Compatible with the free(), so usable as a generic callback. + * + * @param[in] ptr Error record (::ly_err_item, the void pointer is here only for compatibility with a generic free() + * function) to free. With the record, also all the records (if any) connected after this one are freed. + */ +LIBYANG_API_DECL void ly_err_free(void *ptr); + +/** + * @brief Check that the type is suitable for the parser's hints (if any) in the specified format + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * @param[in] hints Bitmap of [value hints](@ref lydvalhints) of all the allowed value types provided by parsers + * to ::lyplg_type_store_clb. + * @param[in] value Lexical representation of the value to be stored. + * @param[in] value_len Length (number of bytes) of the given \p value. + * @param[in] type Expected base type of the @p value by the caller. + * @param[out] base Pointer to store the numeric base for parsing numeric values using strtol()/strtoll() function. + * Returned (and required) only for numeric @p type values. + * @param[out] err Pointer to store error information in case of failure. + * @return LY_ERR value + */ +LIBYANG_API_DECL LY_ERR lyplg_type_check_hints(uint32_t hints, const char *value, size_t value_len, LY_DATA_TYPE type, + int *base, struct ly_err_item **err); + +/** + * @brief Check that the value of a type is allowed based on its status. + * + * @param[in] ctx_node Context node (which references the value). + * @param[in] val_flags Flags fo the value. + * @param[in] format Format of the value. + * @param[in] prefix_data Prefix data of the value. + * @param[in] val_name Name of the value, only for logging. + * @param[out] err Pointer to store error information in case of failure. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_check_status(const struct lysc_node *ctx_node, uint16_t val_flags, LY_VALUE_FORMAT format, + void *prefix_data, const char *val_name, struct ly_err_item **err); + +/** + * @brief Check that the lypath instance-identifier value is allowed based on the status of the nodes. + * + * @param[in] ctx_node Context node (which references the value). + * @param[in] path Path of the instance-identifier. + * @param[in] format Format of the value. + * @param[in] prefix_data Prefix data of the value. + * @param[out] err Pointer to store error information in case of failure. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_lypath_check_status(const struct lysc_node *ctx_node, const struct ly_path *path, + LY_VALUE_FORMAT format, void *prefix_data, struct ly_err_item **err); + +/** + * @brief Get the corresponding module for the identity value. + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * @param[in] ctx libyang context. + * @param[in] ctx_node Schema node where the value is instantiated to determine the module in case of unprefixed value + * in specific @p format. + * @param[in] prefix Prefix to resolve - identified beginning of a prefix in ::lyplg_type_store_clb's value parameter. + * If NULL, an unprefixed identity is resolved. + * @param[in] prefix_len Length of @p prefix. + * @param[in] format Format of the prefix (::lyplg_type_store_clb's format parameter). + * @param[in] prefix_data Format-specific data (::lyplg_type_store_clb's prefix_data parameter). + * @return Resolved prefix module, + * @return NULL otherwise. + */ +LIBYANG_API_DECL const struct lys_module *lyplg_type_identity_module(const struct ly_ctx *ctx, + const struct lysc_node *ctx_node, const char *prefix, size_t prefix_len, LY_VALUE_FORMAT format, + const void *prefix_data); + +/** + * @brief Implement a module (just like ::lys_set_implemented()), but keep maintaining unresolved items. + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * @param[in] mod Module to implement. + * @param[in] features Array of features to enable. + * @param[in,out] unres Global unres to add to. + * @return LY_ERECOMPILE if the context need to be recompiled, should be returned. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_make_implemented(struct lys_module *mod, const char **features, + struct lys_glob_unres *unres); + +/** + * @brief Get the bitmap size of a bits value bitmap. + * + * Bitmap size is rounded up to the smallest integer size (1, 2, 4, or 8 bytes). + * If more than 8 bytes are needed to hold all the bit positions, no rounding is performed. + * + * @param[in] type Bits type. + * @return Bitmap size in bytes. + */ +LIBYANG_API_DECL size_t lyplg_type_bits_bitmap_size(const struct lysc_type_bits *type); + +/** + * @brief Check whether a particular bit of a bitmap is set. + * + * @param[in] bitmap Bitmap to read from. + * @param[in] size Size of @p bitmap. + * @param[in] bit_position Bit position to check. + * @return Whether the bit is set or not. + */ +LIBYANG_API_DECL ly_bool lyplg_type_bits_is_bit_set(const char *bitmap, size_t size, uint32_t bit_position); + +/** + * @brief Print xpath1.0 token in the specific format. + * + * @param[in] token Token to transform. + * @param[in] tok_len Lenghth of @p token. + * @param[in] is_nametest Whether the token is a nametest, it then always requires a prefix in XML @p get_format. + * @param[in,out] context_mod Current context module, may be updated. + * @param[in] resolve_ctx Context to use for resolving prefixes. + * @param[in] resolve_format Format of the resolved prefixes. + * @param[in] resolve_prefix_data Resolved prefixes prefix data. + * @param[in] get_format Format of the output prefixes. + * @param[in] get_prefix_data Format-specific prefix data for the output. + * @param[out] token_p Printed token. + * @param[out] err Error structure on error. + * @return LY_ERR value. + */ +LIBYANG_API_DEF LY_ERR lyplg_type_xpath10_print_token(const char *token, uint16_t tok_len, ly_bool is_nametest, + const struct lys_module **context_mod, const struct ly_ctx *resolve_ctx, LY_VALUE_FORMAT resolve_format, + const void *resolve_prefix_data, LY_VALUE_FORMAT get_format, void *get_prefix_data, char **token_p, + struct ly_err_item **err); + +/** + * @brief Get format-specific prefix for a module. + * + * Use only in implementations of ::lyplg_type_print_clb which provide all the necessary parameters for this function. + * + * @param[in] mod Module whose prefix to get - the module somehow connected with the value to print. + * @param[in] format Format of the prefix (::lyplg_type_print_clb's format parameter). + * @param[in] prefix_data Format-specific data (::lyplg_type_print_clb's prefix_data parameter). + * @return Module prefix to print. + * @return NULL on error. + */ +LIBYANG_API_DECL const char *lyplg_type_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data); + +/** + * @brief Store used prefixes in a string into an internal libyang structure used in ::lyd_value. + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * If @p prefix_data_p are non-NULL, they are treated as valid according to the @p format_p and new possible + * prefixes are simply added. This way it is possible to store prefix data for several strings together. + * + * @param[in] ctx libyang context. + * @param[in] value Value to be parsed. + * @param[in] value_len Length of @p value. + * @param[in] format Format of the prefixes in the value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). + * @param[in,out] format_p Resulting format of the prefixes. + * @param[in,out] prefix_data_p Resulting prefix data for the value in format @p format_p. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_prefix_data_new(const struct ly_ctx *ctx, const void *value, size_t value_len, + LY_VALUE_FORMAT format, const void *prefix_data, LY_VALUE_FORMAT *format_p, void **prefix_data_p); +/** + * @brief Duplicate prefix data. + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * @param[in] ctx libyang context. + * @param[in] format Format of the prefixes in the value. + * @param[in] orig Prefix data to duplicate. + * @param[out] dup Duplicated prefix data. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_prefix_data_dup(const struct ly_ctx *ctx, LY_VALUE_FORMAT format, const void *orig, + void **dup); + +/** + * @brief Free internal prefix data. + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * @param[in] format Format of the prefixes. + * @param[in] prefix_data Format-specific data to free. + */ +LIBYANG_API_DECL void lyplg_type_prefix_data_free(LY_VALUE_FORMAT format, void *prefix_data); + +/** + * @brief Helper function to create internal schema path representation for instance-identifier value representation. + * + * Use only in implementations of ::lyplg_type_store_clb which provide all the necessary parameters for this function. + * + * @param[in] ctx libyang Context + * @param[in] value Lexical representation of the value to be stored. + * @param[in] value_len Length (number of bytes) of the given @p value. + * @param[in] options [Type plugin store options](@ref plugintypestoreopts). + * @param[in] format Input format of the value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). + * @param[in] ctx_node The @p value schema context node. + * @param[in,out] unres Global unres structure for newly implemented modules. + * @param[out] path Pointer to store the created structure representing the schema path from the @p value. + * @param[out] err Pointer to store the error information provided in case of failure. + * @return LY_SUCCESS on success, + * @return LY_ERECOMPILE if the context need to be recompiled, should be returned. + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_lypath_new(const struct ly_ctx *ctx, const char *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, const struct lysc_node *ctx_node, + struct lys_glob_unres *unres, struct ly_path **path, struct ly_err_item **err); + +/** + * @brief Free ly_path structure used by instanceid value representation. + * + * The ly_path representation can be created by ::lyplg_type_lypath_new(). + * + * @param[in] ctx libyang context. + * @param[in] path The structure ([sized array](@ref sizedarrays)) to free. + */ +LIBYANG_API_DECL void lyplg_type_lypath_free(const struct ly_ctx *ctx, struct ly_path *path); + +/** + * @brief Print xpath1.0 value in the specific format. + * + * @param[in] xp_val xpath1.0 value structure. + * @param[in] format Format to print in. + * @param[in] prefix_data Format-specific prefix data. + * @param[out] str_value Printed value. + * @param[out] err Error structure on error. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_print_xpath10_value(const struct lyd_value_xpath10 *xp_val, LY_VALUE_FORMAT format, + void *prefix_data, char **str_value, struct ly_err_item **err); + +/** + * @defgroup plugintypestoreopts Plugins: Type store callback options. + * + * Options applicable to ::lyplg_type_store_clb(). + * + * @{ + */ +#define LYPLG_TYPE_STORE_DYNAMIC 0x01 /**< Value was dynamically allocated in its exact size and is supposed to be freed or + directly inserted into the context's dictionary (e.g. in case of canonization). + In any case, the caller of the callback does not free the provided + value after calling the type's store callback with this option. */ +#define LYPLG_TYPE_STORE_IMPLEMENT 0x02 /**< If a foreign module is needed to be implemented to successfully instantiate + the value, make the module implemented. */ +/** @} plugintypestoreopts */ + +/** + * @brief Callback to store the given @p value according to the given @p type. + * + * Value must always be correctly stored meaning all the other type callbacks (such as print or compare) + * must function as expected. However, ::lyd_value._canonical can be left NULL and will be generated + * and stored on-demand. But if @p format is ::LY_VALUE_CANON (or another, which must be equal to the canonical + * value), the canonical value should be stored so that it does not have to be generated later. + * + * Note that the @p value is not necessarily used whole (may not be zero-terminated if a string). The provided + * @p value_len is always correct. All store functions have to free a dynamically allocated @p value in all + * cases (even on error). + * + * @param[in] ctx libyang context + * @param[in] type Type of the value being stored. + * @param[in] value Value to be stored. + * @param[in] value_len Length (number of bytes) of the given @p value. + * @param[in] options [Type plugin store options](@ref plugintypestoreopts). + * @param[in] format Input format of the value, see the description for details. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). + * @param[in] hints Bitmap of [value hints](@ref lydvalhints) of all the allowed value types. + * @param[in] ctx_node Schema context node of @p value, may be NULL for metadata. + * @param[out] storage Storage for the value in the type's specific encoding. Except for _canonical_, all the members + * should be filled by the plugin (if it fills them at all). + * @param[in,out] unres Global unres structure for newly implemented modules. + * @param[out] err Optionally provided error information in case of failure. If not provided to the caller, a generic + * error message is prepared instead. The error structure can be created by ::ly_err_new(). + * @return LY_SUCCESS on success, + * @return LY_EINCOMPLETE in case the ::lyplg_type_validate_clb should be called to finish value validation in data, + * @return LY_ERR value on error, @p storage must not have any pointers to dynamic memory. + */ +LIBYANG_API_DECL typedef LY_ERR (*lyplg_type_store_clb)(const struct ly_ctx *ctx, const struct lysc_type *type, + const void *value, size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Callback to validate the stored value in data. + * + * This callback is optional for types that can only be validated in a data tree. It must be called and succeed + * in case the ::lyplg_type_store_clb callback returned ::LY_EINCOMPLETE for the value to be valid. However, this + * callback can be called even in other cases (such as separate/repeated validation). + * + * @param[in] ctx libyang context + * @param[in] type Original type of the value (not necessarily the stored one) being validated. + * @param[in] ctx_node The value data context node for validation. + * @param[in] tree External data tree (e.g. when validating RPC/Notification) with possibly referenced data. + * @param[in,out] storage Storage of the value successfully filled by ::lyplg_type_store_clb. May be modified. + * @param[out] err Optionally provided error information in case of failure. If not provided to the caller, a generic + * error message is prepared instead. The error structure can be created by ::ly_err_new(). + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL typedef LY_ERR (*lyplg_type_validate_clb)(const struct ly_ctx *ctx, const struct lysc_type *type, + const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err); + +/** + * @brief Callback for comparing 2 values of the same type. + * + * In case the value types (::lyd_value.realtype) are different, ::LY_ENOT must always be returned. + * It can be assumed that the same context (dictionary) was used for storing both values. + * + * @param[in] val1 First value to compare. + * @param[in] val2 Second value to compare. + * @return LY_SUCCESS if values are same (according to the type's definition of being same). + * @return LY_ENOT if values differ. + */ +typedef LY_ERR (*lyplg_type_compare_clb)(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Unused callback for sorting values. + * + * @param[in] val1 First value to compare. + * @param[in] val2 Second value to compare. + * @return -1 if val1 < val2, + * @return 0 if val1 == val2, + * @return 1 if val1 > val2. + */ +typedef int (*lyplg_type_sort_clb)(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Callback for getting the value of the data stored in @p value. + * + * Canonical value (@p format of ::LY_VALUE_CANON) must always be a zero-terminated const string stored in + * the dictionary. The ::lyd_value._canonical member should be used for storing (caching) it. + * + * @param[in] ctx libyang context for storing the canonical value. May not be set for ::LY_VALUE_LYB format. + * @param[in] value Value to print. + * @param[in] format Format in which the data are supposed to be printed. Formats ::LY_VALUE_SCHEMA and + * ::LY_VALUE_SCHEMA_RESOLVED are not supported and should not be implemented. + * @param[in] prefix_data Format-specific data for processing prefixes. In case of using one of the built-in's print + * callback (or ::lyplg_type_print_simple()), the argument is just simply passed in. If you need to handle prefixes + * in the value on your own, there is ::lyplg_type_get_prefix() function to help. + * @param[out] dynamic Flag if the returned value is dynamically allocated. In such a case the caller is responsible + * for freeing it. Will not be set and should be ignored for @p format ::LY_VALUE_CANON. + * @param[out] value_len Optional returned value length in bytes. For strings it EXCLUDES the terminating zero. + * @return Pointer to @p value in the specified @p format. According to the returned @p dynamic flag, caller + * can be responsible for freeing allocated memory. + * @return NULL in case of error. + */ +typedef const void *(*lyplg_type_print_clb)(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Callback to duplicate data in the data structure. + * + * @param[in] ctx libyang context of the @p dup. Note that the context of @p original and @p dup might not be the same. + * @param[in] original Original data structure to be duplicated. + * @param[in,out] dup Prepared data structure to be filled with the duplicated data of @p original. + * @return LY_SUCCESS after successful duplication. + * @return LY_ERR value on error. + */ +typedef LY_ERR (*lyplg_type_dup_clb)(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup); + +/** + * @brief Callback for freeing the user type values stored by ::lyplg_type_store_clb. + * + * Note that this callback is responsible also for freeing the canonized member in the @p value. + * + * @param[in] ctx libyang ctx to enable correct manipulation with values that are in the dictionary. + * @param[in,out] value Value structure to free the data stored there by the plugin's ::lyplg_type_store_clb callback + */ +typedef void (*lyplg_type_free_clb)(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Hold type-specific functions for various operations with the data values. + * + * libyang includes set of plugins for all the built-in types. They are, by default, inherited to the derived types. + * However, if the user type plugin for the specific type is loaded, the plugin can provide it's own functions. + * The built-in types plugin callbacks are public, so even the user type plugins can use them to do part of their own + * functionality. + */ +struct lyplg_type { + const char *id; /**< Plugin identification (mainly for distinguish incompatible versions when + used by external tools) */ + lyplg_type_store_clb store; /**< store and canonize the value in the type-specific way */ + lyplg_type_validate_clb validate; /**< optional, validate the value in the type-specific way in data */ + lyplg_type_compare_clb compare; /**< comparison callback to compare 2 values of the same type */ + lyplg_type_sort_clb sort; /**< unused comparison callback for sorting values */ + lyplg_type_print_clb print; /**< printer callback to get string representing the value */ + lyplg_type_dup_clb duplicate; /**< data duplication callback */ + lyplg_type_free_clb free; /**< optional function to free the type-spceific way stored value */ + int32_t lyb_data_len; /**< Length of the data in [LYB format](@ref howtoDataLYB). + For variable-length is set to -1. */ +}; + +struct lyplg_type_record { + /* plugin identification */ + const char *module; /**< name of the module where the type is defined (top-level typedef) */ + const char *revision; /**< optional module revision - if not specified, the plugin applies to any revision, + which is not an optimal approach due to a possible future revisions of the module. + Instead, there should be defined multiple items in the plugins list, each with the + different revision, but all with the same pointer to the plugin functions. The + only valid use case for the NULL revision is the case the module has no revision. */ + const char *name; /**< name of the typedef */ + + /* runtime data */ + struct lyplg_type plugin; /**< data to utilize plugin implementation */ +}; + +/** + * @defgroup pluginsTypesSimple Plugins: Simple Types Callbacks + * @ingroup pluginsTypes + * @{ + * + * Simple functions implementing @ref howtoPluginsTypes callbacks handling types that allocate no dynamic + * value and always generate their canonical value (::lyd_value._canonical). + */ + +/** + * @brief Implementation of ::lyplg_type_compare_clb for a generic simple type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_simple(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for a generic simple type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_simple(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_dup_clb for a generic simple type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_simple(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of ::lyplg_type_free_clb for a generic simple type. + */ +LIBYANG_API_DECL void lyplg_type_free_simple(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesSimple */ + +/** + * @defgroup pluginsTypesBinary Plugins: Binary built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used to implement binary built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in binary type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_binary(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in binary type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_binary(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in binary type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_binary(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the built-in binary type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_binary(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of ::lyplg_type_free_clb for the built-in binary type. + */ +LIBYANG_API_DECL void lyplg_type_free_binary(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesBinary */ + +/** + * @defgroup pluginsTypesBits Plugins: Bits built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement bits built-in type. + */ + +/** + * @brief Implementation of the ::lyplg_type_store_clb for the built-in bits type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_bits(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of the ::lyplg_type_compare_clb for the built-in bits type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_bits(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of the ::lyplg_type_print_clb for the built-in bits type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_bits(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of the ::lyplg_type_dup_clb for the built-in bits type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_bits(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of the ::lyplg_type_free_clb for the built-in bits type. + */ +LIBYANG_API_DECL void lyplg_type_free_bits(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesBits */ + +/** + * @defgroup pluginsTypesBoolean Plugins: Boolean built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement boolean built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in boolean type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_boolean(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in boolean type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_boolean(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in boolean type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_boolean(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** @} pluginsTypesBoolean */ + +/** + * @defgroup pluginsTypesDecimal64 Plugins: Decimal64 built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement decimal64 built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in decimal64 type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_decimal64(const struct ly_ctx *ctx, const struct lysc_type *type, + const void *value, size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in decimal64 type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_decimal64(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in decimal64 type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_decimal64(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** @} pluginsTypesDecimal64 */ + +/** + * @defgroup pluginsTypesEmpty Plugins: Empty built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement empty built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in empty type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_empty(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** @} pluginsTypesEmpty */ + +/** + * @defgroup pluginsTypesEnumeration Plugins: Enumeration built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement enumeration built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in enumeration type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_enum(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in enumeration type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_enum(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** @} pluginsTypesEnumeration */ + +/** + * @defgroup pluginsTypesIdentityref Plugins: Identityref built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement identityref built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in identityref type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_identityref(const struct ly_ctx *ctx, const struct lysc_type *type, + const void *value, size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in identityref type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_identityref(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in identityref type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_identityref(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** @} pluginsTypesIdentityref */ + +/** + * @defgroup pluginsTypesInstanceid Plugins: Instance-identifier built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement instance-identifier built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in instance-identifier type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_instanceid(const struct ly_ctx *ctx, const struct lysc_type *type, + const void *value, size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_validate_clb for the built-in instance-identifier type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_validate_instanceid(const struct ly_ctx *ctx, const struct lysc_type *type, + const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in instance-identifier type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_instanceid(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in instance-identifier type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_instanceid(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the built-in instance-identifier type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_instanceid(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of ::lyplg_type_free_clb for the built-in instance-identifier type. + */ +LIBYANG_API_DECL void lyplg_type_free_instanceid(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesInstanceid */ + +/** + * @defgroup pluginsTypesInteger Plugins: Integer built-in types callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement integer built-in types. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in signed integer types. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_int(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in signed integer types. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_int(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in signed integer types. + */ +LIBYANG_API_DECL const void *lyplg_type_print_int(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in unsigned integer types. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_uint(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in unsigned integer types. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_uint(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in unsigned integer types. + */ +LIBYANG_API_DECL const void *lyplg_type_print_uint(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** @} pluginsTypesInteger */ + +/** + * @defgroup pluginsTypesLeafref Plugins: Leafref built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement leafref built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in leafref type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_leafref(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in leafref type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_leafref(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in leafref type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_leafref(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the built-in leafref type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_leafref(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of ::lyplg_type_validate_clb for the built-in leafref type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_validate_leafref(const struct ly_ctx *ctx, const struct lysc_type *type, + const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_free_clb for the built-in leafref type. + */ +LIBYANG_API_DECL void lyplg_type_free_leafref(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesLeafref */ + +/** + * @defgroup pluginsTypesString Plugins: String built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement string built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in string type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_string(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** @} pluginsTypesString */ + +/** + * @defgroup pluginsTypesUnion Plugins: Union built-in type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement union built-in type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the built-in union type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_union(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the built-in union type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_union(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the built-in union type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_union(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the built-in union type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_union(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of ::lyplg_type_validate_clb for the built-in union type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_validate_union(const struct ly_ctx *ctx, const struct lysc_type *type, + const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_free_clb for the built-in union type. + */ +LIBYANG_API_DECL void lyplg_type_free_union(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesUnion */ + +/** + * @defgroup pluginsTypesXpath10 Plugins: xpath1.0 `ietf-yang-types` type callbacks + * @ingroup pluginsTypes + * @{ + * + * Callbacks used (besides the [simple callbacks](@ref pluginsTypesSimple)) to implement xpath1.0 derived type. + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ietf-yang-types xpath1.0 type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_xpath10(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err); + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ietf-yang-types xpath1.0 type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_compare_xpath10(const struct lyd_value *val1, const struct lyd_value *val2); + +/** + * @brief Implementation of ::lyplg_type_print_clb for the ietf-yang-types xpath1.0 type. + */ +LIBYANG_API_DECL const void *lyplg_type_print_xpath10(const struct ly_ctx *ctx, const struct lyd_value *value, + LY_VALUE_FORMAT format, void *prefix_data, ly_bool *dynamic, size_t *value_len); + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the ietf-yang-types xpath1.0 type. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_dup_xpath10(const struct ly_ctx *ctx, const struct lyd_value *original, + struct lyd_value *dup); + +/** + * @brief Implementation of ::lyplg_type_free_clb for the ietf-yang-types xpath1.0 type. + */ +LIBYANG_API_DECL void lyplg_type_free_xpath10(const struct ly_ctx *ctx, struct lyd_value *value); + +/** @} pluginsTypesXpath10 */ + +/** + * @brief Unsigned integer value parser and validator. + * + * @param[in] datatype Type of the integer for logging. + * @param[in] base Base of the integer's lexical representation. In case of built-in types, data must be represented in decimal format (base 10), + * but default values in schemas can be represented also as hexadecimal or octal values (base 0). + * @param[in] min Lower bound of the type. + * @param[in] max Upper bound of the type. + * @param[in] value Value string to parse. + * @param[in] value_len Length of the @p value (mandatory parameter). + * @param[out] ret Parsed integer value (optional). + * @param[out] err Error information in case of failure. The error structure can be freed by ::ly_err_free(). + * @return LY_ERR value according to the result of the parsing and validation. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_parse_int(const char *datatype, int base, int64_t min, int64_t max, const char *value, + size_t value_len, int64_t *ret, struct ly_err_item **err); + +/** + * @brief Unsigned integer value parser and validator. + * + * @param[in] datatype Type of the unsigned integer for logging. + * @param[in] base Base of the integer's lexical representation. In case of built-in types, data must be represented in decimal format (base 10), + * but default values in schemas can be represented also as hexadecimal or octal values (base 0). + * @param[in] max Upper bound of the type. + * @param[in] value Value string to parse. + * @param[in] value_len Length of the @p value (mandatory parameter). + * @param[out] ret Parsed unsigned integer value (optional). + * @param[out] err Error information in case of failure. The error structure can be freed by ::ly_err_free(). + * @return LY_ERR value according to the result of the parsing and validation. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_parse_uint(const char *datatype, int base, uint64_t max, const char *value, + size_t value_len, uint64_t *ret, struct ly_err_item **err); + +/** + * @brief Convert a string with a decimal64 value into libyang representation: + * ret = value * 10^fraction-digits + * + * @param[in] fraction_digits Fraction-digits of the decimal64 type. + * @param[in] value Value string to parse. + * @param[in] value_len Length of the @p value (mandatory parameter). + * @param[out] ret Parsed decimal64 value representing original value * 10^fraction-digits (optional). + * @param[out] err Error information in case of failure. The error structure can be freed by ::ly_err_free(). + * @return LY_ERR value according to the result of the parsing and validation. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_parse_dec64(uint8_t fraction_digits, const char *value, size_t value_len, int64_t *ret, + struct ly_err_item **err); + +/** + * @brief Decide if the @p derived identity is derived from (based on) the @p base identity. + * + * @param[in] base Expected base identity. + * @param[in] derived Expected derived identity. + * @return LY_SUCCESS if @p derived IS based on the @p base identity. + * @return LY_ENOTFOUND if @p derived IS NOT not based on the @p base identity. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_identity_isderived(const struct lysc_ident *base, const struct lysc_ident *derived); + +/** + * @brief Data type validator for a range/length-restricted values. + * + * @param[in] basetype Base built-in type of the type with the range specified to get know if the @p range structure represents range or length restriction. + * @param[in] range Range (length) restriction information. + * @param[in] value Value to check. In case of basetypes using unsigned integer values, the value is actually cast to uint64_t. + * @param[in] strval String representation of the @p value for error logging. + * @param[in] strval_len Length of @p strval. + * @param[out] err Error information in case of failure. The error structure can be freed by ::ly_err_free(). + * @return LY_ERR value according to the result of the validation. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_validate_range(LY_DATA_TYPE basetype, struct lysc_range *range, int64_t value, + const char *strval, size_t strval_len, struct ly_err_item **err); + +/** + * @brief Data type validator for pattern-restricted string values. + * + * @param[in] patterns ([Sized array](@ref sizedarrays)) of the compiled list of pointers to the pattern restrictions. + * The array can be found in the ::lysc_type_str.patterns structure. + * @param[in] str String to validate. + * @param[in] str_len Length (number of bytes) of the string to validate (mandatory). + * @param[out] err Error information in case of failure or non-matching @p str. The error structure can be freed by ::ly_err_free(). + * @return LY_SUCCESS when @p matches all the patterns. + * @return LY_EVALID when @p does not match any of the patterns. + * @return LY_ESYS in case of PCRE2 error. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_validate_patterns(struct lysc_pattern **patterns, const char *str, size_t str_len, + struct ly_err_item **err); + +/** + * @brief Find leafref target in data. + * + * @param[in] lref Leafref type. + * @param[in] node Context node. + * @param[in] value Target value. + * @param[in] tree Full data tree to search in. + * @param[out] target Optional found target. + * @param[out] errmsg Error message in case of error. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_resolve_leafref(const struct lysc_type_leafref *lref, const struct lyd_node *node, + struct lyd_value *value, const struct lyd_node *tree, struct lyd_node **target, char **errmsg); + +/** @} pluginsTypes */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PLUGINS_TYPES_H_ */ diff --git a/src/plugins_types/binary.c b/src/plugins_types/binary.c new file mode 100644 index 0000000..519ec2e --- /dev/null +++ b/src/plugins_types/binary.c @@ -0,0 +1,466 @@ +/** + * @file binary.c + * @author Radek Krejci + * @brief Built-in binary type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesBinary binary (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | binary value size | yes | `void *` | value in binary | + */ + +/** + * @brief base64 encode table + */ +static const char b64_etable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * @brief Encode binary value into a base64 string value. + * + * Reference https://tools.ietf.org/html/rfc4648#section-4 + * + * @param[in] ctx libyang context. + * @param[in] data Binary data value. + * @param[in] size Size of @p data. + * @param[out] str Encoded base64 string. + * @param[out] str_len Length of returned @p str. + * @return LY_ERR value. + */ +static LY_ERR +binary_base64_encode(const struct ly_ctx *ctx, const char *data, size_t size, char **str, size_t *str_len) +{ + uint32_t i; + char *ptr; + + *str_len = (size + 2) / 3 * 4; + *str = malloc(*str_len + 1); + LY_CHECK_ERR_RET(!*str, LOGMEM(ctx), LY_EMEM); + if (!(*str_len)) { + **str = 0; + return LY_SUCCESS; + } + + ptr = *str; + for (i = 0; i + 2 < size; i += 3) { + *ptr++ = b64_etable[(data[i] >> 2) & 0x3F]; + *ptr++ = b64_etable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)]; + *ptr++ = b64_etable[((data[i + 1] & 0xF) << 2) | ((int)(data[i + 2] & 0xC0) >> 6)]; + *ptr++ = b64_etable[data[i + 2] & 0x3F]; + } + if (i < size) { + *ptr++ = b64_etable[(data[i] >> 2) & 0x3F]; + if (i == (size - 1)) { + *ptr++ = b64_etable[((data[i] & 0x3) << 4)]; + *ptr++ = '='; + } else { + *ptr++ = b64_etable[((data[i] & 0x3) << 4) | ((int)(data[i + 1] & 0xF0) >> 4)]; + *ptr++ = b64_etable[((data[i + 1] & 0xF) << 2)]; + } + *ptr++ = '='; + } + *ptr = '\0'; + + return LY_SUCCESS; +} + +/** + * @brief base64 decode table + */ +static const int b64_dtable[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, + 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +/** + * @brief Decode the binary value from a base64 string value. + * + * Reference https://tools.ietf.org/html/rfc4648#section-4 + * + * @param[in] value Base64-encoded string value. + * @param[in] value_len Length of @p value. + * @param[out] data Decoded binary value. + * @param[out] size Size of @p data. + * @return LY_ERR value. + */ +static LY_ERR +binary_base64_decode(const char *value, size_t value_len, void **data, size_t *size) +{ + unsigned char *ptr = (unsigned char *)value; + uint32_t pad_chars, octet_count; + char *str; + + if (!value_len || (ptr[value_len - 1] != '=')) { + pad_chars = 0; + } else if (ptr[value_len - 2] == '=') { + pad_chars = 1; + } else { + pad_chars = 2; + } + + octet_count = ((value_len + 3) / 4 - (pad_chars ? 1 : 0)) * 4; + *size = octet_count / 4 * 3 + pad_chars; + + str = malloc(*size + 1); + LY_CHECK_RET(!str, LY_EMEM); + str[*size] = '\0'; + + for (uint32_t i = 0, j = 0; i < octet_count; i += 4) { + int n = b64_dtable[ptr[i]] << 18 | b64_dtable[ptr[i + 1]] << 12 | b64_dtable[ptr[i + 2]] << 6 | b64_dtable[ptr[i + 3]]; + + str[j++] = n >> 16; + str[j++] = n >> 8 & 0xFF; + str[j++] = n & 0xFF; + } + if (pad_chars) { + int n = b64_dtable[ptr[octet_count]] << 18 | b64_dtable[ptr[octet_count + 1]] << 12; + + str[*size - pad_chars] = n >> 16; + + if (pad_chars == 2) { + n |= b64_dtable[ptr[octet_count + 2]] << 6; + n >>= 8 & 0xFF; + str[*size - pad_chars + 1] = n; + } + } + + *data = str; + return LY_SUCCESS; +} + +/** + * @brief Validate a base64 string. + * + * @param[in] value Value to validate. + * @param[in] value_len Length of @p value. + * @param[in] type type of the value. + * @param[out] err Error information. + * @return LY_ERR value. + */ +static LY_ERR +binary_base64_validate(const char *value, size_t value_len, const struct lysc_type_bin *type, struct ly_err_item **err) +{ + uint32_t idx, pad; + + /* check correct characters in base64 */ + idx = 0; + while ((idx < value_len) && + ((('A' <= value[idx]) && (value[idx] <= 'Z')) || + (('a' <= value[idx]) && (value[idx] <= 'z')) || + (('0' <= value[idx]) && (value[idx] <= '9')) || + ('+' == value[idx]) || ('/' == value[idx]))) { + idx++; + } + + /* find end of padding */ + pad = 0; + while ((idx + pad < value_len) && (pad < 2) && (value[idx + pad] == '=')) { + pad++; + } + + /* check if value is valid base64 value */ + if (value_len != idx + pad) { + if (isprint(value[idx + pad])) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid Base64 character '%c'.", value[idx + pad]); + } else { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid Base64 character 0x%x.", value[idx + pad]); + } + } + + if (value_len & 3) { + /* base64 length must be multiple of 4 chars */ + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Base64 encoded value length must be divisible by 4."); + } + + /* length restriction of the binary value */ + if (type->length) { + const uint32_t octet_count = ((idx + pad) / 4) * 3 - pad; + + LY_CHECK_RET(lyplg_type_validate_range(LY_TYPE_BINARY, type->length, octet_count, value, value_len, err)); + } + + return LY_SUCCESS; +} + +/** + * @brief Remove all newlines from a base64 string if present. + * + * @param[in,out] value Value, may be dynamic and modified. + * @param[in,out] value_len Length of @p value, is updated. + * @param[in,out] options Type options, are updated. + * @param[out] err Error information. + * @return LY_ERR value. + */ +static LY_ERR +binary_base64_newlines(char **value, size_t *value_len, uint32_t *options, struct ly_err_item **err) +{ + char *val; + size_t len; + + if ((*value_len < 65) || ((*value)[64] != '\n')) { + /* no newlines */ + return LY_SUCCESS; + } + + if (!(*options & LYPLG_TYPE_STORE_DYNAMIC)) { + /* make the value dynamic so we can modify it */ + *value = strndup(*value, *value_len); + LY_CHECK_RET(!*value, LY_EMEM); + *options |= LYPLG_TYPE_STORE_DYNAMIC; + } + + val = *value; + len = *value_len; + while (len > 64) { + if (val[64] != '\n') { + /* missing, error */ + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Newlines are expected every 64 Base64 characters."); + } + + /* remove the newline */ + memmove(val + 64, val + 65, len - 64); + --(*value_len); + val += 64; + len -= 65; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_binary(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_bin *type_bin = (struct lysc_type_bin *)type; + struct lyd_value_binary *val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* store value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + val->data = (void *)value; + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + } else if (value_len) { + val->data = malloc(value_len); + LY_CHECK_ERR_GOTO(!val->data, ret = LY_EMEM, cleanup); + memcpy(val->data, value, value_len); + } else { + val->data = strdup(""); + LY_CHECK_ERR_GOTO(!val->data, ret = LY_EMEM, cleanup); + } + + /* store size */ + val->size = value_len; + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + if (format != LY_VALUE_CANON) { + /* accept newline every 64 characters (PEM data) */ + ret = binary_base64_newlines((char **)&value, &value_len, &options, err); + LY_CHECK_GOTO(ret, cleanup); + + /* validate */ + ret = binary_base64_validate(value, value_len, type_bin, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* get the binary value */ + ret = binary_base64_decode(value, value_len, &val->data, &val->size); + LY_CHECK_GOTO(ret, cleanup); + + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_binary(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_binary(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_binary *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + if ((v1->size != v2->size) || memcmp(v1->data, v2->data, v1->size)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_binary(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_binary *val; + char *ret; + size_t ret_len = 0; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = val->size; + } + return val->data; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* get the base64 string value */ + if (binary_base64_encode(ctx, val->data, val->size, &ret, &ret_len)) { + return NULL; + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = ret_len ? ret_len : strlen(value->_canonical); + } + return value->_canonical; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_binary(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_binary *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + + dup_val->data = orig_val->size ? malloc(orig_val->size) : strdup(""); + LY_CHECK_ERR_GOTO(!dup_val->data, ret = LY_EMEM, error); + + memcpy(dup_val->data, orig_val->data, orig_val->size); + dup_val->size = orig_val->size; + dup->realtype = original->realtype; + + return LY_SUCCESS; + +error: + lyplg_type_free_binary(ctx, dup); + return ret; +} + +LIBYANG_API_DEF void +lyplg_type_free_binary(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_binary *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + free(val->data); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for binray type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_binary[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_BINARY_STR, + + .plugin.id = "libyang 2 - binary, version 1", + .plugin.store = lyplg_type_store_binary, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_binary, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_binary, + .plugin.duplicate = lyplg_type_dup_binary, + .plugin.free = lyplg_type_free_binary, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/bits.c b/src/plugins_types/bits.c new file mode 100644 index 0000000..04adace --- /dev/null +++ b/src/plugins_types/bits.c @@ -0,0 +1,510 @@ +/** + * @file bits.c + * @author Radek Krejci + * @brief Built-in bits type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesBits bits (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | returned by ::lyplg_type_bits_bitmap_size() | yes | pointer to integer type of the specific size, if size more than 8 use `char *` | bitmap of the set bits | + */ + +/** + * @brief Get the position of the last bit. + */ +#define BITS_LAST_BIT_POSITION(type_bits) (type_bits->bits[LY_ARRAY_COUNT(type_bits->bits) - 1].position) + +/** + * @brief Get a specific byte in a bitmap. + */ +#ifdef IS_BIG_ENDIAN +# define BITS_BITMAP_BYTE(bitmap, size, idx) (bitmap + (size - 1) - idx) +#else +# define BITS_BITMAP_BYTE(bitmap, size, idx) (bitmap + idx) +#endif + +LIBYANG_API_DEF size_t +lyplg_type_bits_bitmap_size(const struct lysc_type_bits *type) +{ + size_t needed_bytes, size; + + LY_CHECK_ARG_RET(NULL, type, type->basetype == LY_TYPE_BITS, 0); + + /* minimum needed bytes to hold all the bit positions (which start at 0) */ + needed_bytes = ((BITS_LAST_BIT_POSITION(type) + 1) / 8) + ((BITS_LAST_BIT_POSITION(type) + 1) % 8 ? 1 : 0); + LY_CHECK_ERR_RET(!needed_bytes, LOGINT(NULL), 0); + + if ((needed_bytes == 1) || (needed_bytes == 2)) { + /* uint8_t or uint16_t */ + size = needed_bytes; + } else if (needed_bytes < 5) { + /* uint32_t */ + size = 4; + } else if (needed_bytes < 9) { + /* uint64_t */ + size = 8; + } else { + /* no basic type, do not round */ + size = needed_bytes; + } + + return size; +} + +LIBYANG_API_DEF ly_bool +lyplg_type_bits_is_bit_set(const char *bitmap, size_t size, uint32_t bit_position) +{ + char bitmask; + + /* find the byte with our bit */ + (void)size; + bitmap = BITS_BITMAP_BYTE(bitmap, size, bit_position / 8); + bit_position %= 8; + + /* generate bitmask */ + bitmask = 1; + bitmask <<= bit_position; + + /* check if bit set */ + if (*bitmap & bitmask) { + return 1; + } + return 0; +} + +/** + * @brief Set bit at a specific position. + * + * @param[in,out] bitmap Bitmap to modify. + * @param[in] size Size of @p bitmap. + * @param[in] bit_position Bit position to set. + */ +static void +bits_bit_set(char *bitmap, size_t size, uint32_t bit_position) +{ + char bitmask; + + /* find the byte with our bit */ + (void)size; + bitmap = BITS_BITMAP_BYTE(bitmap, size, bit_position / 8); + bit_position %= 8; + + /* generate bitmask */ + bitmask = 1; + bitmask <<= bit_position; + + /* set the bit */ + *bitmap |= bitmask; +} + +/** + * @brief Convert a list of bit names separated by whitespaces to a bitmap. + * + * @param[in] value Value to convert. + * @param[in] value_len Length of @p value. + * @param[in] type Type of the value. + * @param[in,out] bitmap Zeroed bitmap, is filled (set). + * @param[out] err Error information. + * @return LY_ERR value. + */ +static LY_ERR +bits_str2bitmap(const char *value, size_t value_len, struct lysc_type_bits *type, char *bitmap, struct ly_err_item **err) +{ + size_t idx_start, idx_end; + LY_ARRAY_COUNT_TYPE u; + ly_bool found; + + idx_start = idx_end = 0; + while (idx_end < value_len) { + /* skip whitespaces */ + while ((idx_end < value_len) && isspace(value[idx_end])) { + ++idx_end; + } + if (idx_end == value_len) { + break; + } + + /* parse bit name */ + idx_start = idx_end; + while ((idx_end < value_len) && !isspace(value[idx_end])) { + ++idx_end; + } + + /* find the bit */ + found = 0; + LY_ARRAY_FOR(type->bits, u) { + if (!ly_strncmp(type->bits[u].name, value + idx_start, idx_end - idx_start)) { + found = 1; + break; + } + } + + /* check if name exists */ + if (!found) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid bit \"%.*s\".", (int)(idx_end - idx_start), + value + idx_start); + } + + /* check for duplication */ + if (lyplg_type_bits_is_bit_set(bitmap, lyplg_type_bits_bitmap_size(type), type->bits[u].position)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Duplicate bit \"%s\".", type->bits[u].name); + } + + /* set the bit */ + bits_bit_set(bitmap, lyplg_type_bits_bitmap_size(type), type->bits[u].position); + } + + return LY_SUCCESS; +} + +/** + * @brief Add a bit item into an array. + * + * @param[in] position Bit position to add. + * @param[in] type Bitis type to read the bit positions and names from. + * @param[in,out] items Array of bit item pointers to add to. + */ +static void +bits_add_item(uint32_t position, struct lysc_type_bits *type, struct lysc_type_bitenum_item **items) +{ + LY_ARRAY_COUNT_TYPE u; + + /* find the bit item */ + LY_ARRAY_FOR(type->bits, u) { + if (type->bits[u].position == position) { + break; + } + } + + /* add it at the end */ + items[LY_ARRAY_COUNT(items)] = &type->bits[u]; + LY_ARRAY_INCREMENT(items); +} + +/** + * @brief Convert a bitmap to a sized array of pointers to their bit definitions. + * + * @param[in] bitmap Bitmap to read from. + * @param[in] type Bits type. + * @param[in,out] items Allocated sized array to fill with the set bits. + */ +static void +bits_bitmap2items(const char *bitmap, struct lysc_type_bits *type, struct lysc_type_bitenum_item **items) +{ + size_t i, bitmap_size = lyplg_type_bits_bitmap_size(type); + uint32_t bit_pos; + uint8_t bitmask; + const uint8_t *byte; + + bit_pos = 0; + for (i = 0; i < bitmap_size; ++i) { + /* check this byte (but not necessarily all bits in the last byte) */ + byte = (uint8_t *)BITS_BITMAP_BYTE(bitmap, bitmap_size, i); + for (bitmask = 1; bitmask; bitmask <<= 1) { + if (*byte & bitmask) { + /* add this bit */ + bits_add_item(bit_pos, type, items); + } + + if (bit_pos == BITS_LAST_BIT_POSITION(type)) { + /* we have checked the last valid bit */ + break; + } + + ++bit_pos; + } + } +} + +/** + * @brief Generate canonical value from ordered array of set bit items. + * + * @param[in] items Sized array of set bit items. + * @param[out] canonical Canonical string value. + * @return LY_ERR value. + */ +static LY_ERR +bits_items2canon(struct lysc_type_bitenum_item **items, char **canonical) +{ + char *ret; + size_t ret_len; + LY_ARRAY_COUNT_TYPE u; + + *canonical = NULL; + + /* init value */ + ret = strdup(""); + LY_CHECK_RET(!ret, LY_EMEM); + ret_len = 0; + + LY_ARRAY_FOR(items, u) { + if (!ret_len) { + ret = ly_realloc(ret, strlen(items[u]->name) + 1); + LY_CHECK_RET(!ret, LY_EMEM); + strcpy(ret, items[u]->name); + + ret_len = strlen(ret); + } else { + ret = ly_realloc(ret, ret_len + 1 + strlen(items[u]->name) + 1); + LY_CHECK_RET(!ret, LY_EMEM); + sprintf(ret + ret_len, " %s", items[u]->name); + + ret_len += 1 + strlen(items[u]->name); + } + } + + *canonical = ret; + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_bits(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_bits *type_bits = (struct lysc_type_bits *)type; + struct lyd_value_bits *val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != lyplg_type_bits_bitmap_size(type_bits)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB bits value size %zu (expected %zu).", + value_len, lyplg_type_bits_bitmap_size(type_bits)); + goto cleanup; + } + + /* store value (bitmap) */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + val->bitmap = (char *)value; + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + } else { + val->bitmap = malloc(value_len); + LY_CHECK_ERR_GOTO(!val->bitmap, ret = LY_EMEM, cleanup); + memcpy(val->bitmap, value, value_len); + } + + /* allocate and fill the bit item array */ + LY_ARRAY_CREATE_GOTO(ctx, val->items, LY_ARRAY_COUNT(type_bits->bits), ret, cleanup); + bits_bitmap2items(val->bitmap, type_bits, val->items); + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* allocate the bitmap */ + val->bitmap = malloc(lyplg_type_bits_bitmap_size(type_bits)); + LY_CHECK_ERR_GOTO(!val->bitmap, ret = LY_EMEM, cleanup); + memset(val->bitmap, 0, lyplg_type_bits_bitmap_size(type_bits)); + + /* fill the bitmap */ + ret = bits_str2bitmap(value, value_len, type_bits, val->bitmap, err); + LY_CHECK_GOTO(ret, cleanup); + + /* allocate and fill the bit item array */ + LY_ARRAY_CREATE_GOTO(ctx, val->items, LY_ARRAY_COUNT(type_bits->bits), ret, cleanup); + bits_bitmap2items(val->bitmap, type_bits, val->items); + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret != LY_SUCCESS, cleanup); + } + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_bits(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_bits(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_bits *v1, *v2; + struct lysc_type_bits *type_bits = (struct lysc_type_bits *)val1->realtype; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + if (memcmp(v1->bitmap, v2->bitmap, lyplg_type_bits_bitmap_size(type_bits))) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_bits(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lysc_type_bits *type_bits = (struct lysc_type_bits *)value->realtype; + struct lyd_value_bits *val; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = lyplg_type_bits_bitmap_size(type_bits); + } + return val->bitmap; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* get the canonical value */ + if (bits_items2canon(val->items, &ret)) { + return NULL; + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_bits(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lysc_type_bits *type_bits = (struct lysc_type_bits *)original->realtype; + LY_ARRAY_COUNT_TYPE u; + struct lyd_value_bits *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + /* optional canonical value */ + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + /* allocate value */ + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + + /* duplicate bitmap */ + dup_val->bitmap = malloc(lyplg_type_bits_bitmap_size(type_bits)); + LY_CHECK_ERR_GOTO(!dup_val->bitmap, ret = LY_EMEM, error); + memcpy(dup_val->bitmap, orig_val->bitmap, lyplg_type_bits_bitmap_size(type_bits)); + + /* duplicate bit item pointers */ + LY_ARRAY_CREATE_GOTO(ctx, dup_val->items, LY_ARRAY_COUNT(orig_val->items), ret, error); + LY_ARRAY_FOR(orig_val->items, u) { + LY_ARRAY_INCREMENT(dup_val->items); + dup_val->items[u] = orig_val->items[u]; + } + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_bits(ctx, dup); + return ret; +} + +LIBYANG_API_DEF void +lyplg_type_free_bits(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_bits *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + free(val->bitmap); + LY_ARRAY_FREE(val->items); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for bits type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_bits[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_BITS_STR, + + .plugin.id = "libyang 2 - bits, version 1", + .plugin.store = lyplg_type_store_bits, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_bits, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_bits, + .plugin.duplicate = lyplg_type_dup_bits, + .plugin.free = lyplg_type_free_bits, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/boolean.c b/src/plugins_types/boolean.c new file mode 100644 index 0000000..f8b19f6 --- /dev/null +++ b/src/plugins_types/boolean.c @@ -0,0 +1,165 @@ +/** + * @file boolean.c + * @author Radek Krejci + * @brief Built-in boolean type plugin. + * + * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesBoolean boolean (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 1 | yes | `int8_t *` | 0 for false, otherwise true | + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_boolean(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + int8_t i; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != 1) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB boolean value size %zu (expected 1).", + value_len); + goto cleanup; + } + + /* store value */ + i = *(int8_t *)value; + storage->boolean = i ? 1 : 0; + + /* store canonical value, it always is */ + ret = lydict_insert(ctx, i ? "true" : "false", 0, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* validate and store the value */ + if ((value_len == ly_strlen_const("true")) && !strncmp(value, "true", ly_strlen_const("true"))) { + i = 1; + } else if ((value_len == ly_strlen_const("false")) && !strncmp(value, "false", ly_strlen_const("false"))) { + i = 0; + } else { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid boolean value \"%.*s\".", (int)value_len, + (char *)value); + goto cleanup; + } + storage->boolean = i; + + /* store canonical value, it always is */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((char *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_boolean(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + if (val1->boolean != val2->boolean) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_boolean(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = sizeof value->boolean; + } + return &value->boolean; + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Plugin information for boolean type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_boolean[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_BOOL_STR, + + .plugin.id = "libyang 2 - boolean, version 1", + .plugin.store = lyplg_type_store_boolean, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_boolean, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_boolean, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 1, + }, + {0} +}; diff --git a/src/plugins_types/date_and_time.c b/src/plugins_types/date_and_time.c new file mode 100644 index 0000000..5ccb86d --- /dev/null +++ b/src/plugins_types/date_and_time.c @@ -0,0 +1,339 @@ +/** + * @file date_and_time.c + * @author Michal Vasko + * @brief ietf-yang-types date-and-time type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include +#include +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesDateAndTime date-and-time (ietf-yang-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 8 | yes | `time_t *` | UNIX timestamp | + * | 1 | no | `int8_t *` | flag whether the value is in the special -00:00 unknown timezone or not | + * | string length | no | `char *` | string with the fraction digits of a second | + */ + +static void lyplg_type_free_date_and_time(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Implementation of ::lyplg_type_store_clb for ietf-yang-types date-and-time type. + */ +static LY_ERR +lyplg_type_store_date_and_time(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_dat = (struct lysc_type_str *)type; + struct lyd_value_date_and_time *val; + struct tm tm; + uint32_t i; + char c; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len < 8) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB date-and-time value size %zu " + "(expected at least 8).", value_len); + goto cleanup; + } + for (i = 9; i < value_len; ++i) { + c = ((char *)value)[i]; + if (!isdigit(c)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB date-and-time character '%c' " + "(expected a digit).", c); + goto cleanup; + } + } + + /* store timestamp */ + memcpy(&val->time, value, sizeof val->time); + + /* store fractions of second */ + if (value_len > 9) { + val->fractions_s = strndup(((char *)value) + 9, value_len - 9); + LY_CHECK_ERR_GOTO(!val->fractions_s, ret = LY_EMEM, cleanup); + } + + /* store unknown timezone */ + if (value_len > 8) { + val->unknown_tz = *(((int8_t *)value) + 8) ? 1 : 0; + } + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction, there can be only ASCII chars */ + if (type_dat->length) { + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_dat->length, value_len, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* date-and-time pattern */ + ret = lyplg_type_validate_patterns(type_dat->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* pattern validation succeeded, convert to UNIX time and fractions of second */ + ret = ly_time_str2time(value, &val->time, &val->fractions_s); + LY_CHECK_GOTO(ret, cleanup); + +#ifdef HAVE_TIME_H_TIMEZONE + if (!strncmp(((char *)value + value_len) - 6, "-00:00", 6)) { + /* unknown timezone, move the timestamp to UTC */ + tzset(); + val->time += (time_t)timezone; + val->unknown_tz = 1; + + /* DST may apply, adjust accordingly */ + if (!localtime_r(&val->time, &tm)) { + ret = ly_err_new(err, LY_ESYS, LYVE_DATA, NULL, NULL, "localtime_r() call failed (%s).", strerror(errno)); + goto cleanup; + } else if (tm.tm_isdst < 0) { + ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Failed to get DST information."); + goto cleanup; + } + if (tm.tm_isdst) { + /* move an hour back */ + val->time -= 3600; + } + } +#else + (void)tm; + val->unknown_tz = 1; +#endif + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_date_and_time(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for ietf-yang-types date-and-time type. + */ +static LY_ERR +lyplg_type_compare_date_and_time(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_date_and_time *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + /* compare timestamp and unknown tz */ + if ((v1->time != v2->time) || (v1->unknown_tz != v2->unknown_tz)) { + return LY_ENOT; + } + + /* compare second fractions */ + if ((!v1->fractions_s && !v2->fractions_s) || + (v1->fractions_s && v2->fractions_s && !strcmp(v1->fractions_s, v2->fractions_s))) { + return LY_SUCCESS; + } + return LY_ENOT; +} + +/** + * @brief Implementation of ::lyplg_type_print_clb for ietf-yang-types date-and-time type. + */ +static const void * +lyplg_type_print_date_and_time(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_date_and_time *val; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + if (val->unknown_tz || val->fractions_s) { + ret = malloc(8 + 1 + (val->fractions_s ? strlen(val->fractions_s) : 0)); + LY_CHECK_ERR_RET(!ret, LOGMEM(ctx), NULL); + + *dynamic = 1; + if (value_len) { + *value_len = 8 + 1 + (val->fractions_s ? strlen(val->fractions_s) : 0); + } + memcpy(ret, &val->time, sizeof val->time); + memcpy(ret + 8, &val->unknown_tz, sizeof val->unknown_tz); + if (val->fractions_s) { + memcpy(ret + 9, val->fractions_s, strlen(val->fractions_s)); + } + } else { + *dynamic = 0; + if (value_len) { + *value_len = 8; + } + ret = (char *)&val->time; + } + return ret; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* get the canonical value */ + if (ly_time_time2str(val->time, val->fractions_s, &ret)) { + return NULL; + } + + if (val->unknown_tz) { + /* date and time is correct, fix only the timezone */ + strcpy((ret + strlen(ret)) - 6, "-00:00"); + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Implementation of ::lyplg_type_dup_clb for ietf-yang-types date-and-time type. + */ +static LY_ERR +lyplg_type_dup_date_and_time(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_date_and_time *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + /* optional canonical value */ + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + /* allocate value */ + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + + /* copy timestamp and unknown tz */ + dup_val->time = orig_val->time; + dup_val->unknown_tz = orig_val->unknown_tz; + + /* duplicate second fractions */ + if (orig_val->fractions_s) { + dup_val->fractions_s = strdup(orig_val->fractions_s); + LY_CHECK_ERR_GOTO(!dup_val->fractions_s, ret = LY_EMEM, error); + } + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_date_and_time(ctx, dup); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_free_clb for ietf-yang-types date-and-time type. + */ +static void +lyplg_type_free_date_and_time(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_date_and_time *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + free(val->fractions_s); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for date-and-time type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_date_and_time[] = { + { + .module = "ietf-yang-types", + .revision = "2013-07-15", + .name = "date-and-time", + + .plugin.id = "libyang 2 - date-and-time, version 1", + .plugin.store = lyplg_type_store_date_and_time, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_date_and_time, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_date_and_time, + .plugin.duplicate = lyplg_type_dup_date_and_time, + .plugin.free = lyplg_type_free_date_and_time, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/decimal64.c b/src/plugins_types/decimal64.c new file mode 100644 index 0000000..25a88d9 --- /dev/null +++ b/src/plugins_types/decimal64.c @@ -0,0 +1,239 @@ +/** + * @file decimal64.c + * @author Radek Krejci + * @brief Built-in decimal64 type plugin. + * + * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesDecimal64 decimal64 (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 8 | yes | `int64_t *` | little-endian value represented without floating point | + */ + +/** + * @brief Convert decimal64 number to canonical string. + * + * @param[in] num Decimal64 number stored in int64. + * @param[in] type Decimal64 type with fraction digits. + * @param[out] str Canonical string value. + * @return LY_ERR value. + */ +static LY_ERR +decimal64_num2str(int64_t num, struct lysc_type_dec *type, char **str) +{ + char *ret; + + /* allocate the value */ + ret = calloc(1, LY_NUMBER_MAXLEN); + LY_CHECK_RET(!ret, LY_EMEM); + + if (num) { + int count = sprintf(ret, "%" PRId64 " ", num); + + if (((num > 0) && ((count - 1) <= type->fraction_digits)) || ((count - 2) <= type->fraction_digits)) { + /* we have 0. value, print the value with the leading zeros + * (one for 0. and also keep the correct with of num according + * to fraction-digits value) + * for (num < 0) - extra character for '-' sign */ + count = sprintf(ret, "%0*" PRId64 " ", (num > 0) ? (type->fraction_digits + 1) : (type->fraction_digits + 2), num); + } + for (uint8_t i = type->fraction_digits, j = 1; i > 0; i--) { + if (j && (i > 1) && (ret[count - 2] == '0')) { + /* we have trailing zero to skip */ + ret[count - 1] = '\0'; + } else { + j = 0; + ret[count - 1] = ret[count - 2]; + } + count--; + } + ret[count - 1] = '.'; + } else { + /* zero */ + sprintf(ret, "0.0"); + } + + *str = ret; + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_decimal64(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + struct lysc_type_dec *type_dec = (struct lysc_type_dec *)type; + LY_ERR ret = LY_SUCCESS; + int64_t num; + char *canon; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != 8) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB decimal64 value size %zu (expected 8).", + value_len); + goto cleanup; + } + + /* we have the decimal64 number, in host byte order */ + memcpy(&num, value, value_len); + num = le64toh(num); + } else { + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* parse decimal64 value */ + ret = lyplg_type_parse_dec64(type_dec->fraction_digits, value, value_len, &num, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* store value */ + storage->dec64 = num; + + /* we need canonical value for the range check */ + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* generate canonical value */ + ret = decimal64_num2str(num, type_dec, &canon); + LY_CHECK_GOTO(ret, cleanup); + + /* store it */ + ret = lydict_insert_zc(ctx, canon, (const char **)&storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + + if (type_dec->range) { + /* check range of the number */ + ret = lyplg_type_validate_range(type->basetype, type_dec->range, num, storage->_canonical, + strlen(storage->_canonical), err); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_decimal64(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + /* if type is the same, the fraction digits are, too */ + if (val1->dec64 != val2->dec64) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_decimal64(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + int64_t num = 0; + void *buf; + + if (format == LY_VALUE_LYB) { + num = htole64(value->dec64); + if (num == value->dec64) { + /* values are equal, little-endian */ + *dynamic = 0; + if (value_len) { + *value_len = sizeof value->dec64; + } + return &value->dec64; + } else { + /* values differ, big-endian */ + buf = calloc(1, sizeof value->dec64); + LY_CHECK_RET(!buf, NULL); + + *dynamic = 1; + if (value_len) { + *value_len = sizeof value->dec64; + } + memcpy(buf, &num, sizeof value->dec64); + return buf; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Plugin information for decimal64 type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_decimal64[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_DEC64_STR, + + .plugin.id = "libyang 2 - decimal64, version 1", + .plugin.store = lyplg_type_store_decimal64, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_decimal64, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_decimal64, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 8, + }, + {0} +}; diff --git a/src/plugins_types/empty.c b/src/plugins_types/empty.c new file mode 100644 index 0000000..d84634f --- /dev/null +++ b/src/plugins_types/empty.c @@ -0,0 +1,103 @@ +/** + * @file empty.c + * @author Radek Krejci + * @brief Built-in empty type plugin. + * + * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesEmpty empty (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 0 | yes | `void` | none | + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_empty(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT UNUSED(format), void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* validation */ + if (value_len) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid empty value length %zu.", value_len); + goto cleanup; + } + + /* store canonical value, it always is */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +/** + * @brief Plugin information for empty type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_empty[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_EMPTY_STR, + + .plugin.id = "libyang 2 - empty, version 1", + .plugin.store = lyplg_type_store_empty, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_simple, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 0, + }, + {0} +}; diff --git a/src/plugins_types/enumeration.c b/src/plugins_types/enumeration.c new file mode 100644 index 0000000..91ca822 --- /dev/null +++ b/src/plugins_types/enumeration.c @@ -0,0 +1,202 @@ +/** + * @file enumeration.c + * @author Radek Krejci + * @brief Built-in enumeration type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesEnumeration enumeration (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 4 | yes | `int32 *` | assigned little-endian value of the enum | + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_enum(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + struct lysc_type_enum *type_enum = (struct lysc_type_enum *)type; + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; + ly_bool found = 0; + int64_t num = 0; + int32_t num_val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != 4) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB enumeration value size %zu (expected 4).", + value_len); + goto cleanup; + } + + /* convert the value to host byte order */ + memcpy(&num, value, value_len); + num = le64toh(num); + num_val = num; + + /* find the matching enumeration value item */ + LY_ARRAY_FOR(type_enum->enums, u) { + if (type_enum->enums[u].value == num_val) { + found = 1; + break; + } + } + + if (!found) { + /* value not found */ + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid enumeration value % " PRIi32 ".", num_val); + goto cleanup; + } + + /* store value */ + storage->enum_item = &type_enum->enums[u]; + + /* canonical settings via dictionary due to free callback */ + ret = lydict_insert(ctx, type_enum->enums[u].name, 0, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* find the matching enumeration value item */ + LY_ARRAY_FOR(type_enum->enums, u) { + if (!ly_strncmp(type_enum->enums[u].name, value, value_len)) { + found = 1; + break; + } + } + + if (!found) { + /* enum not found */ + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid enumeration value \"%.*s\".", (int)value_len, + (char *)value); + goto cleanup; + } + + /* store value */ + storage->enum_item = &type_enum->enums[u]; + + /* store canonical value, it always is */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_enum(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + int64_t prev_num = 0, num = 0; + void *buf; + + if (format == LY_VALUE_LYB) { + prev_num = num = value->enum_item->value; + num = htole64(num); + if (num == prev_num) { + /* values are equal, little-endian */ + *dynamic = 0; + if (value_len) { + *value_len = 4; + } + return &value->enum_item->value; + } else { + /* values differ, big-endian */ + buf = calloc(1, 4); + LY_CHECK_RET(!buf, NULL); + + *dynamic = 1; + if (value_len) { + *value_len = 4; + } + memcpy(buf, &num, 4); + return buf; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Plugin information for enumeration type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_enumeration[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_ENUM_STR, + + .plugin.id = "libyang 2 - enumeration, version 1", + .plugin.store = lyplg_type_store_enum, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_enum, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 4, + }, + {0} +}; diff --git a/src/plugins_types/identityref.c b/src/plugins_types/identityref.c new file mode 100644 index 0000000..8b7985d --- /dev/null +++ b/src/plugins_types/identityref.c @@ -0,0 +1,352 @@ +/** + * @file identityref.c + * @author Radek Krejci + * @brief Built-in identityref type plugin. + * + * Copyright (c) 2019-2021 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 /* asprintf */ + +#include "plugins_types.h" + +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIdentityref identityref (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string JSON format of the identityref | + */ + +/** + * @brief Print an identityref value in a specific format. + * + * @param[in] ident Identityref to print. + * @param[in] format Value format. + * @param[in] prefix_data Format-specific data for resolving prefixes. + * @param[out] str Printed identityref. + * @param[out] str_len Optional length of @p str. + * @return LY_ERR value. + */ +static LY_ERR +identityref_ident2str(const struct lysc_ident *ident, LY_VALUE_FORMAT format, void *prefix_data, char **str, size_t *str_len) +{ + int len; + + len = asprintf(str, "%s:%s", lyplg_type_get_prefix(ident->module, format, prefix_data), ident->name); + if (len == -1) { + return LY_EMEM; + } + + if (str_len) { + *str_len = (size_t)len; + } + return LY_SUCCESS; +} + +/** + * @brief Convert a string identityref value to matching identity. + * + * @param[in] value Identityref value. + * @param[in] value_len Length of @p value. + * @param[in] format Value format. + * @param[in] prefix_data Format-specific data for resolving prefixes. + * @param[in] ctx libyang context. + * @param[in] ctx_node Context node for resolving the prefixes. + * @param[out] ident Found identity. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +identityref_str2ident(const char *value, size_t value_len, LY_VALUE_FORMAT format, void *prefix_data, + const struct ly_ctx *ctx, const struct lysc_node *ctx_node, struct lysc_ident **ident, struct ly_err_item **err) +{ + const char *id_name, *prefix = value; + size_t id_len, prefix_len; + const struct lys_module *mod; + LY_ARRAY_COUNT_TYPE u; + struct lysc_ident *id, *identities; + + /* locate prefix if any */ + for (prefix_len = 0; (prefix_len < value_len) && (value[prefix_len] != ':'); ++prefix_len) {} + if (prefix_len < value_len) { + id_name = &value[prefix_len + 1]; + id_len = value_len - (prefix_len + 1); + } else { + prefix_len = 0; + id_name = value; + id_len = value_len; + } + + if (!id_len) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid empty identityref value."); + } + + mod = lyplg_type_identity_module(ctx, ctx_node, prefix, prefix_len, format, prefix_data); + if (!mod) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid identityref \"%.*s\" value - unable to map prefix to YANG schema.", (int)value_len, value); + } + + id = NULL; + identities = mod->identities; + LY_ARRAY_FOR(identities, u) { + if (!ly_strncmp(identities[u].name, id_name, id_len)) { + /* we have match */ + id = &identities[u]; + break; + } + } + if (!id) { + /* no match */ + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid identityref \"%.*s\" value - identity not found in module \"%s\".", + (int)value_len, value, mod->name); + } + + *ident = id; + return LY_SUCCESS; +} + +/** + * @brief Check that an identityref is derived from the type base. + * + * @param[in] ident Derived identity to which identityref points. + * @param[in] type Identityref type. + * @param[in] value String value for logging. + * @param[in] value_len Length of @p value. + * @param[out] err Error information on error. + */ +static LY_ERR +identityref_check_base(const struct lysc_ident *ident, struct lysc_type_identityref *type, const char *value, + size_t value_len, struct ly_err_item **err) +{ + LY_ERR ret; + size_t str_len; + char *str; + LY_ARRAY_COUNT_TYPE u; + struct lysc_ident *base; + + /* check that the identity matches some of the type's base identities */ + LY_ARRAY_FOR(type->bases, u) { + if (!lyplg_type_identity_isderived(type->bases[u], ident)) { + /* we have match */ + break; + } + } + + /* it does not, generate a nice error */ + if (u == LY_ARRAY_COUNT(type->bases)) { + str = NULL; + str_len = 1; + LY_ARRAY_FOR(type->bases, u) { + base = type->bases[u]; + str_len += (u ? 2 : 0) + 1 + strlen(base->module->name) + 1 + strlen(base->name) + 1; + str = ly_realloc(str, str_len); + sprintf(str + (u ? strlen(str) : 0), "%s\"%s:%s\"", u ? ", " : "", base->module->name, base->name); + } + + /* no match */ + if (u == 1) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid identityref \"%.*s\" value - identity not derived from the base %s.", + (int)value_len, value, str); + } else { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid identityref \"%.*s\" value - identity not derived from all the bases %s.", + (int)value_len, value, str); + } + free(str); + return ret; + } + + return LY_SUCCESS; +} + +/** + * @brief Check if @p ident is not disabled. + * + * Identity is disabled if it is located in an unimplemented model or + * it can be disabled by if-feature. Calling this function may invoke + * the implementation of another module. + * + * @param[in] ident Derived identity to which identityref points. + * @param[in] value Value of identityref. + * @param[in] value_len Length (number of bytes) of the given @p value. + * @param[in] options [Type plugin store options](@ref plugintypestoreopts). + * @param[in,out] unres Global unres structure for newly implemented modules. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +identityref_check_ident(const struct lysc_ident *ident, const char *value, + size_t value_len, uint32_t options, struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + + if (!ident->module->implemented) { + if (options & LYPLG_TYPE_STORE_IMPLEMENT) { + ret = lyplg_type_make_implemented(ident->module, NULL, unres); + } else { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid identityref \"%.*s\" value - identity found in non-implemented module \"%s\".", + (int)value_len, (char *)value, ident->module->name); + } + } else if (lys_identity_iffeature_value(ident) == LY_ENOT) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid identityref \"%.*s\" value - identity is disabled by if-feature.", + (int)value_len, value); + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_identityref(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, + struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_identityref *type_ident = (struct lysc_type_identityref *)type; + char *canon; + struct lysc_ident *ident = NULL; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* find a matching identity */ + ret = identityref_str2ident(value, value_len, format, prefix_data, ctx, ctx_node, &ident, err); + LY_CHECK_GOTO(ret, cleanup); + + /* check if the identity is enabled */ + ret = identityref_check_ident(ident, value, value_len, options, unres, err); + LY_CHECK_GOTO(ret, cleanup); + + /* check that the identity is derived form all the bases */ + ret = identityref_check_base(ident, type_ident, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + if (ctx_node) { + /* check status */ + ret = lyplg_type_check_status(ctx_node, ident->flags, format, prefix_data, ident->name, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* store value */ + storage->ident = ident; + + /* store canonical value */ + if (format == LY_VALUE_CANON) { + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* JSON format with prefix is the canonical one */ + ret = identityref_ident2str(ident, LY_VALUE_JSON, NULL, &canon, NULL); + LY_CHECK_GOTO(ret, cleanup); + + ret = lydict_insert_zc(ctx, canon, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_identityref(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + if (val1->ident == val2->ident) { + return LY_SUCCESS; + } + return LY_ENOT; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_identityref(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool *dynamic, size_t *value_len) +{ + char *ret; + + if ((format == LY_VALUE_CANON) || (format == LY_VALUE_JSON) || (format == LY_VALUE_LYB)) { + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; + } + + /* print the value in the specific format */ + if (identityref_ident2str(value->ident, format, prefix_data, &ret, value_len)) { + return NULL; + } + *dynamic = 1; + return ret; +} + +/** + * @brief Plugin information for identityref type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_identityref[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_IDENT_STR, + + .plugin.id = "libyang 2 - identityref, version 1", + .plugin.store = lyplg_type_store_identityref, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_identityref, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_identityref, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/instanceid.c b/src/plugins_types/instanceid.c new file mode 100644 index 0000000..d151d0a --- /dev/null +++ b/src/plugins_types/instanceid.c @@ -0,0 +1,382 @@ +/** + * @file instanceid.c + * @author Radek Krejci + * @brief Built-in instance-identifier type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "path.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesInstanceIdentifier instance-identifier (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string JSON format of the instance-identifier | + */ + +/** + * @brief Convert compiled path (instance-identifier) into string. + * + * @param[in] path Compiled path. + * @param[in] format Value format. + * @param[in] prefix_data Format-specific data for resolving prefixes. + * @param[out] str Printed instance-identifier. + * @return LY_ERR value. + */ +static LY_ERR +instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *prefix_data, char **str) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + char *result = NULL, quot; + const struct lys_module *mod = NULL; + ly_bool inherit_prefix = 0, d; + const char *strval; + + switch (format) { + case LY_VALUE_XML: + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + /* everything is prefixed */ + inherit_prefix = 0; + break; + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* the same prefix is inherited and skipped */ + inherit_prefix = 1; + break; + } + + LY_ARRAY_FOR(path, u) { + /* new node */ + if (!inherit_prefix || (mod != path[u].node->module)) { + mod = path[u].node->module; + ret = ly_strcat(&result, "/%s:%s", lyplg_type_get_prefix(mod, format, prefix_data), path[u].node->name); + } else { + ret = ly_strcat(&result, "/%s", path[u].node->name); + } + LY_CHECK_GOTO(ret, cleanup); + + /* node predicates */ + LY_ARRAY_FOR(path[u].predicates, v) { + struct ly_path_predicate *pred = &path[u].predicates[v]; + + switch (path[u].pred_type) { + case LY_PATH_PREDTYPE_NONE: + break; + case LY_PATH_PREDTYPE_POSITION: + /* position predicate */ + ret = ly_strcat(&result, "[%" PRIu64 "]", pred->position); + break; + case LY_PATH_PREDTYPE_LIST: + /* key-predicate */ + strval = pred->value.realtype->plugin->print(path[u].node->module->ctx, &pred->value, format, prefix_data, + &d, NULL); + + /* default quote */ + quot = '\''; + if (strchr(strval, quot)) { + quot = '"'; + } + if (inherit_prefix) { + /* always the same prefix as the parent */ + ret = ly_strcat(&result, "[%s=%c%s%c]", pred->key->name, quot, strval, quot); + } else { + ret = ly_strcat(&result, "[%s:%s=%c%s%c]", lyplg_type_get_prefix(pred->key->module, format, prefix_data), + pred->key->name, quot, strval, quot); + } + if (d) { + free((char *)strval); + } + break; + case LY_PATH_PREDTYPE_LEAFLIST: + /* leaf-list-predicate */ + strval = pred->value.realtype->plugin->print(path[u].node->module->ctx, &pred->value, format, prefix_data, + &d, NULL); + + /* default quote */ + quot = '\''; + if (strchr(strval, quot)) { + quot = '"'; + } + ret = ly_strcat(&result, "[.=%c%s%c]", quot, strval, quot); + if (d) { + free((char *)strval); + } + break; + } + + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (ret) { + free(result); + } else { + *str = result; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_instanceid(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, + struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_instanceid *type_inst = (struct lysc_type_instanceid *)type; + struct ly_path *path; + char *canon; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* compile instance-identifier into path */ + if (format == LY_VALUE_LYB) { + /* The @p value in LYB format is the same as in JSON format. */ + ret = lyplg_type_lypath_new(ctx, value, value_len, options, LY_VALUE_JSON, prefix_data, ctx_node, + unres, &path, err); + } else { + ret = lyplg_type_lypath_new(ctx, value, value_len, options, format, prefix_data, ctx_node, + unres, &path, err); + } + LY_CHECK_GOTO(ret, cleanup); + + /* store value */ + storage->target = path; + + /* check status */ + ret = lyplg_type_lypath_check_status(ctx_node, path, format, prefix_data, err); + LY_CHECK_GOTO(ret, cleanup); + + /* store canonical value */ + if (format == LY_VALUE_CANON) { + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* JSON format with prefix is the canonical one */ + ret = instanceid_path2str(path, LY_VALUE_JSON, NULL, &canon); + LY_CHECK_GOTO(ret, cleanup); + + ret = lydict_insert_zc(ctx, canon, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_instanceid(ctx, storage); + } + if (!ret && type_inst->require_instance) { + /* needs to be resolved */ + return LY_EINCOMPLETE; + } else { + return ret; + } +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_validate_instanceid(const struct ly_ctx *ctx, const struct lysc_type *UNUSED(type), + const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lyd_value *storage, + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_instanceid *type_inst = (struct lysc_type_instanceid *)storage->realtype; + const char *value; + char *path; + + *err = NULL; + + if (!type_inst->require_instance) { + /* redundant to resolve */ + return LY_SUCCESS; + } + + /* find the target in data */ + if ((ret = ly_path_eval(storage->target, tree, NULL))) { + value = lyplg_type_print_instanceid(ctx, storage, LY_VALUE_CANON, NULL, NULL, NULL); + path = lyd_path(ctx_node, LYD_PATH_STD, NULL, 0); + return ly_err_new(err, ret, LYVE_DATA, path, strdup("instance-required"), LY_ERRMSG_NOINST, value); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_instanceid(const struct lyd_value *val1, const struct lyd_value *val2) +{ + LY_ARRAY_COUNT_TYPE u, v; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + if (val1 == val2) { + return LY_SUCCESS; + } else if (LY_ARRAY_COUNT(val1->target) != LY_ARRAY_COUNT(val2->target)) { + return LY_ENOT; + } + + LY_ARRAY_FOR(val1->target, u) { + struct ly_path *s1 = &val1->target[u]; + struct ly_path *s2 = &val2->target[u]; + + if ((s1->node != s2->node) || (s1->pred_type != s2->pred_type) || + (s1->predicates && (LY_ARRAY_COUNT(s1->predicates) != LY_ARRAY_COUNT(s2->predicates)))) { + return LY_ENOT; + } + if (s1->predicates) { + LY_ARRAY_FOR(s1->predicates, v) { + struct ly_path_predicate *pred1 = &s1->predicates[v]; + struct ly_path_predicate *pred2 = &s2->predicates[v]; + + switch (s1->pred_type) { + case LY_PATH_PREDTYPE_NONE: + break; + case LY_PATH_PREDTYPE_POSITION: + /* position predicate */ + if (pred1->position != pred2->position) { + return LY_ENOT; + } + break; + case LY_PATH_PREDTYPE_LIST: + /* key-predicate */ + if ((pred1->key != pred2->key) || + ((struct lysc_node_leaf *)pred1->key)->type->plugin->compare(&pred1->value, &pred2->value)) { + return LY_ENOT; + } + break; + case LY_PATH_PREDTYPE_LEAFLIST: + /* leaf-list predicate */ + if (((struct lysc_node_leaflist *)s1->node)->type->plugin->compare(&pred1->value, &pred2->value)) { + return LY_ENOT; + } + } + } + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_instanceid(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool *dynamic, size_t *value_len) +{ + char *ret; + + if ((format == LY_VALUE_CANON) || (format == LY_VALUE_JSON) || (format == LY_VALUE_LYB)) { + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; + } + + /* print the value in the specific format */ + if (instanceid_path2str(value->target, format, prefix_data, &ret)) { + return NULL; + } + *dynamic = 1; + if (value_len) { + *value_len = strlen(ret); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_instanceid(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + + memset(dup, 0, sizeof *dup); + + /* canonical value */ + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + /* copy path */ + ret = ly_path_dup(ctx, original->target, &dup->target); + LY_CHECK_GOTO(ret, error); + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_instanceid(ctx, dup); + return ret; +} + +LIBYANG_API_DEF void +lyplg_type_free_instanceid(const struct ly_ctx *ctx, struct lyd_value *value) +{ + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + ly_path_free(ctx, value->target); +} + +/** + * @brief Plugin information for instance-identifier type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_instanceid[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_INST_STR, + + .plugin.id = "libyang 2 - instance-identifier, version 1", + .plugin.store = lyplg_type_store_instanceid, + .plugin.validate = lyplg_type_validate_instanceid, + .plugin.compare = lyplg_type_compare_instanceid, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_instanceid, + .plugin.duplicate = lyplg_type_dup_instanceid, + .plugin.free = lyplg_type_free_instanceid, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/instanceid_keys.c b/src/plugins_types/instanceid_keys.c new file mode 100644 index 0000000..0cd08f7 --- /dev/null +++ b/src/plugins_types/instanceid_keys.c @@ -0,0 +1,229 @@ +/** + * @file instanceid_keys.c + * @author Michal Basko + * @brief ietf-netconf edit-config key metadata instance-identifier keys predicate type plugin. + * + * Copyright (c) 2022 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "path.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ +#include "xpath.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesInstanceIdentifierKeys instance-identifier-keys (yang) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string JSON format of the instance-identifier keys predicate | + */ + +/** + * @brief Special lyd_value structure for yang instance-identifier-keys values. + */ +struct lyd_value_instance_identifier_keys { + struct lyxp_expr *keys; + const struct ly_ctx *ctx; + void *prefix_data; + LY_VALUE_FORMAT format; +}; + +/** + * @brief Print instance-id-keys value in the specific format. + * + * @param[in] val instance-id-keys value structure. + * @param[in] format Format to print in. + * @param[in] prefix_data Format-specific prefix data. + * @param[out] str_value Printed value. + * @param[out] err Error structure on error. + * @return LY_ERR value. + */ +static LY_ERR +instanceid_keys_print_value(const struct lyd_value_instance_identifier_keys *val, LY_VALUE_FORMAT format, void *prefix_data, + char **str_value, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t cur_idx = 0, str_len = 0; + enum lyxp_token cur_tok; + char *str_tok; + void *mem; + const char *cur_exp_ptr; + ly_bool is_nt; + const struct lys_module *context_mod = NULL; + + *str_value = NULL; + + while (cur_idx < val->keys->used) { + cur_tok = val->keys->tokens[cur_idx]; + cur_exp_ptr = val->keys->expr + val->keys->tok_pos[cur_idx]; + + if ((cur_tok == LYXP_TOKEN_NAMETEST) || (cur_tok == LYXP_TOKEN_LITERAL)) { + /* tokens that may include prefixes, get them in the target format */ + is_nt = (cur_tok == LYXP_TOKEN_NAMETEST) ? 1 : 0; + LY_CHECK_GOTO(ret = lyplg_type_xpath10_print_token(cur_exp_ptr, val->keys->tok_len[cur_idx], is_nt, &context_mod, + val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), error); + + /* append the converted token */ + mem = realloc(*str_value, str_len + strlen(str_tok) + 1); + LY_CHECK_ERR_GOTO(!mem, free(str_tok), error_mem); + *str_value = mem; + str_len += sprintf(*str_value + str_len, "%s", str_tok); + free(str_tok); + + /* token processed */ + ++cur_idx; + } else { + /* just copy the token */ + mem = realloc(*str_value, str_len + val->keys->tok_len[cur_idx] + 1); + LY_CHECK_GOTO(!mem, error_mem); + *str_value = mem; + str_len += sprintf(*str_value + str_len, "%.*s", (int)val->keys->tok_len[cur_idx], cur_exp_ptr); + + /* token processed */ + ++cur_idx; + } + } + + return LY_SUCCESS; + +error_mem: + ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."); + +error: + free(*str_value); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the instance-identifier-keys yang type. + */ +static LY_ERR +lyplg_type_store_instanceid_keys(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *UNUSED(ctx_node), + struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_instance_identifier_keys *val; + char *canon; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* parse instance-identifier keys, with optional prefix even though it should be mandatory */ + if (value_len && (((char *)value)[0] != '[')) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid first character '%c', list key predicates expected.", + ((char *)value)[0]); + goto cleanup; + } + ret = ly_path_parse_predicate(ctx, NULL, value_len ? value : "", value_len, LY_PATH_PREFIX_OPTIONAL, + LY_PATH_PRED_KEYS, &val->keys); + if (ret) { + ret = ly_err_new(err, ret, LYVE_DATA, NULL, NULL, "%s", ly_errmsg(ctx)); + goto cleanup; + } + val->ctx = ctx; + + /* store format-specific data and context for later prefix resolution */ + ret = lyplg_type_prefix_data_new(ctx, value, value_len, format, prefix_data, &val->format, &val->prefix_data); + LY_CHECK_GOTO(ret, cleanup); + + switch (format) { + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + break; + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + case LY_VALUE_XML: + /* JSON format with prefix is the canonical one */ + ret = instanceid_keys_print_value(val, LY_VALUE_JSON, NULL, &canon, err); + LY_CHECK_GOTO(ret, cleanup); + + ret = lydict_insert_zc(ctx, canon, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + break; + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_xpath10(ctx, storage); + } + return ret; +} + +/** + * @brief Plugin information for instance-identifier type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_instanceid_keys[] = { + { + .module = "yang", + .revision = NULL, + .name = "instance-identifier-keys", + + .plugin.id = "libyang 2 - instance-identifier-keys, version 1", + .plugin.store = lyplg_type_store_instanceid_keys, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_xpath10, + .plugin.duplicate = lyplg_type_dup_xpath10, + .plugin.free = lyplg_type_free_xpath10, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/integer.c b/src/plugins_types/integer.c new file mode 100644 index 0000000..0903365 --- /dev/null +++ b/src/plugins_types/integer.c @@ -0,0 +1,585 @@ +/** + * @file integer.c + * @author Radek Krejci + * @brief Built-in integer types plugin. + * + * Copyright (c) 2019-2021 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 /* asprintf, strdup */ + +#include "plugins_types.h" + +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesInteger (u)int(8/16/32/64) (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 1/2/4/8 | yes | pointer to the specific integer type | little-endian integer value | + */ + +/** + * @brief LYB value size of each integer type. + */ +static size_t integer_lyb_size[] = { + [LY_TYPE_INT8] = 1, [LY_TYPE_INT16] = 2, [LY_TYPE_INT32] = 4, [LY_TYPE_INT64] = 8, + [LY_TYPE_UINT8] = 1, [LY_TYPE_UINT16] = 2, [LY_TYPE_UINT32] = 4, [LY_TYPE_UINT64] = 8 +}; + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_int(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + int64_t num = 0; + int base = 1; + char *canon = NULL; + struct lysc_type_num *type_num = (struct lysc_type_num *)type; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != integer_lyb_size[type->basetype]) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB signed integer value size %zu (expected %zu).", + value_len, integer_lyb_size[type->basetype]); + goto cleanup; + } + + /* copy the integer and correct the byte order */ + memcpy(&num, value, value_len); + num = le64toh(num); + } else { + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, &base, err); + LY_CHECK_GOTO(ret, cleanup); + + /* parse the integer */ + switch (type->basetype) { + case LY_TYPE_INT8: + ret = lyplg_type_parse_int("int8", base, INT64_C(-128), INT64_C(127), value, value_len, &num, err); + break; + case LY_TYPE_INT16: + ret = lyplg_type_parse_int("int16", base, INT64_C(-32768), INT64_C(32767), value, value_len, &num, err); + break; + case LY_TYPE_INT32: + ret = lyplg_type_parse_int("int32", base, INT64_C(-2147483648), INT64_C(2147483647), value, value_len, &num, err); + break; + case LY_TYPE_INT64: + ret = lyplg_type_parse_int("int64", base, INT64_C(-9223372036854775807) - INT64_C(1), + INT64_C(9223372036854775807), value, value_len, &num, err); + break; + default: + LOGINT(ctx); + ret = LY_EINT; + } + LY_CHECK_GOTO(ret, cleanup); + } + + /* set the value (matters for big-endian) and get the correct int64 number */ + switch (type->basetype) { + case LY_TYPE_INT8: + storage->int8 = num; + num = storage->int8; + break; + case LY_TYPE_INT16: + storage->int16 = num; + num = storage->int16; + break; + case LY_TYPE_INT32: + storage->int32 = num; + num = storage->int32; + break; + case LY_TYPE_INT64: + storage->int64 = num; + num = storage->int64; + break; + default: + break; + } + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* generate canonical value */ + switch (type->basetype) { + case LY_TYPE_INT8: + LY_CHECK_ERR_GOTO(asprintf(&canon, "%" PRId8, storage->int8) == -1, ret = LY_EMEM, cleanup); + break; + case LY_TYPE_INT16: + LY_CHECK_ERR_GOTO(asprintf(&canon, "%" PRId16, storage->int16) == -1, ret = LY_EMEM, cleanup); + break; + case LY_TYPE_INT32: + LY_CHECK_ERR_GOTO(asprintf(&canon, "%" PRId32, storage->int32) == -1, ret = LY_EMEM, cleanup); + break; + case LY_TYPE_INT64: + LY_CHECK_ERR_GOTO(asprintf(&canon, "%" PRId64, storage->int64) == -1, ret = LY_EMEM, cleanup); + break; + default: + break; + } + + /* store it */ + ret = lydict_insert_zc(ctx, canon, (const char **)&storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + + /* validate range of the number */ + if (type_num->range) { + ret = lyplg_type_validate_range(type->basetype, type_num->range, num, storage->_canonical, + strlen(storage->_canonical), err); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_int(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + switch (val1->realtype->basetype) { + case LY_TYPE_INT8: + if (val1->int8 != val2->int8) { + return LY_ENOT; + } + break; + case LY_TYPE_INT16: + if (val1->int16 != val2->int16) { + return LY_ENOT; + } + break; + case LY_TYPE_INT32: + if (val1->int32 != val2->int32) { + return LY_ENOT; + } + break; + case LY_TYPE_INT64: + if (val1->int64 != val2->int64) { + return LY_ENOT; + } + break; + default: + break; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + int64_t prev_num = 0, num = 0; + void *buf; + + if (format == LY_VALUE_LYB) { + switch (value->realtype->basetype) { + case LY_TYPE_INT8: + prev_num = num = value->int8; + break; + case LY_TYPE_INT16: + prev_num = num = value->int16; + break; + case LY_TYPE_INT32: + prev_num = num = value->int32; + break; + case LY_TYPE_INT64: + prev_num = num = value->int64; + break; + default: + break; + } + num = htole64(num); + if (num == prev_num) { + /* values are equal, little-endian or int8 */ + *dynamic = 0; + if (value_len) { + *value_len = integer_lyb_size[value->realtype->basetype]; + } + return &value->int64; + } else { + /* values differ, big-endian */ + buf = calloc(1, integer_lyb_size[value->realtype->basetype]); + LY_CHECK_RET(!buf, NULL); + + *dynamic = 1; + if (value_len) { + *value_len = integer_lyb_size[value->realtype->basetype]; + } + memcpy(buf, &num, integer_lyb_size[value->realtype->basetype]); + return buf; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_uint(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + uint64_t num = 0; + int base = 0; + char *canon; + struct lysc_type_num *type_num = (struct lysc_type_num *)type; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != integer_lyb_size[type->basetype]) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB unsigned integer value size %zu (expected %zu).", + value_len, integer_lyb_size[type->basetype]); + goto cleanup; + } + + /* copy the integer and correct the byte order */ + memcpy(&num, value, value_len); + num = le64toh(num); + } else { + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, &base, err); + LY_CHECK_GOTO(ret, cleanup); + + /* parse the integer */ + switch (type->basetype) { + case LY_TYPE_UINT8: + ret = lyplg_type_parse_uint("uint8", base, UINT64_C(255), value, value_len, &num, err); + break; + case LY_TYPE_UINT16: + ret = lyplg_type_parse_uint("uint16", base, UINT64_C(65535), value, value_len, &num, err); + break; + case LY_TYPE_UINT32: + ret = lyplg_type_parse_uint("uint32", base, UINT64_C(4294967295), value, value_len, &num, err); + break; + case LY_TYPE_UINT64: + ret = lyplg_type_parse_uint("uint64", base, UINT64_C(18446744073709551615), value, value_len, &num, err); + break; + default: + LOGINT(ctx); + ret = LY_EINT; + } + LY_CHECK_GOTO(ret, cleanup); + } + + /* store value, matters for big-endian */ + switch (type->basetype) { + case LY_TYPE_UINT8: + storage->uint8 = num; + break; + case LY_TYPE_UINT16: + storage->uint16 = num; + break; + case LY_TYPE_UINT32: + storage->uint32 = num; + break; + case LY_TYPE_UINT64: + storage->uint64 = num; + break; + default: + break; + } + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* generate canonical value */ + LY_CHECK_ERR_GOTO(asprintf(&canon, "%" PRIu64, num) == -1, ret = LY_EMEM, cleanup); + + /* store it */ + ret = lydict_insert_zc(ctx, canon, (const char **)&storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + + /* validate range of the number */ + if (type_num->range) { + ret = lyplg_type_validate_range(type->basetype, type_num->range, num, storage->_canonical, + strlen(storage->_canonical), err); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_uint(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + switch (val1->realtype->basetype) { + case LY_TYPE_UINT8: + if (val1->uint8 != val2->uint8) { + return LY_ENOT; + } + break; + case LY_TYPE_UINT16: + if (val1->uint16 != val2->uint16) { + return LY_ENOT; + } + break; + case LY_TYPE_UINT32: + if (val1->uint32 != val2->uint32) { + return LY_ENOT; + } + break; + case LY_TYPE_UINT64: + if (val1->uint64 != val2->uint64) { + return LY_ENOT; + } + break; + default: + break; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + uint64_t num = 0; + void *buf; + + if (format == LY_VALUE_LYB) { + switch (value->realtype->basetype) { + case LY_TYPE_UINT8: + num = value->uint8; + break; + case LY_TYPE_UINT16: + num = value->uint16; + break; + case LY_TYPE_UINT32: + num = value->uint32; + break; + case LY_TYPE_UINT64: + num = value->uint64; + break; + default: + break; + } + num = htole64(num); + if (num == value->uint64) { + /* values are equal, little-endian or uint8 */ + *dynamic = 0; + if (value_len) { + *value_len = integer_lyb_size[value->realtype->basetype]; + } + return &value->uint64; + } else { + /* values differ, big-endian */ + buf = calloc(1, integer_lyb_size[value->realtype->basetype]); + LY_CHECK_RET(!buf, NULL); + + *dynamic = 1; + if (value_len) { + *value_len = integer_lyb_size[value->realtype->basetype]; + } + memcpy(buf, &num, integer_lyb_size[value->realtype->basetype]); + return buf; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Plugin information for integer types implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_integer[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_UINT8_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_uint, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_uint, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_uint, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 1, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_UINT16_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_uint, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_uint, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_uint, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 2, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_UINT32_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_uint, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_uint, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_uint, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 4, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_UINT64_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_uint, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_uint, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_uint, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 8, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_INT8_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_int, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_int, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_int, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 1, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_INT16_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_int, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_int, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_int, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 2, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_INT32_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_int, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_int, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_int, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 4, + }, { + .module = "", + .revision = NULL, + .name = LY_TYPE_INT64_STR, + + .plugin.id = "libyang 2 - integers, version 1", + .plugin.store = lyplg_type_store_int, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_int, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_int, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 8, + }, + {0} +}; diff --git a/src/plugins_types/ipv4_address.c b/src/plugins_types/ipv4_address.c new file mode 100644 index 0000000..f7b297c --- /dev/null +++ b/src/plugins_types/ipv4_address.c @@ -0,0 +1,377 @@ +/** + * @file ipv4_address.c + * @author Michal Vasko + * @brief ietf-inet-types ipv4-address type plugin. + * + * Copyright (c) 2019-2021 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 /* strndup */ + +#include "plugins_types.h" + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIPv4Address ipv4-address (ietf-inet-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 4 | yes | `struct in_addr *` | IPv4 address in network-byte order | + * | string length | no | `char *` | IPv4 address zone string | + */ + +static void lyplg_type_free_ipv4_address(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Convert IP address with optional zone to network-byte order. + * + * @param[in] value Value to convert. + * @param[in] value_len Length of @p value. + * @param[in] options Type store callback options. + * @param[in] ctx libyang context with dictionary. + * @param[in,out] addr Allocated value for the address. + * @param[out] zone Ipv6 address zone in dictionary. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +ipv4address_str2ip(const char *value, size_t value_len, uint32_t options, const struct ly_ctx *ctx, + struct in_addr *addr, const char **zone, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *addr_no_zone; + char *zone_ptr = NULL, *addr_dyn = NULL; + size_t zone_len; + + /* store zone and get the string IPv4 address without it */ + if ((zone_ptr = ly_strnchr(value, '%', value_len))) { + /* there is a zone index */ + zone_len = value_len - (zone_ptr - value) - 1; + ret = lydict_insert(ctx, zone_ptr + 1, zone_len, zone); + LY_CHECK_GOTO(ret, cleanup); + + /* get the IP without it */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + *zone_ptr = '\0'; + addr_no_zone = value; + } else { + addr_dyn = strndup(value, zone_ptr - value); + addr_no_zone = addr_dyn; + } + } else { + /* no zone */ + *zone = NULL; + + /* get the IP terminated with zero */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + /* we can use the value directly */ + addr_no_zone = value; + } else { + addr_dyn = strndup(value, value_len); + addr_no_zone = addr_dyn; + } + } + + /* store the IPv4 address in network-byte order */ + if (!inet_pton(AF_INET, addr_no_zone, addr)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv4 address \"%s\".", addr_no_zone); + goto cleanup; + } + + /* restore the value */ + if ((options & LYPLG_TYPE_STORE_DYNAMIC) && zone_ptr) { + *zone_ptr = '%'; + } + +cleanup: + free(addr_dyn); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ipv4-address ietf-inet-types type. + */ +static LY_ERR +lyplg_type_store_ipv4_address(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *value_str = value; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_ipv4_address *val; + size_t i; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len < 4) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv4-address value size %zu " + "(expected at least 4).", value_len); + goto cleanup; + } + for (i = 4; i < value_len; ++i) { + if (!isalnum(value_str[i])) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv4-address zone character 0x%x.", + value_str[i]); + goto cleanup; + } + } + + /* store IP address */ + memcpy(&val->addr, value, sizeof val->addr); + + /* store zone, if any */ + if (value_len > 4) { + ret = lydict_insert(ctx, value_str + 4, value_len - 4, &val->zone); + LY_CHECK_GOTO(ret, cleanup); + } else { + val->zone = NULL; + } + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* get the network-byte order address */ + ret = ipv4address_str2ip(value, value_len, options, ctx, &val->addr, &val->zone, err); + LY_CHECK_GOTO(ret, cleanup); + + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_ipv4_address(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ipv4-address ietf-inet-types type. + */ +static LY_ERR +lyplg_type_compare_ipv4_address(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_ipv4_address *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + /* zones are NULL or in the dictionary */ + if (memcmp(&v1->addr, &v2->addr, sizeof v1->addr) || (v1->zone != v2->zone)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +/** + * @brief Implementation of ::lyplg_type_print_clb for the ipv4-address ietf-inet-types type. + */ +static const void * +lyplg_type_print_ipv4_address(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_ipv4_address *val; + size_t zone_len; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + if (!val->zone) { + /* address-only, const */ + *dynamic = 0; + if (value_len) { + *value_len = sizeof val->addr; + } + return &val->addr; + } + + /* dynamic */ + zone_len = strlen(val->zone); + ret = malloc(sizeof val->addr + zone_len); + LY_CHECK_RET(!ret, NULL); + + memcpy(ret, &val->addr, sizeof val->addr); + memcpy(ret + sizeof val->addr, val->zone, zone_len); + + *dynamic = 1; + if (value_len) { + *value_len = sizeof val->addr + zone_len; + } + return ret; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* '%' + zone */ + zone_len = val->zone ? strlen(val->zone) + 1 : 0; + ret = malloc(INET_ADDRSTRLEN + zone_len); + LY_CHECK_RET(!ret, NULL); + + /* get the address in string */ + if (!inet_ntop(AF_INET, &val->addr, ret, INET_ADDRSTRLEN)) { + free(ret); + LOGERR(ctx, LY_EVALID, "Failed to get IPv4 address in string (%s).", strerror(errno)); + return NULL; + } + + /* add zone */ + if (zone_len) { + sprintf(ret + strlen(ret), "%%%s", val->zone); + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the ipv4-address ietf-inet-types type. + */ +static LY_ERR +lyplg_type_dup_ipv4_address(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_ipv4_address *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + + memcpy(&dup_val->addr, &orig_val->addr, sizeof orig_val->addr); + ret = lydict_insert(ctx, orig_val->zone, 0, &dup_val->zone); + LY_CHECK_GOTO(ret, error); + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_ipv4_address(ctx, dup); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_free_clb for the ipv4-address ietf-inet-types type. + */ +static void +lyplg_type_free_ipv4_address(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_ipv4_address *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + lydict_remove(ctx, val->zone); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for ipv4-address type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_ipv4_address[] = { + { + .module = "ietf-inet-types", + .revision = "2013-07-15", + .name = "ipv4-address", + + .plugin.id = "libyang 2 - ipv4-address, version 1", + .plugin.store = lyplg_type_store_ipv4_address, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_ipv4_address, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_ipv4_address, + .plugin.duplicate = lyplg_type_dup_ipv4_address, + .plugin.free = lyplg_type_free_ipv4_address, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/ipv4_address_no_zone.c b/src/plugins_types/ipv4_address_no_zone.c new file mode 100644 index 0000000..91fe677 --- /dev/null +++ b/src/plugins_types/ipv4_address_no_zone.c @@ -0,0 +1,221 @@ +/** + * @file ipv4_address_no_zone.c + * @author Michal Vasko + * @brief ietf-inet-types ipv4-address-no-zone type plugin. + * + * Copyright (c) 2019-2021 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 /* strndup */ + +#include "plugins_types.h" + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIPv4AddressNoZone ipv4-address-no-zone (ietf-inet-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 4 | yes | `struct in_addr *` | IPv4 address in network-byte order | + */ + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ipv4-address-no-zone ietf-inet-types type. + */ +static LY_ERR +lyplg_type_store_ipv4_address_no_zone(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_ipv4_address_no_zone *val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != 4) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv4-address-no-zone value size %zu " + "(expected 4).", value_len); + goto cleanup; + } + + /* store IP address */ + memcpy(&val->addr, value, 4); + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* we always need a dynamic value */ + if (!(options & LYPLG_TYPE_STORE_DYNAMIC)) { + value = strndup(value, value_len); + LY_CHECK_ERR_GOTO(!value, ret = LY_EMEM, cleanup); + + options |= LYPLG_TYPE_STORE_DYNAMIC; + } + + /* get the network-byte order address */ + if (!inet_pton(AF_INET, value, &val->addr)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv4 address \"%s\".", (char *)value); + goto cleanup; + } + + /* store canonical value */ + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ipv4-address-no-zone ietf-inet-types type. + */ +static LY_ERR +lyplg_type_compare_ipv4_address_no_zone(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_ipv4_address_no_zone *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + if (memcmp(&v1->addr, &v2->addr, sizeof v1->addr)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +/** + * @brief Implementation of ::lyplg_type_print_clb for the ipv4-address-no-zone ietf-inet-types type. + */ +static const void * +lyplg_type_print_ipv4_address_no_zone(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_ipv4_address_no_zone *val; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = 4; + } + return &val->addr; + } + + /* generate canonical value if not already (loaded from LYB) */ + if (!value->_canonical) { + ret = malloc(INET_ADDRSTRLEN); + LY_CHECK_RET(!ret, NULL); + + /* get the address in string */ + if (!inet_ntop(AF_INET, &val->addr, ret, INET_ADDRSTRLEN)) { + free(ret); + LOGERR(ctx, LY_EVALID, "Failed to get IPv4 address in string (%s).", strerror(errno)); + return NULL; + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Plugin information for ipv4-address-no-zone type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_ipv4_address_no_zone[] = { + { + .module = "ietf-inet-types", + .revision = "2013-07-15", + .name = "ipv4-address-no-zone", + + .plugin.id = "libyang 2 - ipv4-address-no-zone, version 1", + .plugin.store = lyplg_type_store_ipv4_address_no_zone, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_ipv4_address_no_zone, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_ipv4_address_no_zone, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = 4, + }, + {0} +}; diff --git a/src/plugins_types/ipv4_prefix.c b/src/plugins_types/ipv4_prefix.c new file mode 100644 index 0000000..6f13eee --- /dev/null +++ b/src/plugins_types/ipv4_prefix.c @@ -0,0 +1,337 @@ +/** + * @file ipv4_prefix.c + * @author Michal Vasko + * @brief ietf-inet-types ipv4-prefix type plugin. + * + * Copyright (c) 2019-2021 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 /* strndup */ + +#include "plugins_types.h" + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIPv4Prefix ipv4-prefix (ietf-inet-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 4 | yes | `struct in_addr *` | IPv4 address in network-byte order | + * | 1 | yes | `uint8_t *` | prefix length up to 32 | + */ + +#define LYB_VALUE_LEN 5 + +static void lyplg_type_free_ipv4_prefix(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Convert IP address with a prefix in string to a binary network-byte order value. + * + * @param[in] value String to convert. + * @param[in] value_len Length of @p value. + * @param[in,out] addr Allocated address value to fill. + * @param[out] prefix Prefix length. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +ipv4prefix_str2ip(const char *value, size_t value_len, struct in_addr *addr, uint8_t *prefix, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *pref_str; + char *mask_str = NULL; + + /* it passed the pattern validation */ + pref_str = ly_strnchr(value, '/', value_len); + ly_strntou8(pref_str + 1, value_len - (pref_str + 1 - value), prefix); + + /* get just the network prefix */ + mask_str = strndup(value, pref_str - value); + LY_CHECK_ERR_GOTO(!mask_str, ret = LY_EMEM, cleanup); + + /* convert it to netword-byte order */ + if (inet_pton(AF_INET, mask_str, addr) != 1) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv4 address \"%s\".", mask_str); + goto cleanup; + } + +cleanup: + free(mask_str); + return ret; +} + +/** + * @brief Zero host-portion of the IP address. + * + * @param[in,out] addr IP address. + * @param[in] prefix Prefix length. + */ +static void +ipv4prefix_zero_host(struct in_addr *addr, uint8_t prefix) +{ + uint32_t i, mask; + + /* zero host bits */ + mask = 0; + for (i = 0; i < 32; ++i) { + mask <<= 1; + if (prefix > i) { + mask |= 1; + } + } + mask = htonl(mask); + addr->s_addr &= mask; +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ipv4-prefix ietf-inet-types type. + */ +static LY_ERR +lyplg_type_store_ipv4_prefix(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_ipv4_prefix *val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != LYB_VALUE_LEN) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv4-prefix value size %zu (expected %d).", + value_len, LYB_VALUE_LEN); + goto cleanup; + } + if (((uint8_t *)value)[4] > 32) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv4-prefix prefix length %" PRIu8 ".", + ((uint8_t *)value)[4]); + goto cleanup; + } + + /* store addr + prefix */ + memcpy(val, value, value_len); + + /* zero host */ + ipv4prefix_zero_host(&val->addr, val->prefix); + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* get the mask in network-byte order */ + ret = ipv4prefix_str2ip(value, value_len, &val->addr, &val->prefix, err); + LY_CHECK_GOTO(ret, cleanup); + + /* zero host */ + ipv4prefix_zero_host(&val->addr, val->prefix); + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_ipv4_prefix(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ietf-inet-types ipv4-prefix type. + */ +static LY_ERR +lyplg_type_compare_ipv4_prefix(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_ipv4_prefix *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + if (memcmp(v1, v2, sizeof *v1)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ietf-inet-types ipv4-prefix type. + */ +static const void * +lyplg_type_print_ipv4_prefix(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_ipv4_prefix *val; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = LYB_VALUE_LEN; + } + return val; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* IPv4 mask + '/' + prefix */ + ret = malloc(INET_ADDRSTRLEN + 3); + LY_CHECK_RET(!ret, NULL); + + /* convert back to string */ + if (!inet_ntop(AF_INET, &val->addr, ret, INET_ADDRSTRLEN)) { + free(ret); + return NULL; + } + + /* add the prefix */ + sprintf(ret + strlen(ret), "/%" PRIu8, val->prefix); + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the ietf-inet-types ipv4-prefix type. + */ +static LY_ERR +lyplg_type_dup_ipv4_prefix(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_ipv4_prefix *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + memcpy(dup_val, orig_val, sizeof *orig_val); + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_ipv4_prefix(ctx, dup); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_free_clb for the ietf-inet-types ipv4-prefix type. + */ +static void +lyplg_type_free_ipv4_prefix(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_ipv4_prefix *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); +} + +/** + * @brief Plugin information for ipv4-prefix type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_ipv4_prefix[] = { + { + .module = "ietf-inet-types", + .revision = "2013-07-15", + .name = "ipv4-prefix", + + .plugin.id = "libyang 2 - ipv4-prefix, version 1", + .plugin.store = lyplg_type_store_ipv4_prefix, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_ipv4_prefix, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_ipv4_prefix, + .plugin.duplicate = lyplg_type_dup_ipv4_prefix, + .plugin.free = lyplg_type_free_ipv4_prefix, + .plugin.lyb_data_len = LYB_VALUE_LEN, + }, + {0} +}; diff --git a/src/plugins_types/ipv6_address.c b/src/plugins_types/ipv6_address.c new file mode 100644 index 0000000..74f5c62 --- /dev/null +++ b/src/plugins_types/ipv6_address.c @@ -0,0 +1,378 @@ +/** + * @file ipv6_address.c + * @author Michal Vasko + * @brief ietf-inet-types ipv6-address type plugin. + * + * Copyright (c) 2019-2021 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 /* strndup */ + +#include "plugins_types.h" + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIPv6Address ipv6-address (ietf-inet-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 16 | yes | `struct in6_addr *` | IPv6 address in network-byte order | + * | string length | no | `char *` | IPv6 address zone string | + */ + +static void lyplg_type_free_ipv6_address(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Convert IP address with optional zone to network-byte order. + * + * @param[in] value Value to convert. + * @param[in] value_len Length of @p value. + * @param[in] options Type store callback options. + * @param[in] ctx libyang context with dictionary. + * @param[in,out] addr Allocated value for the address. + * @param[out] zone Ipv6 address zone in dictionary. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +ipv6address_str2ip(const char *value, size_t value_len, uint32_t options, const struct ly_ctx *ctx, + struct in6_addr *addr, const char **zone, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *addr_no_zone; + char *zone_ptr = NULL, *addr_dyn = NULL; + size_t zone_len; + + /* store zone and get the string IPv6 address without it */ + if ((zone_ptr = ly_strnchr(value, '%', value_len))) { + /* there is a zone index */ + zone_len = value_len - (zone_ptr - value) - 1; + ret = lydict_insert(ctx, zone_ptr + 1, zone_len, zone); + LY_CHECK_GOTO(ret, cleanup); + + /* get the IP without it */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + *zone_ptr = '\0'; + addr_no_zone = value; + } else { + addr_dyn = strndup(value, zone_ptr - value); + addr_no_zone = addr_dyn; + } + } else { + /* no zone */ + *zone = NULL; + + /* get the IP terminated with zero */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + /* we can use the value directly */ + addr_no_zone = value; + } else { + addr_dyn = strndup(value, value_len); + addr_no_zone = addr_dyn; + } + } + + /* store the IPv6 address in network-byte order */ + if (!inet_pton(AF_INET6, addr_no_zone, addr)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address \"%s\".", addr_no_zone); + goto cleanup; + } + + /* restore the value */ + if ((options & LYPLG_TYPE_STORE_DYNAMIC) && zone_ptr) { + *zone_ptr = '%'; + } + +cleanup: + free(addr_dyn); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ipv6-address ietf-inet-types type. + */ +static LY_ERR +lyplg_type_store_ipv6_address(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *value_str = value; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_ipv6_address *val; + size_t i; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len < 16) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv6-address value size %zu " + "(expected at least 16).", value_len); + goto cleanup; + } + for (i = 16; i < value_len; ++i) { + if (!isalnum(value_str[i])) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv6-address zone character 0x%x.", + value_str[i]); + goto cleanup; + } + } + + /* store IP address */ + memcpy(&val->addr, value, sizeof val->addr); + + /* store zone, if any */ + if (value_len > 16) { + ret = lydict_insert(ctx, value_str + 16, value_len - 16, &val->zone); + LY_CHECK_GOTO(ret, cleanup); + } else { + val->zone = NULL; + } + + /* success */ + goto cleanup; + } + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* get the network-byte order address */ + ret = ipv6address_str2ip(value, value_len, options, ctx, &val->addr, &val->zone, err); + LY_CHECK_GOTO(ret, cleanup); + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_ipv6_address(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ipv6-address ietf-inet-types type. + */ +static LY_ERR +lyplg_type_compare_ipv6_address(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_ipv6_address *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + /* zones are NULL or in the dictionary */ + if (memcmp(&v1->addr, &v2->addr, sizeof v1->addr) || (v1->zone != v2->zone)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +/** + * @brief Implementation of ::lyplg_type_print_clb for the ipv6-address ietf-inet-types type. + */ +static const void * +lyplg_type_print_ipv6_address(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_ipv6_address *val; + size_t zone_len; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + if (!val->zone) { + /* address-only, const */ + *dynamic = 0; + if (value_len) { + *value_len = sizeof val->addr; + } + return &val->addr; + } + + /* dynamic */ + zone_len = strlen(val->zone); + ret = malloc(sizeof val->addr + zone_len); + LY_CHECK_RET(!ret, NULL); + + memcpy(ret, &val->addr, sizeof val->addr); + memcpy(ret + sizeof val->addr, val->zone, zone_len); + + *dynamic = 1; + if (value_len) { + *value_len = sizeof val->addr + zone_len; + } + return ret; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* '%' + zone */ + zone_len = val->zone ? strlen(val->zone) + 1 : 0; + ret = malloc(INET6_ADDRSTRLEN + zone_len); + LY_CHECK_RET(!ret, NULL); + + /* get the address in string */ + if (!inet_ntop(AF_INET6, &val->addr, ret, INET6_ADDRSTRLEN)) { + free(ret); + LOGERR(ctx, LY_EVALID, "Failed to get IPv6 address in string (%s).", strerror(errno)); + return NULL; + } + + /* add zone */ + if (zone_len) { + sprintf(ret + strlen(ret), "%%%s", val->zone); + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the ipv6-address ietf-inet-types type. + */ +static LY_ERR +lyplg_type_dup_ipv6_address(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_ipv6_address *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + memcpy(&dup_val->addr, &orig_val->addr, sizeof orig_val->addr); + ret = lydict_insert(ctx, orig_val->zone, 0, &dup_val->zone); + LY_CHECK_GOTO(ret, error); + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_ipv6_address(ctx, dup); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_free_clb for the ipv6-address ietf-inet-types type. + */ +static void +lyplg_type_free_ipv6_address(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_ipv6_address *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + lydict_remove(ctx, val->zone); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for ipv6-address type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_ipv6_address[] = { + { + .module = "ietf-inet-types", + .revision = "2013-07-15", + .name = "ipv6-address", + + .plugin.id = "libyang 2 - ipv6-address, version 1", + .plugin.store = lyplg_type_store_ipv6_address, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_ipv6_address, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_ipv6_address, + .plugin.duplicate = lyplg_type_dup_ipv6_address, + .plugin.free = lyplg_type_free_ipv6_address, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/ipv6_address_no_zone.c b/src/plugins_types/ipv6_address_no_zone.c new file mode 100644 index 0000000..26fbf80 --- /dev/null +++ b/src/plugins_types/ipv6_address_no_zone.c @@ -0,0 +1,312 @@ +/** + * @file ipv6_address_no_zone.c + * @author Michal Vasko + * @brief ietf-inet-types ipv6-address-no-zone type plugin. + * + * Copyright (c) 2019-2021 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 /* strndup */ + +#include "plugins_types.h" + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIPv6AddressNoZone ipv6-address-no-zone (ietf-inet-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 16 | yes | `struct in6_addr *` | IPv6 address in network-byte order | + */ + +static void lyplg_type_free_ipv6_address_no_zone(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Convert IP address to network-byte order. + * + * @param[in] value Value to convert. + * @param[in] value_len Length of @p value. + * @param[in] options Type store callback options. + * @param[in,out] addr Allocated value for the address. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +ipv6addressnozone_str2ip(const char *value, size_t value_len, uint32_t options, struct in6_addr *addr, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *addr_str; + char *addr_dyn = NULL; + + /* get the IP terminated with zero */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + /* we can use the value directly */ + addr_str = value; + } else { + addr_dyn = strndup(value, value_len); + addr_str = addr_dyn; + } + + /* store the IPv6 address in network-byte order */ + if (!inet_pton(AF_INET6, addr_str, addr)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address \"%s\".", addr_str); + goto cleanup; + } + +cleanup: + free(addr_dyn); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ipv6-address-no-zone ietf-inet-types type. + */ +static LY_ERR +lyplg_type_store_ipv6_address_no_zone(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + size_t value_len, uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_ipv6_address_no_zone *val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != 16) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv6-address-no-zone value size %zu " + "(expected 16).", value_len); + goto cleanup; + } + + if ((options & LYPLG_TYPE_STORE_DYNAMIC) && LYPLG_TYPE_VAL_IS_DYN(val)) { + /* use the value directly */ + storage->dyn_mem = (void *)value; + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + } else { + /* allocate value */ + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + + /* store IP address */ + memcpy(&val->addr, value, sizeof val->addr); + } + + /* success */ + goto cleanup; + } + + /* allocate value */ + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* get the network-byte order address */ + ret = ipv6addressnozone_str2ip(value, value_len, options, &val->addr, err); + LY_CHECK_GOTO(ret, cleanup); + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_ipv6_address_no_zone(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ipv6-address-no-zone ietf-inet-types type. + */ +static LY_ERR +lyplg_type_compare_ipv6_address_no_zone(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_ipv6_address_no_zone *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + if (memcmp(&v1->addr, &v2->addr, sizeof v1->addr)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +/** + * @brief Implementation of ::lyplg_type_print_clb for the ipv6-address-no-zone ietf-inet-types type. + */ +static const void * +lyplg_type_print_ipv6_address_no_zone(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_ipv6_address_no_zone *val; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = sizeof val->addr; + } + return &val->addr; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* '%' + zone */ + ret = malloc(INET6_ADDRSTRLEN); + LY_CHECK_RET(!ret, NULL); + + /* get the address in string */ + if (!inet_ntop(AF_INET6, &val->addr, ret, INET6_ADDRSTRLEN)) { + free(ret); + LOGERR(ctx, LY_EVALID, "Failed to get IPv6 address in string (%s).", strerror(errno)); + return NULL; + } + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the ipv6-address-no-zone ietf-inet-types type. + */ +static LY_ERR +lyplg_type_dup_ipv6_address_no_zone(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_ipv6_address_no_zone *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + memcpy(&dup_val->addr, &orig_val->addr, sizeof orig_val->addr); + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_ipv6_address_no_zone(ctx, dup); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_free_clb for the ipv6-address-no-zone ietf-inet-types type. + */ +static void +lyplg_type_free_ipv6_address_no_zone(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_ipv6_address_no_zone *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); +} + +/** + * @brief Plugin information for ipv6-address-no-zone type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_ipv6_address_no_zone[] = { + { + .module = "ietf-inet-types", + .revision = "2013-07-15", + .name = "ipv6-address-no-zone", + + .plugin.id = "libyang 2 - ipv6-address-no-zone, version 1", + .plugin.store = lyplg_type_store_ipv6_address_no_zone, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_ipv6_address_no_zone, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_ipv6_address_no_zone, + .plugin.duplicate = lyplg_type_dup_ipv6_address_no_zone, + .plugin.free = lyplg_type_free_ipv6_address_no_zone, + .plugin.lyb_data_len = 16, + }, + {0} +}; diff --git a/src/plugins_types/ipv6_prefix.c b/src/plugins_types/ipv6_prefix.c new file mode 100644 index 0000000..8e62311 --- /dev/null +++ b/src/plugins_types/ipv6_prefix.c @@ -0,0 +1,351 @@ +/** + * @file ipv6_prefix.c + * @author Michal Vasko + * @brief ietf-inet-types ipv6-prefix type plugin. + * + * Copyright (c) 2019-2021 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 /* strndup */ + +#include "plugins_types.h" + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesIPv6Prefix ipv6-prefix (ietf-inet-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 16 | yes | `struct in6_addr *` | IPv6 address in network-byte order | + * | 1 | yes | `uint8_t *` | prefix length up to 128 | + */ + +#define LYB_VALUE_LEN 17 + +static void lyplg_type_free_ipv6_prefix(const struct ly_ctx *ctx, struct lyd_value *value); + +/** + * @brief Convert IP address with a prefix in string to a binary network-byte order value. + * + * @param[in] value String to convert. + * @param[in] value_len Length of @p value. + * @param[in,out] addr Allocated address value to fill. + * @param[out] prefix Prefix length. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +ipv6prefix_str2ip(const char *value, size_t value_len, struct in6_addr *addr, uint8_t *prefix, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *pref_str; + char *mask_str = NULL; + + /* it passed the pattern validation */ + pref_str = ly_strnchr(value, '/', value_len); + ly_strntou8(pref_str + 1, value_len - (pref_str + 1 - value), prefix); + + /* get just the network prefix */ + mask_str = strndup(value, pref_str - value); + LY_CHECK_ERR_GOTO(!mask_str, ret = LY_EMEM, cleanup); + + /* convert it to netword-byte order */ + if (inet_pton(AF_INET6, mask_str, addr) != 1) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to convert IPv6 address \"%s\".", mask_str); + goto cleanup; + } + +cleanup: + free(mask_str); + return ret; +} + +/** + * @brief Zero host-portion of the IP address. + * + * @param[in,out] addr IP address. + * @param[in] prefix Prefix length. + */ +static void +ipv6prefix_zero_host(struct in6_addr *addr, uint8_t prefix) +{ + uint32_t i, j, mask; + + /* zero host bits */ + for (i = 0; i < 4; ++i) { + mask = 0; + for (j = 0; j < 32; ++j) { + mask <<= 1; + if (prefix > (i * 32) + j) { + mask |= 1; + } + } + mask = htonl(mask); + ((uint32_t *)addr->s6_addr)[i] &= mask; + } +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the ipv6-prefix ietf-inet-types type. + */ +static LY_ERR +lyplg_type_store_ipv6_prefix(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_ipv6_prefix *val; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + if (format == LY_VALUE_LYB) { + /* validation */ + if (value_len != LYB_VALUE_LEN) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv6-prefix value size %zu (expected %d).", + value_len, LYB_VALUE_LEN); + goto cleanup; + } + if (((uint8_t *)value)[16] > 128) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB ipv6-prefix prefix length %" PRIu8 ".", + ((uint8_t *)value)[16]); + goto cleanup; + } + + /* store/allocate value */ + if ((options & LYPLG_TYPE_STORE_DYNAMIC) && LYPLG_TYPE_VAL_IS_DYN(val)) { + storage->dyn_mem = (void *)value; + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + + LYD_VALUE_GET(storage, val); + } else { + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + + memcpy(val, value, value_len); + } + + /* zero host */ + ipv6prefix_zero_host(&val->addr, val->prefix); + + /* success */ + goto cleanup; + } + + /* allocate value */ + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* get the mask in network-byte order */ + ret = ipv6prefix_str2ip(value, value_len, &val->addr, &val->prefix, err); + LY_CHECK_GOTO(ret, cleanup); + + /* zero host */ + ipv6prefix_zero_host(&val->addr, val->prefix); + + if (format == LY_VALUE_CANON) { + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_ipv6_prefix(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ietf-inet-types ipv6-prefix type. + */ +static LY_ERR +lyplg_type_compare_ipv6_prefix(const struct lyd_value *val1, const struct lyd_value *val2) +{ + struct lyd_value_ipv6_prefix *v1, *v2; + + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + LYD_VALUE_GET(val1, v1); + LYD_VALUE_GET(val2, v2); + + if (memcmp(v1, v2, sizeof *v1)) { + return LY_ENOT; + } + return LY_SUCCESS; +} + +/** + * @brief Implementation of ::lyplg_type_compare_clb for the ietf-inet-types ipv6-prefix type. + */ +static const void * +lyplg_type_print_ipv6_prefix(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_ipv6_prefix *val; + char *ret; + + LYD_VALUE_GET(value, val); + + if (format == LY_VALUE_LYB) { + *dynamic = 0; + if (value_len) { + *value_len = LYB_VALUE_LEN; + } + return val; + } + + /* generate canonical value if not already */ + if (!value->_canonical) { + /* IPv6 mask + '/' + prefix */ + ret = malloc(INET6_ADDRSTRLEN + 4); + LY_CHECK_RET(!ret, NULL); + + /* convert back to string */ + if (!inet_ntop(AF_INET6, &val->addr, ret, INET6_ADDRSTRLEN)) { + free(ret); + return NULL; + } + + /* add the prefix */ + sprintf(ret + strlen(ret), "/%" PRIu8, val->prefix); + + /* store it */ + if (lydict_insert_zc(ctx, ret, (const char **)&value->_canonical)) { + LOGMEM(ctx); + return NULL; + } + } + + /* use the cached canonical value */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; +} + +/** + * @brief Implementation of ::lyplg_type_dup_clb for the ietf-inet-types ipv6-prefix type. + */ +static LY_ERR +lyplg_type_dup_ipv6_prefix(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret; + struct lyd_value_ipv6_prefix *orig_val, *dup_val; + + memset(dup, 0, sizeof *dup); + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, error); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, ret = LY_EMEM, error); + + LYD_VALUE_GET(original, orig_val); + memcpy(dup_val, orig_val, sizeof *orig_val); + + dup->realtype = original->realtype; + return LY_SUCCESS; + +error: + lyplg_type_free_ipv6_prefix(ctx, dup); + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_free_clb for the ietf-inet-types ipv6-prefix type. + */ +static void +lyplg_type_free_ipv6_prefix(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_ipv6_prefix *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + LYPLG_TYPE_VAL_INLINE_DESTROY(val); +} + +/** + * @brief Plugin information for ipv6-prefix type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_ipv6_prefix[] = { + { + .module = "ietf-inet-types", + .revision = "2013-07-15", + .name = "ipv6-prefix", + + .plugin.id = "libyang 2 - ipv6-prefix, version 1", + .plugin.store = lyplg_type_store_ipv6_prefix, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_ipv6_prefix, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_ipv6_prefix, + .plugin.duplicate = lyplg_type_dup_ipv6_prefix, + .plugin.free = lyplg_type_free_ipv6_prefix, + .plugin.lyb_data_len = LYB_VALUE_LEN, + }, + {0} +}; diff --git a/src/plugins_types/leafref.c b/src/plugins_types/leafref.c new file mode 100644 index 0000000..8ab3fc5 --- /dev/null +++ b/src/plugins_types/leafref.c @@ -0,0 +1,140 @@ +/** + * @file leafref.c + * @author Radek Krejci + * @brief Built-in leafref type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesLeafref leafref (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------: | :-------: | :--: | :-----: | + * | exact same format as the leafref target |||| + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_leafref(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, + struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_leafref *type_lr = (struct lysc_type_leafref *)type; + + assert(type_lr->realtype); + + /* store the value as the real type of the leafref target */ + ret = type_lr->realtype->plugin->store(ctx, type_lr->realtype, value, value_len, options, format, prefix_data, + hints, ctx_node, storage, unres, err); + if (ret == LY_EINCOMPLETE) { + /* it is irrelevant whether the target type needs some resolving */ + ret = LY_SUCCESS; + } + LY_CHECK_RET(ret); + + if (type_lr->require_instance) { + /* needs to be resolved */ + return LY_EINCOMPLETE; + } else { + return LY_SUCCESS; + } +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_validate_leafref(const struct ly_ctx *UNUSED(ctx), const struct lysc_type *type, const struct lyd_node *ctx_node, + const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err) +{ + LY_ERR ret; + struct lysc_type_leafref *type_lr = (struct lysc_type_leafref *)type; + char *errmsg = NULL, *path; + + *err = NULL; + + if (!type_lr->require_instance) { + /* redundant to resolve */ + return LY_SUCCESS; + } + + /* check leafref target existence */ + if (lyplg_type_resolve_leafref(type_lr, ctx_node, storage, tree, NULL, &errmsg)) { + path = lyd_path(ctx_node, LYD_PATH_STD, NULL, 0); + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, path, strdup("instance-required"), "%s", errmsg); + free(errmsg); + return ret; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_leafref(const struct lyd_value *val1, const struct lyd_value *val2) +{ + return val1->realtype->plugin->compare(val1, val2); +} + +LIBYANG_API_DEF const void * +lyplg_type_print_leafref(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool *dynamic, size_t *value_len) +{ + return value->realtype->plugin->print(ctx, value, format, prefix_data, dynamic, value_len); +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_leafref(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + return original->realtype->plugin->duplicate(ctx, original, dup); +} + +LIBYANG_API_DEF void +lyplg_type_free_leafref(const struct ly_ctx *ctx, struct lyd_value *value) +{ + value->realtype->plugin->free(ctx, value); +} + +/** + * @brief Plugin information for leafref type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_leafref[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_LEAFREF_STR, + + .plugin.id = "libyang 2 - leafref, version 1", + .plugin.store = lyplg_type_store_leafref, + .plugin.validate = lyplg_type_validate_leafref, + .plugin.compare = lyplg_type_compare_leafref, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_leafref, + .plugin.duplicate = lyplg_type_dup_leafref, + .plugin.free = lyplg_type_free_leafref, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/node_instanceid.c b/src/plugins_types/node_instanceid.c new file mode 100644 index 0000000..04fb164 --- /dev/null +++ b/src/plugins_types/node_instanceid.c @@ -0,0 +1,320 @@ +/** + * @file node_instanceid.c + * @author Michal Vasko + * @brief ietf-netconf-acm node-instance-identifier type plugin. + * + * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "path.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ +#include "xpath.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesNodeInstanceIdentifier node-instance-identifier (ietf-netconf-acm) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string JSON format of the instance-identifier | + */ + +/** + * @brief Convert compiled path (node-instance-identifier) or NULL ("/") into string. + * + * @param[in] path Compiled path. + * @param[in] format Value format. + * @param[in] prefix_data Format-specific data for resolving prefixes. + * @param[out] str Printed instance-identifier. + * @return LY_ERR value. + */ +static LY_ERR +node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *prefix_data, char **str) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + char *result = NULL, quot; + const struct lys_module *mod = NULL; + ly_bool inherit_prefix = 0, d; + const char *strval; + + if (!path) { + /* special path */ + ret = ly_strcat(&result, "/"); + goto cleanup; + } + + switch (format) { + case LY_VALUE_XML: + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + /* everything is prefixed */ + inherit_prefix = 0; + break; + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* the same prefix is inherited and skipped */ + inherit_prefix = 1; + break; + } + + LY_ARRAY_FOR(path, u) { + /* new node */ + if (!inherit_prefix || (mod != path[u].node->module)) { + mod = path[u].node->module; + ret = ly_strcat(&result, "/%s:%s", lyplg_type_get_prefix(mod, format, prefix_data), path[u].node->name); + } else { + ret = ly_strcat(&result, "/%s", path[u].node->name); + } + LY_CHECK_GOTO(ret, cleanup); + + /* node predicates */ + LY_ARRAY_FOR(path[u].predicates, v) { + struct ly_path_predicate *pred = &path[u].predicates[v]; + + switch (path[u].pred_type) { + case LY_PATH_PREDTYPE_NONE: + break; + case LY_PATH_PREDTYPE_POSITION: + /* position predicate */ + ret = ly_strcat(&result, "[%" PRIu64 "]", pred->position); + break; + case LY_PATH_PREDTYPE_LIST: + /* key-predicate */ + strval = pred->value.realtype->plugin->print(path[u].node->module->ctx, &pred->value, format, prefix_data, + &d, NULL); + + /* default quote */ + quot = '\''; + if (strchr(strval, quot)) { + quot = '"'; + } + if (inherit_prefix) { + /* always the same prefix as the parent */ + ret = ly_strcat(&result, "[%s=%c%s%c]", pred->key->name, quot, strval, quot); + } else { + ret = ly_strcat(&result, "[%s:%s=%c%s%c]", lyplg_type_get_prefix(pred->key->module, format, prefix_data), + pred->key->name, quot, strval, quot); + } + if (d) { + free((char *)strval); + } + break; + case LY_PATH_PREDTYPE_LEAFLIST: + /* leaf-list-predicate */ + strval = pred->value.realtype->plugin->print(path[u].node->module->ctx, &pred->value, format, prefix_data, + &d, NULL); + + /* default quote */ + quot = '\''; + if (strchr(strval, quot)) { + quot = '"'; + } + ret = ly_strcat(&result, "[.=%c%s%c]", quot, strval, quot); + if (d) { + free((char *)strval); + } + break; + } + + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + if (ret) { + free(result); + } else { + *str = result; + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_store_clb for the node-instance-identifier ietf-netconf-acm type. + */ +static LY_ERR +lyplg_type_store_node_instanceid(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, + struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + uint32_t prefix_opt = 0; + struct ly_path *path = NULL; + char *canon; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + if ((((char *)value)[0] == '/') && (value_len == 1)) { + /* special path */ + goto store; + } + + switch (format) { + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + case LY_VALUE_XML: + prefix_opt = LY_PATH_PREFIX_MANDATORY; + break; + case LY_VALUE_CANON: + case LY_VALUE_LYB: + case LY_VALUE_JSON: + case LY_VALUE_STR_NS: + prefix_opt = LY_PATH_PREFIX_STRICT_INHERIT; + break; + } + + /* parse the value */ + ret = ly_path_parse(ctx, ctx_node, value, value_len, 0, LY_PATH_BEGIN_ABSOLUTE, prefix_opt, LY_PATH_PRED_SIMPLE, &exp); + if (ret) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid instance-identifier \"%.*s\" value - syntax error.", (int)value_len, (char *)value); + goto cleanup; + } + + if (options & LYPLG_TYPE_STORE_IMPLEMENT) { + /* implement all prefixes */ + LY_CHECK_GOTO(ret = lys_compile_expr_implement(ctx, exp, format, prefix_data, 1, unres, NULL), cleanup); + } + + /* resolve it on schema tree, use JSON format instead of LYB because for this type they are equal but for some + * nested types (such as numbers in predicates in the path) LYB would be invalid */ + ret = ly_path_compile(ctx, NULL, ctx_node, NULL, exp, (ctx_node && (ctx_node->flags & LYS_IS_OUTPUT)) ? + LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, 1, (format == LY_VALUE_LYB) ? + LY_VALUE_JSON : format, prefix_data, &path); + if (ret) { + ret = ly_err_new(err, ret, LYVE_DATA, NULL, NULL, + "Invalid instance-identifier \"%.*s\" value - semantic error.", (int)value_len, (char *)value); + goto cleanup; + } + +store: + /* store value */ + storage->target = path; + + /* store canonical value */ + if (format == LY_VALUE_CANON) { + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value, value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + } else { + /* JSON format with prefix is the canonical one */ + ret = node_instanceid_path2str(path, LY_VALUE_JSON, NULL, &canon); + LY_CHECK_GOTO(ret, cleanup); + + ret = lydict_insert_zc(ctx, canon, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + lyxp_expr_free(ctx, exp); + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_instanceid(ctx, storage); + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_print_clb for the node-instance-identifier ietf-netconf-acm type. + */ +static const void * +lyplg_type_print_node_instanceid(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool *dynamic, size_t *value_len) +{ + char *ret; + + if ((format == LY_VALUE_CANON) || (format == LY_VALUE_JSON) || (format == LY_VALUE_LYB)) { + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; + } + + /* print the value in the specific format */ + if (node_instanceid_path2str(value->target, format, prefix_data, &ret)) { + return NULL; + } + *dynamic = 1; + if (value_len) { + *value_len = strlen(ret); + } + return ret; +} + +/** + * @brief Plugin information for instance-identifier type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_node_instanceid[] = { + { + .module = "ietf-netconf-acm", + .revision = "2012-02-22", + .name = "node-instance-identifier", + + .plugin.id = "libyang 2 - node-instance-identifier, version 1", + .plugin.store = lyplg_type_store_node_instanceid, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_instanceid, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_node_instanceid, + .plugin.duplicate = lyplg_type_dup_instanceid, + .plugin.free = lyplg_type_free_instanceid, + .plugin.lyb_data_len = -1, + }, + { + .module = "ietf-netconf-acm", + .revision = "2018-02-14", + .name = "node-instance-identifier", + + .plugin.id = "libyang 2 - node-instance-identifier, version 1", + .plugin.store = lyplg_type_store_node_instanceid, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_instanceid, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_node_instanceid, + .plugin.duplicate = lyplg_type_dup_instanceid, + .plugin.free = lyplg_type_free_instanceid, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/string.c b/src/plugins_types/string.c new file mode 100644 index 0000000..4f988ef --- /dev/null +++ b/src/plugins_types/string.c @@ -0,0 +1,109 @@ +/** + * @file string.c + * @author Radek Krejci + * @brief Built-in string type plugin. + * + * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "plugins_types.h" + +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesString string (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string itself | + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_string(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT UNUSED(format), void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +/** + * @brief Plugin information for string type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_string[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_STRING_STR, + + .plugin.id = "libyang 2 - string, version 1", + .plugin.store = lyplg_type_store_string, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_simple, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/union.c b/src/plugins_types/union.c new file mode 100644 index 0000000..6e31d1e --- /dev/null +++ b/src/plugins_types/union.c @@ -0,0 +1,585 @@ +/** + * @file union.c + * @author Radek Krejci + * @brief Built-in union type plugin. + * + * Copyright (c) 2019-2021 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 /* strdup */ + +#include "plugins_types.h" + +#include +#include +#include +#include + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" +#include "plugins_internal.h" /* LY_TYPE_*_STR */ + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesUnion union (built-in) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | 4 | yes | `uint32_t *` | little-endian index of the resolved type in ::lysc_type_union.types | + * | exact same format as the resolved type |||| + * + * Note that loading union value in this format prevents it from changing its real (resolved) type. + */ + +/** + * @brief Size in bytes of the index in the LYB Binary Format. + */ +#define IDX_SIZE 4 + +/** + * @brief Assign a value to the union subvalue. + * + * @param[in] value Value for assignment. + * @param[in] value_len Length of the @p value. + * @param[out] original Destination item of the subvalue. + * @param[out] orig_len Length of the @p original. + * @param[in,out] options Flag containing LYPLG_TYPE_STORE_DYNAMIC. + * @return LY_ERR value. + */ +static LY_ERR +union_subvalue_assignment(const void *value, size_t value_len, void **original, size_t *orig_len, uint32_t *options) +{ + LY_ERR ret = LY_SUCCESS; + + if (*options & LYPLG_TYPE_STORE_DYNAMIC) { + /* The allocated value is stored and spend. */ + *original = (void *)value; + *options &= ~LYPLG_TYPE_STORE_DYNAMIC; + } else if (value_len) { + /* Make copy of the value. */ + *original = calloc(1, value_len); + LY_CHECK_ERR_RET(!*original, ret = LY_EMEM, ret); + memcpy(*original, value, value_len); + } else { + /* Empty value. */ + *original = strdup(""); + LY_CHECK_ERR_RET(!*original, ret = LY_EMEM, ret); + } + *orig_len = value_len; + + return ret; +} + +/** + * @brief Validate LYB Binary Format. + * + * @param[in] lyb_data Source of LYB data to parse. + * @param[in] lyb_data_len Length of @p lyb_data. + * @param[in] type_u Compiled type of union. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +lyb_union_validate(const void *lyb_data, size_t lyb_data_len, const struct lysc_type_union *type_u, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + uint64_t type_idx = 0; + + /* Basic validation. */ + if (lyb_data_len < IDX_SIZE) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid LYB union value size %zu (expected at least 4).", + lyb_data_len); + return ret; + } + + /* Get index in correct byte order. */ + memcpy(&type_idx, lyb_data, IDX_SIZE); + type_idx = le64toh(type_idx); + if (type_idx >= LY_ARRAY_COUNT(type_u->types)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Invalid LYB union type index %" PRIu64 " (type count %" LY_PRI_ARRAY_COUNT_TYPE ").", + type_idx, LY_ARRAY_COUNT(type_u->types)); + return ret; + } + + return ret; +} + +/** + * @brief Parse index and lyb_value from LYB Binary Format. + * + * @param[in] lyb_data Source of LYB data to parse. + * @param[in] lyb_data_len Length of @p lyb_data. + * @param[out] type_idx Index of the union type. + * @param[out] lyb_value Value after index number. If there is no value + * after the index, it is set to empty string (""). + * @param[out] lyb_value_len Length of @p lyb_value. + */ +static void +lyb_parse_union(const void *lyb_data, size_t lyb_data_len, uint32_t *type_idx, const void **lyb_value, size_t *lyb_value_len) +{ + uint64_t num = 0; + + assert(lyb_data && !(lyb_value && !lyb_value_len)); + + if (type_idx) { + memcpy(&num, lyb_data, IDX_SIZE); + num = le64toh(num); + + *type_idx = num; + } + + if (lyb_value && lyb_value_len && lyb_data_len) { + /* Get lyb_value and its length. */ + if (lyb_data_len == IDX_SIZE) { + *lyb_value_len = 0; + *lyb_value = ""; + } else { + *lyb_value_len = lyb_data_len - IDX_SIZE; + *lyb_value = (char *)lyb_data + IDX_SIZE; + } + } +} + +/** + * @brief Store subvalue as a specific type. + * + * @param[in] ctx libyang context. + * @param[in] type Specific union type to use for storing. + * @param[in] subvalue Union subvalue structure. + * @param[in] resolve Whether the value needs to be resolved (validated by a callback). + * @param[in] ctx_node Context node for prefix resolution. + * @param[in] tree Data tree for resolving (validation). + * @param[in,out] unres Global unres structure. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +union_store_type(const struct ly_ctx *ctx, struct lysc_type *type, struct lyd_value_union *subvalue, + ly_bool resolve, const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lys_glob_unres *unres, + struct ly_err_item **err) +{ + LY_ERR ret; + const void *value = NULL; + size_t value_len = 0; + + if (subvalue->format == LY_VALUE_LYB) { + lyb_parse_union(subvalue->original, subvalue->orig_len, NULL, &value, &value_len); + } else { + value = subvalue->original; + value_len = subvalue->orig_len; + } + + ret = type->plugin->store(ctx, type, value, value_len, 0, subvalue->format, subvalue->prefix_data, subvalue->hints, + subvalue->ctx_node, &subvalue->value, unres, err); + if ((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE)) { + /* clear any leftover/freed garbage */ + memset(&subvalue->value, 0, sizeof subvalue->value); + return ret; + } + + if (resolve && (ret == LY_EINCOMPLETE)) { + /* we need the value resolved */ + ret = type->plugin->validate(ctx, type, ctx_node, tree, &subvalue->value, err); + if (ret) { + /* resolve failed, we need to free the stored value */ + type->plugin->free(ctx, &subvalue->value); + } + } + + return ret; +} + +/** + * @brief Find the first valid type for a union value. + * + * @param[in] ctx libyang context. + * @param[in] types Sized array of union types. + * @param[in] subvalue Union subvalue structure. + * @param[in] resolve Whether the value needs to be resolved (validated by a callback). + * @param[in] ctx_node Context node for prefix resolution. + * @param[in] tree Data tree for resolving (validation). + * @param[out] type_idx Index of the type in which the value was stored. + * @param[in,out] unres Global unres structure. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +union_find_type(const struct ly_ctx *ctx, struct lysc_type **types, struct lyd_value_union *subvalue, + ly_bool resolve, const struct lyd_node *ctx_node, const struct lyd_node *tree, uint32_t *type_idx, + struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; + uint32_t temp_lo = 0; + + if (!types || !LY_ARRAY_COUNT(types)) { + return LY_EINVAL; + } + + *err = NULL; + + /* turn logging temporarily off */ + ly_temp_log_options(&temp_lo); + + /* use the first usable subtype to store the value */ + for (u = 0; u < LY_ARRAY_COUNT(types); ++u) { + ret = union_store_type(ctx, types[u], subvalue, resolve, ctx_node, tree, unres, err); + if ((ret == LY_SUCCESS) || (ret == LY_EINCOMPLETE)) { + break; + } + + ly_err_free(*err); + *err = NULL; + } + + if (u == LY_ARRAY_COUNT(types)) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid union value \"%.*s\" - no matching subtype found.", + (int)subvalue->orig_len, (char *)subvalue->original); + } else if (type_idx) { + *type_idx = u; + } + + /* restore logging */ + ly_temp_log_options(NULL); + return ret; +} + +/** + * @brief Fill union subvalue items: original, origin_len, format prefix_data and call 'store' function for value. + * + * @param[in] ctx libyang context. + * @param[in] type_u Compiled type of union. + * @param[in] lyb_data Input LYB data consisting of index followed by value (lyb_value). + * @param[in] lyb_data_len Length of @p lyb_data. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). + * @param[in,out] subvalue Union subvalue to be filled. + * @param[in,out] options Option containing LYPLG_TYPE_STORE_DYNAMIC. + * @param[in,out] unres Global unres structure for newly implemented modules. + * @param[out] err Error information on error. + * @return LY_ERR value. + */ +static LY_ERR +lyb_fill_subvalue(const struct ly_ctx *ctx, struct lysc_type_union *type_u, const void *lyb_data, size_t lyb_data_len, + void *prefix_data, struct lyd_value_union *subvalue, uint32_t *options, struct lys_glob_unres *unres, + struct ly_err_item **err) +{ + LY_ERR ret; + uint32_t type_idx; + const void *lyb_value = NULL; + size_t lyb_value_len = 0; + + ret = lyb_union_validate(lyb_data, lyb_data_len, type_u, err); + LY_CHECK_RET(ret); + + /* Parse lyb_data and set the lyb_value and lyb_value_len. */ + lyb_parse_union(lyb_data, lyb_data_len, &type_idx, &lyb_value, &lyb_value_len); + LY_CHECK_RET(ret); + + /* Store lyb_data to subvalue. */ + ret = union_subvalue_assignment(lyb_data, lyb_data_len, + &subvalue->original, &subvalue->orig_len, options); + LY_CHECK_RET(ret); + + if (lyb_value) { + /* Resolve prefix_data and set format. */ + ret = lyplg_type_prefix_data_new(ctx, lyb_value, lyb_value_len, LY_VALUE_LYB, prefix_data, &subvalue->format, + &subvalue->prefix_data); + LY_CHECK_RET(ret); + assert(subvalue->format == LY_VALUE_LYB); + } else { + /* The lyb_parse_union() did not find lyb_value. + * Just set format. + */ + subvalue->format = LY_VALUE_LYB; + } + + /* Use the specific type to store the value. */ + ret = union_store_type(ctx, type_u->types[type_idx], subvalue, 0, NULL, NULL, unres, err); + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_union(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, + struct lyd_value *storage, struct lys_glob_unres *unres, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lysc_type_union *type_u = (struct lysc_type_union *)type; + struct lyd_value_union *subvalue; + + *err = NULL; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, subvalue); + LY_CHECK_ERR_GOTO(!subvalue, ret = LY_EMEM, cleanup); + storage->realtype = type; + subvalue->hints = hints; + subvalue->ctx_node = ctx_node; + + if (format == LY_VALUE_LYB) { + ret = lyb_fill_subvalue(ctx, type_u, value, value_len, + prefix_data, subvalue, &options, unres, err); + LY_CHECK_GOTO((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE), cleanup); + } else { + /* Store @p value to subvalue. */ + ret = union_subvalue_assignment(value, value_len, + &subvalue->original, &subvalue->orig_len, &options); + LY_CHECK_GOTO(ret, cleanup); + + /* store format-specific data for later prefix resolution */ + ret = lyplg_type_prefix_data_new(ctx, value, value_len, format, prefix_data, &subvalue->format, + &subvalue->prefix_data); + LY_CHECK_GOTO(ret, cleanup); + + /* use the first usable subtype to store the value */ + ret = union_find_type(ctx, type_u->types, subvalue, 0, NULL, NULL, NULL, unres, err); + LY_CHECK_GOTO((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE), cleanup); + } + + /* store canonical value, if any (use the specific type value) */ + r = lydict_insert(ctx, subvalue->value._canonical, 0, &storage->_canonical); + LY_CHECK_ERR_GOTO(r, ret = r, cleanup); + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if ((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE)) { + lyplg_type_free_union(ctx, storage); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_validate_union(const struct ly_ctx *ctx, const struct lysc_type *type, const struct lyd_node *ctx_node, + const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_union *type_u = (struct lysc_type_union *)type; + struct lyd_value_union *subvalue = storage->subvalue; + + *err = NULL; + + /* because of types that do not store their own type as realtype (leafref), we are not able to call their + * validate callback (there is no way to get the type TODO could be added to struct lyd_value_union), so + * we have to perform union value storing again from scratch */ + subvalue->value.realtype->plugin->free(ctx, &subvalue->value); + + /* use the first usable subtype to store the value */ + ret = union_find_type(ctx, type_u->types, subvalue, 1, ctx_node, tree, NULL, NULL, err); + LY_CHECK_RET(ret); + + /* success, update the canonical value, if any generated */ + lydict_remove(ctx, storage->_canonical); + LY_CHECK_RET(lydict_insert(ctx, subvalue->value._canonical, 0, &storage->_canonical)); + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_compare_union(const struct lyd_value *val1, const struct lyd_value *val2) +{ + if (val1->realtype != val2->realtype) { + return LY_ENOT; + } + + if (val1->subvalue->value.realtype != val2->subvalue->value.realtype) { + return LY_ENOT; + } + return val1->subvalue->value.realtype->plugin->compare(&val1->subvalue->value, &val2->subvalue->value); +} + +/** + * @brief Create LYB data for printing. + * + * @param[in] ctx libyang context. + * @param[in] type_u Compiled type of union. + * @param[in] subvalue Union value. + * @param[in] prefix_data Format-specific data for resolving any + * prefixes (see ly_resolve_prefix()). + * @param[out] value_len Length of returned data. + * @return Pointer to created LYB data. Caller must release. + * @return NULL in case of error. + */ +static const void * +lyb_union_print(const struct ly_ctx *ctx, struct lysc_type_union *type_u, struct lyd_value_union *subvalue, + void *prefix_data, size_t *value_len) +{ + void *ret = NULL; + LY_ERR retval; + struct ly_err_item *err; + uint64_t num = 0; + uint32_t type_idx; + ly_bool dynamic; + size_t pval_len; + void *pval; + + /* Find out the index number (type_idx). The call should succeed + * because the union_find_type() has already been called in the + * lyplg_type_store_union(). + */ + if (!ctx) { + assert(subvalue->ctx_node); + ctx = subvalue->ctx_node->module->ctx; + } + subvalue->value.realtype->plugin->free(ctx, &subvalue->value); + retval = union_find_type(ctx, type_u->types, subvalue, 0, NULL, NULL, &type_idx, NULL, &err); + LY_CHECK_RET((retval != LY_SUCCESS) && (retval != LY_EINCOMPLETE), NULL); + + /* Print subvalue in LYB format. */ + pval = (void *)subvalue->value.realtype->plugin->print(NULL, &subvalue->value, LY_VALUE_LYB, prefix_data, &dynamic, + &pval_len); + LY_CHECK_RET(!pval, NULL); + + /* Create LYB data. */ + *value_len = IDX_SIZE + pval_len; + ret = malloc(*value_len); + LY_CHECK_RET(!ret, NULL); + + num = type_idx; + num = htole64(num); + memcpy(ret, &num, IDX_SIZE); + memcpy((char *)ret + IDX_SIZE, pval, pval_len); + + if (dynamic) { + free(pval); + } + + return ret; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_union(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool *dynamic, size_t *value_len) +{ + const void *ret; + struct lyd_value_union *subvalue = value->subvalue; + struct lysc_type_union *type_u = (struct lysc_type_union *)value->realtype; + size_t lyb_data_len = 0; + + if ((format == LY_VALUE_LYB) && (subvalue->format == LY_VALUE_LYB)) { + /* The return value is already ready. */ + *dynamic = 0; + if (value_len) { + *value_len = subvalue->orig_len; + } + return subvalue->original; + } else if ((format == LY_VALUE_LYB) && (subvalue->format != LY_VALUE_LYB)) { + /* The return LYB data must be created. */ + *dynamic = 1; + ret = lyb_union_print(ctx, type_u, subvalue, prefix_data, &lyb_data_len); + if (value_len) { + *value_len = lyb_data_len; + } + return ret; + } + + assert(format != LY_VALUE_LYB); + ret = (void *)subvalue->value.realtype->plugin->print(ctx, &subvalue->value, format, prefix_data, dynamic, value_len); + if (!value->_canonical && (format == LY_VALUE_CANON)) { + /* the canonical value is supposed to be stored now */ + lydict_insert(ctx, subvalue->value._canonical, 0, (const char **)&value->_canonical); + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_union(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_value_union *orig_val = original->subvalue, *dup_val; + + /* init dup value */ + memset(dup, 0, sizeof *dup); + dup->realtype = original->realtype; + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, cleanup); + + dup_val = calloc(1, sizeof *dup_val); + LY_CHECK_ERR_GOTO(!dup_val, LOGMEM(ctx); ret = LY_EMEM, cleanup); + dup->subvalue = dup_val; + + ret = orig_val->value.realtype->plugin->duplicate(ctx, &orig_val->value, &dup_val->value); + LY_CHECK_GOTO(ret, cleanup); + + if (orig_val->orig_len) { + dup_val->original = calloc(1, orig_val->orig_len); + LY_CHECK_ERR_GOTO(!dup_val->original, LOGMEM(ctx); ret = LY_EMEM, cleanup); + memcpy(dup_val->original, orig_val->original, orig_val->orig_len); + } else { + dup_val->original = strdup(""); + LY_CHECK_ERR_GOTO(!dup_val->original, LOGMEM(ctx); ret = LY_EMEM, cleanup); + } + dup_val->orig_len = orig_val->orig_len; + + dup_val->format = orig_val->format; + dup_val->ctx_node = orig_val->ctx_node; + dup_val->hints = orig_val->hints; + ret = lyplg_type_prefix_data_dup(ctx, orig_val->format, orig_val->prefix_data, &dup_val->prefix_data); + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + if (ret) { + lyplg_type_free_union(ctx, dup); + } + return ret; +} + +LIBYANG_API_DEF void +lyplg_type_free_union(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_union *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + if (val->value.realtype) { + val->value.realtype->plugin->free(ctx, &val->value); + } + lyplg_type_prefix_data_free(val->format, val->prefix_data); + free(val->original); + + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for union type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_union[] = { + { + .module = "", + .revision = NULL, + .name = LY_TYPE_UNION_STR, + + .plugin.id = "libyang 2 - union,version 1", + .plugin.store = lyplg_type_store_union, + .plugin.validate = lyplg_type_validate_union, + .plugin.compare = lyplg_type_compare_union, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_union, + .plugin.duplicate = lyplg_type_dup_union, + .plugin.free = lyplg_type_free_union, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/xpath1.0.c b/src/plugins_types/xpath1.0.c new file mode 100644 index 0000000..a15e5b7 --- /dev/null +++ b/src/plugins_types/xpath1.0.c @@ -0,0 +1,521 @@ +/** + * @file xpath1.0.c + * @author Michal Vasko + * @brief ietf-yang-types xpath1.0 type plugin. + * + * Copyright (c) 2021 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 + +#include "plugins_types.h" + +#include +#include +#include +#include + +#include "libyang.h" + +#include "common.h" +#include "compat.h" + +/* internal headers */ +#include "xml.h" +#include "xpath.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesXpath10 xpath1.0 (ietf-yang-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string JSON format of the XPath expression | + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_xpath10_print_token(const char *token, uint16_t tok_len, ly_bool is_nametest, const struct lys_module **context_mod, + const struct ly_ctx *resolve_ctx, LY_VALUE_FORMAT resolve_format, const void *resolve_prefix_data, + LY_VALUE_FORMAT get_format, void *get_prefix_data, char **token_p, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + const char *str_begin, *str_next, *prefix; + ly_bool is_prefix, has_prefix = 0; + char *str = NULL; + void *mem; + uint32_t len, str_len = 0, pref_len; + const struct lys_module *mod; + + str_begin = token; + + while (!(ret = ly_value_prefix_next(str_begin, token + tok_len, &len, &is_prefix, &str_next)) && len) { + if (!is_prefix) { + if (!has_prefix && is_nametest && (get_format == LY_VALUE_XML) && *context_mod) { + /* prefix is always needed, get it in the target format */ + prefix = lyplg_type_get_prefix(*context_mod, get_format, get_prefix_data); + if (!prefix) { + ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error."); + goto cleanup; + } + + /* append the nametest and prefix */ + mem = realloc(str, str_len + strlen(prefix) + 1 + len + 1); + LY_CHECK_ERR_GOTO(!mem, ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."), cleanup); + str = mem; + str_len += sprintf(str + str_len, "%s:%.*s", prefix, len, str_begin); + } else { + /* just append the string, we may get the first expression node without a prefix but since this + * is not strictly forbidden, allow it */ + mem = realloc(str, str_len + len + 1); + LY_CHECK_ERR_GOTO(!mem, ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."), cleanup); + str = mem; + str_len += sprintf(str + str_len, "%.*s", len, str_begin); + } + } else { + /* remember there was a prefix found */ + has_prefix = 1; + + /* resolve the module in the original format */ + mod = lyplg_type_identity_module(resolve_ctx, NULL, str_begin, len, resolve_format, resolve_prefix_data); + if (!mod && is_nametest) { + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Failed to resolve prefix \"%.*s\".", len, str_begin); + goto cleanup; + } + + if (is_nametest && ((get_format == LY_VALUE_JSON) || (get_format == LY_VALUE_LYB)) && (*context_mod == mod)) { + /* inherit the prefix and do not print it again */ + } else { + if (mod) { + /* get the prefix in the target format */ + prefix = lyplg_type_get_prefix(mod, get_format, get_prefix_data); + if (!prefix) { + ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error."); + goto cleanup; + } + pref_len = strlen(prefix); + } else { + /* invalid prefix, just copy it */ + prefix = str_begin; + pref_len = len; + } + + /* append the prefix */ + mem = realloc(str, str_len + pref_len + 2); + LY_CHECK_ERR_GOTO(!mem, ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."), cleanup); + str = mem; + str_len += sprintf(str + str_len, "%.*s:", (int)pref_len, prefix); + } + + if (is_nametest) { + /* update context module */ + *context_mod = mod; + } + } + + str_begin = str_next; + } + +cleanup: + if (ret) { + free(str); + } else { + *token_p = str; + } + return ret; +} + +/** + * @brief Print xpath1.0 subexpression in the specific format. + * + * @param[in,out] cur_idx Current index of the next token in the expression. + * @param[in] end_tok End token (including) that finishes this subexpression parsing. If 0, parse until the end. + * @param[in] context_mod Current context module, some formats (::LY_VALUE_JSON and ::LY_VALUE_LYB) inherit this module + * instead of printing it again. + * @param[in] xp_val xpath1.0 value structure. + * @param[in] format Format to print in. + * @param[in] prefix_data Format-specific prefix data. + * @param[in,out] str_value Printed value, appended to. + * @param[in,out] str_len Length of @p str_value, updated. + * @param[out] err Error structure on error. + * @return LY_ERR value. + */ +static LY_ERR +xpath10_print_subexpr_r(uint16_t *cur_idx, enum lyxp_token end_tok, const struct lys_module *context_mod, + const struct lyd_value_xpath10 *xp_val, LY_VALUE_FORMAT format, void *prefix_data, char **str_value, + uint32_t *str_len, struct ly_err_item **err) +{ + enum lyxp_token cur_tok, sub_end_tok; + char *str_tok; + void *mem; + const char *cur_exp_ptr; + ly_bool is_nt; + const struct lys_module *orig_context_mod = context_mod; + + while (*cur_idx < xp_val->exp->used) { + cur_tok = xp_val->exp->tokens[*cur_idx]; + cur_exp_ptr = xp_val->exp->expr + xp_val->exp->tok_pos[*cur_idx]; + + if ((cur_tok == LYXP_TOKEN_NAMETEST) || (cur_tok == LYXP_TOKEN_LITERAL)) { + /* tokens that may include prefixes, get them in the target format */ + is_nt = (cur_tok == LYXP_TOKEN_NAMETEST) ? 1 : 0; + LY_CHECK_RET(lyplg_type_xpath10_print_token(cur_exp_ptr, xp_val->exp->tok_len[*cur_idx], is_nt, &context_mod, + xp_val->ctx, xp_val->format, xp_val->prefix_data, format, prefix_data, &str_tok, err)); + + /* append the converted token */ + mem = realloc(*str_value, *str_len + strlen(str_tok) + 1); + LY_CHECK_ERR_GOTO(!mem, free(str_tok), error_mem); + *str_value = mem; + *str_len += sprintf(*str_value + *str_len, "%s", str_tok); + free(str_tok); + + /* token processed */ + ++(*cur_idx); + } else { + if ((cur_tok == LYXP_TOKEN_OPER_LOG) || (cur_tok == LYXP_TOKEN_OPER_UNI) || (cur_tok == LYXP_TOKEN_OPER_MATH)) { + /* copy the token with spaces around */ + mem = realloc(*str_value, *str_len + 1 + xp_val->exp->tok_len[*cur_idx] + 2); + LY_CHECK_GOTO(!mem, error_mem); + *str_value = mem; + *str_len += sprintf(*str_value + *str_len, " %.*s ", (int)xp_val->exp->tok_len[*cur_idx], cur_exp_ptr); + + /* reset context mod */ + context_mod = orig_context_mod; + } else { + /* just copy the token */ + mem = realloc(*str_value, *str_len + xp_val->exp->tok_len[*cur_idx] + 1); + LY_CHECK_GOTO(!mem, error_mem); + *str_value = mem; + *str_len += sprintf(*str_value + *str_len, "%.*s", (int)xp_val->exp->tok_len[*cur_idx], cur_exp_ptr); + } + + /* token processed but keep it in cur_tok */ + ++(*cur_idx); + + if (end_tok && (cur_tok == end_tok)) { + /* end token found */ + break; + } else if ((cur_tok == LYXP_TOKEN_BRACK1) || (cur_tok == LYXP_TOKEN_PAR1)) { + sub_end_tok = (cur_tok == LYXP_TOKEN_BRACK1) ? LYXP_TOKEN_BRACK2 : LYXP_TOKEN_PAR2; + + /* parse the subexpression separately, use the current context mod */ + LY_CHECK_RET(xpath10_print_subexpr_r(cur_idx, sub_end_tok, context_mod, xp_val, format, prefix_data, + str_value, str_len, err)); + } + } + } + + return LY_SUCCESS; + +error_mem: + return ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."); +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_print_xpath10_value(const struct lyd_value_xpath10 *xp_val, LY_VALUE_FORMAT format, void *prefix_data, + char **str_value, struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + uint16_t expr_idx = 0; + uint32_t str_len = 0; + + *str_value = NULL; + *err = NULL; + + /* recursively print the expression */ + ret = xpath10_print_subexpr_r(&expr_idx, 0, NULL, xp_val, format, prefix_data, str_value, &str_len, err); + + if (ret) { + free(*str_value); + *str_value = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_xpath10(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, + struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + struct lyd_value_xpath10 *val; + char *canon; + + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, val); + LY_CHECK_ERR_GOTO(!val, ret = LY_EMEM, cleanup); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* parse */ + ret = lyxp_expr_parse(ctx, value_len ? value : "", value_len, 1, &val->exp); + LY_CHECK_GOTO(ret, cleanup); + val->ctx = ctx; + + if (ctx_node && !strcmp(ctx_node->name, "parent-reference") && !strcmp(ctx_node->module->name, "ietf-yang-schema-mount")) { + /* special case, this type uses prefix-namespace mapping provided directly in data, keep empty for now */ + val->format = format = LY_VALUE_STR_NS; + ret = ly_set_new((struct ly_set **)&val->prefix_data); + LY_CHECK_GOTO(ret, cleanup); + } else { + /* store format-specific data and context for later prefix resolution */ + ret = lyplg_type_prefix_data_new(ctx, value, value_len, format, prefix_data, &val->format, &val->prefix_data); + LY_CHECK_GOTO(ret, cleanup); + } + + switch (format) { + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* store canonical value */ + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + break; + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + case LY_VALUE_XML: + /* JSON format with prefix is the canonical one */ + ret = lyplg_type_print_xpath10_value(val, LY_VALUE_JSON, NULL, &canon, err); + LY_CHECK_GOTO(ret, cleanup); + + ret = lydict_insert_zc(ctx, canon, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + break; + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_xpath10(ctx, storage); + } else if (val->format == LY_VALUE_STR_NS) { + /* needs validation */ + return LY_EINCOMPLETE; + } + return ret; +} + +/** + * @brief Implementation of ::lyplg_type_validate_clb for the xpath1.0 ietf-yang-types type. + */ +static LY_ERR +lyplg_type_validate_xpath10(const struct ly_ctx *UNUSED(ctx), const struct lysc_type *UNUSED(type), + const struct lyd_node *ctx_node, const struct lyd_node *UNUSED(tree), struct lyd_value *storage, + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_value_xpath10 *val; + struct ly_set *set = NULL; + uint32_t i; + const char *pref, *uri; + struct lyxml_ns *ns; + + *err = NULL; + LYD_VALUE_GET(storage, val); + + if (val->format != LY_VALUE_STR_NS) { + /* nothing to validate */ + return LY_SUCCESS; + } + + /* the XML namespace set must exist */ + assert(val->prefix_data); + + /* special handling of this particular node */ + assert(!strcmp(LYD_NAME(ctx_node), "parent-reference") && + !strcmp(ctx_node->schema->module->name, "ietf-yang-schema-mount")); + + /* get all the prefix mappings */ + if ((ret = lyd_find_xpath(ctx_node, "../../../namespace", &set))) { + goto cleanup; + } + + for (i = 0; i < set->count; ++i) { + assert(!strcmp(LYD_NAME(lyd_child(set->dnodes[i])), "prefix")); + pref = lyd_get_value(lyd_child(set->dnodes[i])); + + if (!lyd_child(set->dnodes[i])->next) { + /* missing URI - invalid mapping, skip */ + continue; + } + assert(!strcmp(LYD_NAME(lyd_child(set->dnodes[i])->next), "uri")); + uri = lyd_get_value(lyd_child(set->dnodes[i])->next); + + /* create new ns */ + ns = calloc(1, sizeof *ns); + if (!ns) { + ret = LY_EMEM; + goto cleanup; + } + ns->prefix = strdup(pref); + ns->uri = strdup(uri); + if (!ns->prefix || !ns->uri) { + free(ns->prefix); + free(ns->uri); + free(ns); + ret = LY_EMEM; + goto cleanup; + } + ns->depth = 1; + + /* add into the XML namespace set */ + if ((ret = ly_set_add(val->prefix_data, ns, 1, NULL))) { + free(ns->prefix); + free(ns->uri); + free(ns); + goto cleanup; + } + } + +cleanup: + ly_set_free(set, NULL); + if (ret == LY_EMEM) { + ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, LY_EMEM_MSG); + } else if (ret) { + ly_err_new(err, ret, LYVE_DATA, NULL, NULL, "%s", ly_errmsg(LYD_CTX(ctx_node))); + } + return ret; +} + +LIBYANG_API_DEF const void * +lyplg_type_print_xpath10(const struct ly_ctx *ctx, const struct lyd_value *value, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool *dynamic, size_t *value_len) +{ + struct lyd_value_xpath10 *val; + char *ret; + struct ly_err_item *err = NULL; + + LYD_VALUE_GET(value, val); + + /* LY_VALUE_STR_NS should never be transformed */ + if ((val->format == LY_VALUE_STR_NS) || (format == LY_VALUE_CANON) || (format == LY_VALUE_JSON) || + (format == LY_VALUE_LYB)) { + /* canonical */ + if (dynamic) { + *dynamic = 0; + } + if (value_len) { + *value_len = strlen(value->_canonical); + } + return value->_canonical; + } + + /* print in the specific format */ + if (lyplg_type_print_xpath10_value(val, format, prefix_data, &ret, &err)) { + if (err) { + LOGVAL_ERRITEM(ctx, err); + ly_err_free(err); + } + return NULL; + } + + *dynamic = 1; + if (value_len) { + *value_len = strlen(ret); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyplg_type_dup_xpath10(const struct ly_ctx *ctx, const struct lyd_value *original, struct lyd_value *dup) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_value_xpath10 *orig_val, *dup_val; + + /* init dup value */ + memset(dup, 0, sizeof *dup); + dup->realtype = original->realtype; + + ret = lydict_insert(ctx, original->_canonical, 0, &dup->_canonical); + LY_CHECK_GOTO(ret, cleanup); + + LYPLG_TYPE_VAL_INLINE_PREPARE(dup, dup_val); + LY_CHECK_ERR_GOTO(!dup_val, LOGMEM(ctx); ret = LY_EMEM, cleanup); + dup_val->ctx = ctx; + + LYD_VALUE_GET(original, orig_val); + ret = lyxp_expr_dup(ctx, orig_val->exp, 0, 0, &dup_val->exp); + LY_CHECK_GOTO(ret, cleanup); + + ret = lyplg_type_prefix_data_dup(ctx, orig_val->format, orig_val->prefix_data, &dup_val->prefix_data); + LY_CHECK_GOTO(ret, cleanup); + dup_val->format = orig_val->format; + +cleanup: + if (ret) { + lyplg_type_free_xpath10(ctx, dup); + } + return ret; +} + +LIBYANG_API_DEF void +lyplg_type_free_xpath10(const struct ly_ctx *ctx, struct lyd_value *value) +{ + struct lyd_value_xpath10 *val; + + lydict_remove(ctx, value->_canonical); + value->_canonical = NULL; + LYD_VALUE_GET(value, val); + if (val) { + lyxp_expr_free(ctx, val->exp); + lyplg_type_prefix_data_free(val->format, val->prefix_data); + + LYPLG_TYPE_VAL_INLINE_DESTROY(val); + } +} + +/** + * @brief Plugin information for xpath1.0 type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_xpath10[] = { + { + .module = "ietf-yang-types", + .revision = "2013-07-15", + .name = "xpath1.0", + + .plugin.id = "libyang 2 - xpath1.0, version 1", + .plugin.store = lyplg_type_store_xpath10, + .plugin.validate = lyplg_type_validate_xpath10, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_xpath10, + .plugin.duplicate = lyplg_type_dup_xpath10, + .plugin.free = lyplg_type_free_xpath10, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/printer_data.c b/src/printer_data.c new file mode 100644 index 0000000..ea330c7 --- /dev/null +++ b/src/printer_data.c @@ -0,0 +1,159 @@ +/** + * @file printer_data.c + * @author Radek Krejci + * @brief Generic data printers functions. + * + * Copyright (c) 2015 - 2019 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "printer_data.h" + +#include + +#include "common.h" +#include "log.h" +#include "out.h" +#include "out_internal.h" +#include "printer_internal.h" +#include "tree_data.h" + +static LY_ERR +lyd_print_(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_ERR ret = LY_SUCCESS; + + switch (format) { + case LYD_XML: + ret = xml_print_data(out, root, options); + break; + case LYD_JSON: + ret = json_print_data(out, root, options); + break; + case LYD_LYB: + ret = lyb_print_data(out, root, options); + break; + case LYD_UNKNOWN: + LOGINT(root ? LYD_CTX(root) : NULL); + ret = LY_EINT; + break; + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_all(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_CHECK_ARG_RET(NULL, out, !(options & LYD_PRINT_WITHSIBLINGS), LY_EINVAL); + + /* reset the number of printed bytes */ + out->func_printed = 0; + + if (root) { + /* get first top-level sibling */ + while (root->parent) { + root = lyd_parent(root); + } + while (root->prev->next) { + root = root->prev; + } + } + + /* print each top-level sibling */ + LY_CHECK_RET(lyd_print_(out, root, format, options | LYD_PRINT_WITHSIBLINGS)); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_tree(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_CHECK_ARG_RET(NULL, out, !(options & LYD_PRINT_WITHSIBLINGS), LY_EINVAL); + + /* reset the number of printed bytes */ + out->func_printed = 0; + + /* print the subtree */ + LY_CHECK_RET(lyd_print_(out, root, format, options)); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_mem(char **strp, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_ERR ret; + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, strp, LY_EINVAL); + + /* init */ + *strp = NULL; + + LY_CHECK_RET(ly_out_new_memory(strp, 0, &out)); + ret = lyd_print_(out, root, format, options); + ly_out_free(out, NULL, 0); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_fd(int fd, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_ERR ret; + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, fd != -1, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_fd(fd, &out)); + ret = lyd_print_(out, root, format, options); + ly_out_free(out, NULL, 0); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_file(FILE *f, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_ERR ret; + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, f, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_file(f, &out)); + ret = lyd_print_(out, root, format, options); + ly_out_free(out, NULL, 0); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_path(const char *path, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_ERR ret; + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, path, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_filepath(path, &out)); + ret = lyd_print_(out, root, format, options); + ly_out_free(out, NULL, 0); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_print_clb(ly_write_clb writeclb, void *user_data, const struct lyd_node *root, LYD_FORMAT format, uint32_t options) +{ + LY_ERR ret; + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, writeclb, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_clb(writeclb, user_data, &out)); + ret = lyd_print_(out, root, format, options); + ly_out_free(out, NULL, 0); + return ret; +} diff --git a/src/printer_data.h b/src/printer_data.h new file mode 100644 index 0000000..fb2a5fb --- /dev/null +++ b/src/printer_data.h @@ -0,0 +1,196 @@ +/** + * @file printer_data.h + * @author Radek Krejci + * @brief Data printers for libyang + * + * Copyright (c) 2015-2019 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_PRINTER_DATA_H_ +#define LY_PRINTER_DATA_H_ + +#include +#include + +#include "log.h" +#include "out.h" +#include "tree_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_out; + +/** + * @page howtoDataPrinters Printing Data + * + * Data printers allows to serialize internal representation of a data tree in a specific format. libyang + * supports the following data formats for printing: + * + * - XML + * + * Basic format as specified in rules of mapping YANG modeled data to XML in + * [RFC 6020](http://tools.ietf.org/html/rfc6020). + * + * - JSON + * + * The alternative data format available in RESTCONF protocol. Specification of JSON encoding of data modeled by YANG + * can be found in [RFC 7951](https://tools.ietf.org/html/rfc7951). + * + * By default, both formats are printed with indentation (formatting), which can be avoided by ::LYD_PRINT_SHRINK + * [printer option](@ref dataprinterflags)). Other options adjust e.g. [with-defaults mode](@ref howtoDataWD). + * + * Besides the legacy functions from libyang 1.x (::lyd_print_clb(), ::lyd_print_fd(), ::lyd_print_file(), ::lyd_print_mem() + * and ::lyd_print_path()) printing data into the specified output, there are also new functions using + * [output handler](@ref howtoOutput) introduced in libyang 2.0. In contrast to + * [schema printers](@ref howtoSchemaPrinters), there is no limit of the functionality and the functions can be used + * interchangeable. The only think to note is that the new functions ::lyd_print_all() and ::lyd_print_tree() does not + * accept ::LYD_PRINT_WITHSIBLINGS [printer option](@ref dataprinterflags)) since this flag differentiate the functions + * themselves. + * + * Functions List + * -------------- + * - ::lyd_print_all() + * - ::lyd_print_tree() + * - ::lyd_print_mem() + * - ::lyd_print_fd() + * - ::lyd_print_file() + * - ::lyd_print_path() + * - ::lyd_print_clb() + */ + +/** + * @ingroup datatree + * @defgroup dataprinterflags Data printer flags + * + * Options to change default behavior of the data printers. + * + * @{ + */ +#define LYD_PRINT_WITHSIBLINGS 0x01 /**< Flag for printing also the (following) sibling nodes of the data node. + The flag is not allowed for ::lyd_print_all() and ::lyd_print_tree(). */ +#define LYD_PRINT_SHRINK LY_PRINT_SHRINK /**< Flag for output without indentation and formatting new lines. */ +#define LYD_PRINT_KEEPEMPTYCONT 0x04 /**< Preserve empty non-presence containers */ +#define LYD_PRINT_WD_MASK 0xF0 /**< Mask for with-defaults modes */ +#define LYD_PRINT_WD_EXPLICIT 0x00 /**< Explicit with-defaults mode. Only the data explicitly being present in + the data tree are printed, so the implicitly added default nodes are + not printed. Note that this is the default value when no WD option is + specified. */ +#define LYD_PRINT_WD_TRIM 0x10 /**< Trim mode avoids printing the nodes with the value equal to their + default value */ +#define LYD_PRINT_WD_ALL 0x20 /**< With this option, all the nodes are printed as none of them are + considered default */ +#define LYD_PRINT_WD_ALL_TAG 0x40 /**< Same as ::LYD_PRINT_WD_ALL but also adds attribute 'default' with value 'true' to + all nodes that has its default value. The 'default' attribute has namespace: + urn:ietf:params:xml:ns:netconf:default:1.0 and thus the attributes are + printed only when the ietf-netconf-with-defaults module is present in libyang + context (but in that case this namespace is always printed). */ +#define LYD_PRINT_WD_IMPL_TAG 0x80 /**< Same as ::LYD_PRINT_WD_ALL_TAG but the attributes are added only to the nodes that + are not explicitly present in the original data tree despite their + value is equal to their default value. There is the same limitation regarding + the presence of ietf-netconf-with-defaults module in libyang context. */ +/** + * @} + */ + +/** + * @brief Print the whole data tree of the root, including all the siblings. + * + * @param[in] out Printer handler for a specific output. Use ly_out_*() functions to create and free the handler. + * @param[in] root The root element of the tree to print, can be any sibling. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags) except ::LYD_PRINT_WITHSIBLINGS. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_all(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * @brief Print the selected data subtree. + * + * @param[in] out Printer handler for a specific output. Use ly_out_*() functions to create and free the handler. + * @param[in] root The root element of the subtree to print. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags) except ::LYD_PRINT_WITHSIBLINGS. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_tree(struct ly_out *out, const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * @brief Print data tree in the specified format. + * + * @param[out] strp Pointer to store the resulting dump. + * @param[in] root The root element of the (sub)tree to print. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_mem(char **strp, const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * @brief Print data tree in the specified format. + * + * @param[in] fd File descriptor where to print the data. + * @param[in] root The root element of the (sub)tree to print. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_fd(int fd, const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * @brief Print data tree in the specified format. + * + * @param[in] f File stream where to print the data. + * @param[in] root The root element of the (sub)tree to print. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_file(FILE *f, const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * @brief Print data tree in the specified format. + * + * @param[in] path File path where to print the data. + * @param[in] root The root element of the (sub)tree to print. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_path(const char *path, const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * @brief Print data tree in the specified format. + * + * @param[in] writeclb Callback function to write the data (see write(1)). + * @param[in] user_data Optional caller-specific argument to be passed to the \p writeclb callback. + * @param[in] root The root element of the (sub)tree to print. + * @param[in] format Output format. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_print_clb(ly_write_clb writeclb, void *user_data, const struct lyd_node *root, + LYD_FORMAT format, uint32_t options); + +/** + * @brief Check whether the node should be printed based on the printing options. + * + * @param[in] node Node to check. + * @param[in] options [Data printer flags](@ref dataprinterflags). + * @return 0 if not, + * @return non-0 if should be printed. + */ +LIBYANG_API_DECL ly_bool lyd_node_should_print(const struct lyd_node *node, uint32_t options); + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PRINTER_DATA_H_ */ diff --git a/src/printer_internal.h b/src/printer_internal.h new file mode 100644 index 0000000..c835567 --- /dev/null +++ b/src/printer_internal.h @@ -0,0 +1,218 @@ +/** + * @file printer_internal.h + * @author Radek Krejci + * @brief Internal structures and functions for libyang + * + * Copyright (c) 2015-2019 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_PRINTER_INTERNAL_H_ +#define LY_PRINTER_INTERNAL_H_ + +#include "out.h" +#include "plugins_exts.h" +#include "printer_data.h" +#include "printer_schema.h" + +struct lysp_module; +struct lysp_submodule; + +/** + * @brief Generic YANG schema printer context + * + * Note that the YANG extensions API provides getter to the members for the extension plugins. + */ +struct lyspr_ctx { + struct ly_out *out; /**< output specification */ + uint16_t level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */ + uint16_t flags; /**< internal flags for use by printer */ + uint32_t options; /**< Schema output options (see @ref schemaprinterflags). */ + const struct lys_module *module; /**< schema to print */ +}; + +/** + * @brief YANG schema provided from plugin extension for printer_tree. + * + * The YANG extensions API provides setting functions. + */ +struct lyspr_tree_schema { + ly_bool compiled; /**< Flag if it is a compiled schema. */ + + union { + struct lysc_node *ctree; /**< Compiled schema. */ + struct lysp_node *ptree; /**< Parsed schema. */ + }; + union { + lyplg_ext_sprinter_ctree_override_clb cn_overr; /**< Override clb function for compiled node. */ + lyplg_ext_sprinter_ptree_override_clb pn_overr; /**< Override clb function for parsed node. */ + }; +}; + +/** + * @brief Context used between plugin extension and printer_tree. + * + * The YANG extensions API provides setting functions. + */ +struct lyspr_tree_ctx { + struct lyspr_tree_schema *schemas; /**< Parsed or compiled schemas ([sized array](@ref sizedarrays)) */ + void *plugin_priv; /**< Private data from plugin which printer_tree does not use. */ + + void (*free_plugin_priv)(void *plugin_priv); /**< Release function for lyspr_tree_ctx.plugin_priv. */ +}; + +/** + * @brief YANG printer of the parsed module. Full YANG printer. + * + * @param[in] out Output specification. + * @param[in] modp Parsed module to print. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR yang_print_parsed_module(struct ly_out *out, const struct lysp_module *modp, uint32_t options); + +/** + * @brief Helper macros for data printers + */ +#define DO_FORMAT (!(pctx->options & LY_PRINT_SHRINK)) +#define LEVEL pctx->level /**< current level */ +#define INDENT (DO_FORMAT ? (LEVEL)*2 : 0),"" /**< indentation parameters for printer functions */ +#define LEVEL_INC LEVEL++ /**< increase indentation level */ +#define LEVEL_DEC LEVEL-- /**< decrease indentation level */ + +#define XML_NS_INDENT 8 + +/** + * @brief YANG printer of the parsed submodule. Full YANG printer. + * + * @param[in] out Output specification. + * @param[in] submodp Parsed submodule to print. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR yang_print_parsed_submodule(struct ly_out *out, const struct lysp_submodule *submodp, uint32_t options); + +/** + * @brief YANG printer of the compiled schemas. + * + * This printer provides information about modules how they are understood by libyang. + * Despite the format is inspired by YANG, it is not fully compatible and should not be + * used as a standard YANG format. + * + * @param[in] out Output specification. + * @param[in] module Schema to be printed (the compiled member is used). + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR yang_print_compiled(struct ly_out *out, const struct lys_module *module, uint32_t options); + +/** + * @brief YANG printer of the compiled schema node + * + * This printer provides information about modules how they are understood by libyang. + * Despite the format is inspired by YANG, it is not fully compatible and should not be + * used as a standard YANG format. + * + * @param[in] out Output specification. + * @param[in] node Schema node to be printed including all its substatements. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR yang_print_compiled_node(struct ly_out *out, const struct lysc_node *node, uint32_t options); + +/** + * @brief YIN printer of the parsed module. Full YIN printer. + * + * @param[in] out Output specification. + * @param[in] modp Parsed module to print. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR yin_print_parsed_module(struct ly_out *out, const struct lysp_module *modp, uint32_t options); + +/** + * @brief YIN printer of the parsed submodule. Full YIN printer. + * + * @param[in] out Output specification. + * @param[in] submodp Parsed submodule to print. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR yin_print_parsed_submodule(struct ly_out *out, const struct lysp_submodule *submodp, uint32_t options); + +/** + * @brief Full YANG Tree Diagram printer. + * + * The module should be compiled and the @ref contextoptions must be set to LY_CTX_SET_PRIV_PARSED. + * If not, the printer will use parsed (unresolved) YANG schema tree, which means, + * for example, that `grouping` sections will be on the output. + * + * @param[in] out Output specification. + * @param[in] module Main module. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for ::LYS_OUT_TREE printer. + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR tree_print_module(struct ly_out *out, const struct lys_module *module, uint32_t options, + size_t line_length); + +/** + * @brief YANG Tree Diagram printer of the parsed submodule. Full Tree printer. + * + * @param[in] out Output specification. + * @param[in] submodp Parsed submodule to print. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for ::LYS_OUT_TREE printer. + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR tree_print_parsed_submodule(struct ly_out *out, const struct lysp_submodule *submodp, uint32_t options, + size_t line_length); + +/** + * @brief YANG Tree Diagram printer of the compiled schema node. + * + * @param[in] out Output specification. + * @param[in] node Schema node to be printed including all its substatements. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for ::LYS_OUT_TREE printer. + * @return LY_ERR value, number of the printed bytes is updated in ::ly_out.printed. + */ +LY_ERR tree_print_compiled_node(struct ly_out *out, const struct lysc_node *node, uint32_t options, + size_t line_length); + +/** + * @brief XML printer of YANG data. + * + * @param[in] out Output specification. + * @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 xml_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options); + +/** + * @brief JSON printer of YANG data. + * + * @param[in] out Output specification. + * @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 json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options); + +/** + * @brief LYB 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 lyb_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 new file mode 100644 index 0000000..327799b --- /dev/null +++ b/src/printer_json.c @@ -0,0 +1,1010 @@ +/** + * @file printer_json.c + * @author Radek Krejci + * @author Michal Vasko + * @brief JSON printer for libyang data structure + * + * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include + +#include "common.h" +#include "context.h" +#include "log.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" + +/** + * @brief JSON printer context. + */ +struct jsonpr_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 */ + uint16_t level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */ + uint32_t options; /**< [Data printer flags](@ref dataprinterflags) */ + const struct ly_ctx *ctx; /**< libyang context */ + + uint16_t level_printed; /* level where some data were already printed */ + struct ly_set open; /* currently open array(s) */ + const struct lyd_node *print_sibling_metadata; +}; + +/** + * @brief Mark that something was already written in the current level, + * used to decide if a comma is expected between the items + */ +#define LEVEL_PRINTED pctx->level_printed = pctx->level + +#define PRINT_COMMA \ + if (pctx->level_printed >= pctx->level) { \ + ly_print_(pctx->out, ",%s", (DO_FORMAT ? "\n" : "")); \ + } + +static LY_ERR json_print_node(struct jsonpr_ctx *pctx, const struct lyd_node *node); + +/** + * @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 the JSON array ('[') for the specified @p node + * + * @param[in] ctx JSON printer context. + * @param[in] node First node of the array. + * @return LY_ERR value. + */ +static LY_ERR +json_print_array_open(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + ly_print_(pctx->out, "[%s", DO_FORMAT ? "\n" : ""); + LY_CHECK_RET(ly_set_add(&pctx->open, (void *)node, 0, NULL)); + LEVEL_INC; + + return LY_SUCCESS; +} + +/** + * @brief Get know if the array for the provided @p node is currently open. + * + * @param[in] ctx JSON 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 jsonpr_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 JSON array. + * + * @param[in] ctx JSON printer context. + */ +static void +json_print_array_close(struct jsonpr_ctx *pctx) +{ + LEVEL_DEC; + ly_set_rm_index(&pctx->open, pctx->open.count - 1, NULL); + ly_print_(pctx->out, "%s%*s]", DO_FORMAT ? "\n" : "", INDENT); +} + +/** + * @brief Get the node's module name to use as the @p node prefix in JSON. + * + * @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_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 +json_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 Print the @p text as JSON string - encode special characters and add quotation around the string. + * + * @param[in] out The output handler. + * @param[in] text The string to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_string(struct ly_out *out, const char *text) +{ + uint64_t i, n; + + if (!text) { + return LY_SUCCESS; + } + + ly_write_(out, "\"", 1); + for (i = n = 0; text[i]; i++) { + const unsigned char ascii = text[i]; + + if (ascii < 0x20) { + /* control character */ + ly_print_(out, "\\u%.4X", ascii); + } else { + switch (ascii) { + case '"': + ly_print_(out, "\\\""); + break; + case '\\': + ly_print_(out, "\\\\"); + break; + default: + ly_write_(out, &text[i], 1); + n++; + } + } + } + ly_write_(out, "\"", 1); + + return LY_SUCCESS; +} + +/** + * @brief Print JSON object's member name, ending by ':'. It resolves if the prefix is supposed to be printed. + * + * @param[in] ctx JSON 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 LY_ERR value. + */ +static LY_ERR +json_print_member(struct jsonpr_ctx *pctx, const struct lyd_node *node, ly_bool is_attr) +{ + PRINT_COMMA; + if ((LEVEL == 1) || json_nscmp(node, pctx->parent)) { + /* print "namespace" */ + ly_print_(pctx->out, "%*s\"%s%s:%s\":%s", INDENT, is_attr ? "@" : "", + node_prefix(node), node->schema->name, DO_FORMAT ? " " : ""); + } else { + ly_print_(pctx->out, "%*s\"%s%s\":%s", INDENT, is_attr ? "@" : "", node->schema->name, DO_FORMAT ? " " : ""); + } + + return LY_SUCCESS; +} + +/** + * @brief More generic alternative to json_print_member() to print some special cases of the member names. + * + * @param[in] ctx JSON 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 LY_ERR value. + */ +static LY_ERR +json_print_member2(struct jsonpr_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; + + PRINT_COMMA; + + /* determine prefix string */ + if (name) { + const struct lys_module *mod; + + switch (format) { + case LY_VALUE_JSON: + module_name = name->module_name; + break; + case LY_VALUE_XML: + 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 = ""; + } + + /* print the member */ + if (module_name && (!parent || (node_prefix(parent) != module_name))) { + ly_print_(pctx->out, "%*s\"%s%s:%s\":%s", INDENT, is_attr ? "@" : "", module_name, name_str, DO_FORMAT ? " " : ""); + } else { + ly_print_(pctx->out, "%*s\"%s%s\":%s", INDENT, is_attr ? "@" : "", name_str, DO_FORMAT ? " " : ""); + } + + return LY_SUCCESS; +} + +/** + * @brief Print data value. + * + * @param[in] pctx JSON printer context. + * @param[in] ctx Context used to print the value. + * @param[in] val Data value to be printed. + * @return LY_ERR value. + */ +static LY_ERR +json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val) +{ + ly_bool dynamic; + LY_DATA_TYPE basetype; + const char *value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, NULL, &dynamic, NULL); + + basetype = val->realtype->basetype; + +print_val: + /* leafref is not supported */ + switch (basetype) { + case LY_TYPE_UNION: + /* use the resolved type */ + basetype = val->subvalue->value.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_INT64: + case LY_TYPE_UINT64: + case LY_TYPE_DEC64: + case LY_TYPE_IDENT: + json_print_string(pctx->out, value); + break; + + case LY_TYPE_INT8: + case LY_TYPE_INT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: + case LY_TYPE_BOOL: + ly_print_(pctx->out, "%s", value[0] ? value : "null"); + break; + + case LY_TYPE_EMPTY: + ly_print_(pctx->out, "[null]"); + break; + + default: + /* error */ + LOGINT_RET(pctx->ctx); + } + + if (dynamic) { + free((char *)value); + } + + return LY_SUCCESS; +} + +/** + * @brief Print all the attributes of the opaq node. + * + * @param[in] ctx JSON printer context. + * @param[in] node Opaq node where the attributes are placed. + * @param[in] wdmod With-defaults module to mark that default attribute is supposed to be printed. + * @return LY_ERR value. + */ +static LY_ERR +json_print_attribute(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node, const struct lys_module *wdmod) +{ + struct lyd_attr *attr; + + if (wdmod) { + ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name); + LEVEL_PRINTED; + } + + for (attr = node->attr; attr; attr = attr->next) { + json_print_member2(pctx, &node->node, attr->format, &attr->name, 0); + + if (attr->hints & (LYD_VALHINT_STRING | LYD_VALHINT_OCTNUM | LYD_VALHINT_HEXNUM | LYD_VALHINT_NUM64)) { + json_print_string(pctx->out, attr->value); + } else if (attr->hints & (LYD_VALHINT_BOOLEAN | LYD_VALHINT_DECNUM)) { + ly_print_(pctx->out, "%s", attr->value[0] ? attr->value : "null"); + } else if (attr->hints & LYD_VALHINT_EMPTY) { + ly_print_(pctx->out, "[null]"); + } else { + /* unknown value format with no hints, use universal string */ + json_print_string(pctx->out, attr->value); + } + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print all the metadata of the node. + * + * @param[in] ctx JSON 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. + * @return LY_ERR value. + */ +static LY_ERR +json_print_metadata(struct jsonpr_ctx *pctx, const struct lyd_node *node, const struct lys_module *wdmod) +{ + struct lyd_meta *meta; + + if (wdmod) { + ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name); + LEVEL_PRINTED; + } + + for (meta = node->meta; meta; meta = meta->next) { + PRINT_COMMA; + ly_print_(pctx->out, "%*s\"%s:%s\":%s", INDENT, meta->annotation->module->name, meta->name, DO_FORMAT ? " " : ""); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value)); + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print attributes/metadata of the given @p node. Accepts both regular as well as opaq nodes. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node where the attributes/metadata are placed. + * @param[in] inner Flag if the @p node is an inner node in the tree. + * @return LY_ERR value. + */ +static LY_ERR +json_print_attributes(struct jsonpr_ctx *pctx, const struct lyd_node *node, ly_bool inner) +{ + const struct lys_module *wdmod = 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))) { + /* 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 && (node->meta || wdmod)) { + if (inner) { + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), LY_VALUE_JSON, NULL, 1)); + } else { + LY_CHECK_RET(json_print_member(pctx, node, 1)); + } + ly_print_(pctx->out, "{%s", (DO_FORMAT ? "\n" : "")); + LEVEL_INC; + LY_CHECK_RET(json_print_metadata(pctx, node, wdmod)); + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); + LEVEL_PRINTED; + } else if (!node->schema && ((struct lyd_node_opaq *)node)->attr) { + if (inner) { + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), LY_VALUE_JSON, NULL, 1)); + } else { + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), ((struct lyd_node_opaq *)node)->format, + &((struct lyd_node_opaq *)node)->name, 1)); + } + ly_print_(pctx->out, "{%s", (DO_FORMAT ? "\n" : "")); + LEVEL_INC; + LY_CHECK_RET(json_print_attribute(pctx, (struct lyd_node_opaq *)node, wdmod)); + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print leaf data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_leaf(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value)); + LEVEL_PRINTED; + + /* print attributes as sibling */ + json_print_attributes(pctx, node, 0); + + return LY_SUCCESS; +} + +/** + * @brief Print anydata/anyxml content. + * + * @param[in] ctx JSON printer context. + * @param[in] any Anydata node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_any_content(struct jsonpr_ctx *pctx, struct lyd_node_any *any) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *iter; + const struct lyd_node *prev_parent; + uint32_t prev_opts, temp_lo = 0; + + 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 */ + 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(NULL); + } + + switch (any->value_type) { + case LYD_ANYDATA_DATATREE: + /* print as an object */ + ly_print_(pctx->out, "{%s", DO_FORMAT ? "\n" : ""); + LEVEL_INC; + + /* close opening tag and print data */ + 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 = json_print_node(pctx, iter); + LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret); + } + pctx->parent = prev_parent; + pctx->options = prev_opts; + + /* terminate the object */ + LEVEL_DEC; + if (DO_FORMAT) { + ly_print_(pctx->out, "\n%*s}", INDENT); + } else { + ly_print_(pctx->out, "}"); + } + break; + case LYD_ANYDATA_JSON: + if (!any->value.json) { + /* no content */ + if (any->schema->nodetype == LYS_ANYXML) { + ly_print_(pctx->out, "null"); + } else { + ly_print_(pctx->out, "{}"); + } + } else { + /* print without escaping special characters */ + ly_print_(pctx->out, "%s", any->value.json); + } + break; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + if (!any->value.str) { + /* no content */ + if (any->schema->nodetype == LYS_ANYXML) { + ly_print_(pctx->out, "null"); + } else { + ly_print_(pctx->out, "{}"); + } + } else { + /* print as a string */ + json_print_string(pctx->out, any->value.str); + } + break; + case LYD_ANYDATA_LYB: + /* LYB format is not supported */ + LOGWRN(pctx->ctx, "Unable to print anydata content (type %d) as JSON.", any->value_type); + break; + } + + return LY_SUCCESS; +} + +/** + * @brief Print content of a single container/list data node including its metadata. + * The envelope specific to nodes are expected to be printed by the caller. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_inner(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + struct lyd_node *child; + const struct lyd_node *prev_parent; + struct lyd_node_opaq *opaq = NULL; + ly_bool has_content = 0; + + LY_LIST_FOR(lyd_child(node), child) { + if (lyd_node_should_print(child, pctx->options)) { + break; + } + } + if (node->meta || child) { + has_content = 1; + } + if (!node->schema) { + opaq = (struct lyd_node_opaq *)node; + } + + if ((node->schema && (node->schema->nodetype == LYS_LIST)) || + (opaq && (opaq->hints != LYD_HINT_DATA) && (opaq->hints & LYD_NODEHINT_LIST))) { + ly_print_(pctx->out, "%s%*s{%s", (is_open_array(pctx, node) && (pctx->level_printed >= pctx->level)) ? + (DO_FORMAT ? ",\n" : ",") : "", INDENT, (DO_FORMAT && has_content) ? "\n" : ""); + } else { + ly_print_(pctx->out, "%s{%s", (is_open_array(pctx, node) && (pctx->level_printed >= pctx->level)) ? "," : "", + (DO_FORMAT && has_content) ? "\n" : ""); + } + LEVEL_INC; + + json_print_attributes(pctx, node, 1); + + /* print children */ + prev_parent = pctx->parent; + pctx->parent = node; + LY_LIST_FOR(lyd_child(node), child) { + LY_CHECK_RET(json_print_node(pctx, child)); + } + pctx->parent = prev_parent; + + LEVEL_DEC; + if (DO_FORMAT && has_content) { + ly_print_(pctx->out, "\n%*s}", INDENT); + } else { + ly_print_(pctx->out, "}"); + } + LEVEL_PRINTED; + + return LY_SUCCESS; +} + +/** + * @brief Print container data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static int +json_print_container(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_inner(pctx, node)); + + return LY_SUCCESS; +} + +/** + * @brief Print anydata/anyxml data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static int +json_print_any(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_any_content(pctx, (struct lyd_node_any *)node)); + LEVEL_PRINTED; + + /* print attributes as sibling */ + json_print_attributes(pctx, node, 0); + + return LY_SUCCESS; +} + +/** + * @brief Check whether a node is the last printed instance of a (leaf-)list. + * + * @param[in] ctx JSON printer context. + * @param[in] node Last printed node. + * @return Whether it is the last printed instance or not. + */ +static ly_bool +json_print_array_is_last_inst(struct jsonpr_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. + * + * In case of list, metadata are printed inside the list object. For the leaf-list, + * metadata are marked in the context for later printing after closing the array next to it using + * json_print_metadata_leaflist(). + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_leaf_list(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + const struct lys_module *wdmod = NULL; + + if (!is_open_array(pctx, node)) { + LY_CHECK_RET(json_print_member(pctx, node, 0)); + LY_CHECK_RET(json_print_array_open(pctx, node)); + if (node->schema->nodetype == LYS_LEAFLIST) { + ly_print_(pctx->out, "%*s", INDENT); + } + } else if (node->schema->nodetype == LYS_LEAFLIST) { + ly_print_(pctx->out, ",%s%*s", DO_FORMAT ? "\n" : "", INDENT); + } + + if (node->schema->nodetype == LYS_LIST) { + /* print list's content */ + LY_CHECK_RET(json_print_inner(pctx, node)); + } else { + assert(node->schema->nodetype == LYS_LEAFLIST); + + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value)); + + if (!pctx->print_sibling_metadata) { + if ((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) { + /* 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->meta || wdmod) { + /* we will be printing metadata for these siblings */ + pctx->print_sibling_metadata = node; + } + } + } + + if (json_print_array_is_last_inst(pctx, node)) { + json_print_array_close(pctx); + } + + return LY_SUCCESS; +} + +/** + * @brief Print leaf-list's metadata in case they were marked in the last call to json_print_leaf_list(). + * This function is supposed to be called when the leaf-list array is closed. + * + * @param[in] ctx JSON printer context. + * @return LY_ERR value. + */ +static LY_ERR +json_print_metadata_leaflist(struct jsonpr_ctx *pctx) +{ + const struct lyd_node *prev, *node, *iter; + const struct lys_module *wdmod = NULL; + + if (!pctx->print_sibling_metadata) { + return LY_SUCCESS; + } + + 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->print_sibling_metadata, prev = pctx->print_sibling_metadata->prev; + prev->next && matching_node(node, prev); + node = prev, prev = node->prev) {} + + LY_CHECK_RET(json_print_member(pctx, node, 1)); + ly_print_(pctx->out, "[%s", (DO_FORMAT ? "\n" : "")); + LEVEL_INC; + LY_LIST_FOR(node, iter) { + PRINT_COMMA; + if (iter->meta || (iter->flags & LYD_DEFAULT)) { + ly_print_(pctx->out, "%*s%s", INDENT, DO_FORMAT ? "{\n" : "{"); + LEVEL_INC; + LY_CHECK_RET(json_print_metadata(pctx, iter, (iter->flags & LYD_DEFAULT) ? wdmod : NULL)); + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); + } else { + ly_print_(pctx->out, "null"); + } + LEVEL_PRINTED; + if (!matching_node(iter, iter->next)) { + break; + } + } + LEVEL_DEC; + ly_print_(pctx->out, "%s%*s]", DO_FORMAT ? "\n" : "", INDENT); + LEVEL_PRINTED; + + return LY_SUCCESS; +} + +/** + * @brief Print opaq data node including its attributes. + * + * @param[in] ctx JSON printer context. + * @param[in] node Opaq node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_opaq(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node) +{ + ly_bool first = 1, last = 1; + + if (node->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) { + LY_CHECK_RET(json_print_member2(pctx, pctx->parent, node->format, &node->name, 0)); + + if (node->hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST)) { + LY_CHECK_RET(json_print_array_open(pctx, &node->node)); + } + if (node->hints & LYD_NODEHINT_LEAFLIST) { + ly_print_(pctx->out, "%*s", INDENT); + } + } else if (node->hints & LYD_NODEHINT_LEAFLIST) { + ly_print_(pctx->out, ",%s%*s", DO_FORMAT ? "\n" : "", INDENT); + } + if (node->child || (node->hints & LYD_NODEHINT_LIST)) { + LY_CHECK_RET(json_print_inner(pctx, &node->node)); + LEVEL_PRINTED; + } else { + if (node->hints & LYD_VALHINT_EMPTY) { + ly_print_(pctx->out, "[null]"); + } else if ((node->hints & (LYD_VALHINT_BOOLEAN | LYD_VALHINT_DECNUM)) && !(node->hints & LYD_VALHINT_NUM64)) { + ly_print_(pctx->out, "%s", node->value); + } else { + /* string or a large number */ + ly_print_(pctx->out, "\"%s\"", node->value); + } + LEVEL_PRINTED; + + /* attributes */ + json_print_attributes(pctx, (const struct lyd_node *)node, 0); + + } + if (last && (node->hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST))) { + json_print_array_close(pctx); + LEVEL_PRINTED; + } + + return LY_SUCCESS; +} + +/** + * @brief Print all the types of data node including its metadata. + * + * @param[in] ctx JSON printer context. + * @param[in] node Data node to print. + * @return LY_ERR value. + */ +static LY_ERR +json_print_node(struct jsonpr_ctx *pctx, const struct lyd_node *node) +{ + if (!lyd_node_should_print(node, pctx->options)) { + if (json_print_array_is_last_inst(pctx, node)) { + json_print_array_close(pctx); + } + return LY_SUCCESS; + } + + if (!node->schema) { + LY_CHECK_RET(json_print_opaq(pctx, (const struct lyd_node_opaq *)node)); + } else { + switch (node->schema->nodetype) { + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + case LYS_CONTAINER: + LY_CHECK_RET(json_print_container(pctx, node)); + break; + case LYS_LEAF: + LY_CHECK_RET(json_print_leaf(pctx, node)); + break; + case LYS_LEAFLIST: + case LYS_LIST: + LY_CHECK_RET(json_print_leaf_list(pctx, node)); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + LY_CHECK_RET(json_print_any(pctx, node)); + break; + default: + LOGINT(pctx->ctx); + return EXIT_FAILURE; + } + } + + pctx->level_printed = pctx->level; + + if (pctx->print_sibling_metadata && !matching_node(node->next, pctx->print_sibling_metadata)) { + json_print_metadata_leaflist(pctx); + pctx->print_sibling_metadata = NULL; + } + + return LY_SUCCESS; +} + +LY_ERR +json_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + const struct lyd_node *node; + struct jsonpr_ctx pctx = {0}; + const char *delimiter = (options & LYD_PRINT_SHRINK) ? "" : "\n"; + + if (!root) { + ly_print_(out, "{}%s", delimiter); + ly_print_flush(out); + return LY_SUCCESS; + } + + pctx.out = out; + pctx.parent = NULL; + pctx.level = 1; + pctx.level_printed = 0; + pctx.options = options; + pctx.ctx = LYD_CTX(root); + + /* start */ + ly_print_(pctx.out, "{%s", delimiter); + + /* content */ + LY_LIST_FOR(root, node) { + pctx.root = node; + LY_CHECK_RET(json_print_node(&pctx, node)); + if (!(options & LYD_PRINT_WITHSIBLINGS)) { + break; + } + } + + /* end */ + ly_print_(out, "%s}%s", delimiter, delimiter); + + assert(!pctx.open.count); + ly_set_erase(&pctx.open, NULL); + + ly_print_flush(out); + return LY_SUCCESS; +} diff --git a/src/printer_lyb.c b/src/printer_lyb.c new file mode 100644 index 0000000..686c2d8 --- /dev/null +++ b/src/printer_lyb.c @@ -0,0 +1,1335 @@ +/** + * @file printer_lyb.c + * @author Michal Vasko + * @brief LYB printer for libyang data structure + * + * Copyright (c) 2020 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "lyb.h" + +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "hash_table.h" +#include "log.h" +#include "out.h" +#include "out_internal.h" +#include "plugins_exts/metadata.h" +#include "printer_data.h" +#include "printer_internal.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xml.h" + +static LY_ERR lyb_print_siblings(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx); + +/** + * @brief Hash table equal callback for checking hash equality only. + * + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lyb_hash_equal_cb(void *UNUSED(val1_p), void *UNUSED(val2_p), ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + /* for this purpose, if hash matches, the value does also, we do not want 2 values to have the same hash */ + return 1; +} + +/** + * @brief Hash table equal callback for checking value pointer equality only. + * + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lyb_ptr_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + struct lysc_node *val1 = *(struct lysc_node **)val1_p; + struct lysc_node *val2 = *(struct lysc_node **)val2_p; + + if (val1 == val2) { + return 1; + } + return 0; +} + +/** + * @brief Check that sibling collision hash is safe to insert into hash table. + * + * @param[in] ht Hash table. + * @param[in] sibling Hashed sibling. + * @param[in] ht_col_id Sibling hash collision ID. + * @param[in] compare_col_id Last collision ID to compare with. + * @return LY_SUCCESS when the whole hash sequence does not collide, + * @return LY_EEXIST when the whole hash sequence sollides. + */ +static LY_ERR +lyb_hash_sequence_check(struct hash_table *ht, struct lysc_node *sibling, LYB_HASH ht_col_id, LYB_HASH compare_col_id) +{ + struct lysc_node **col_node; + + /* get the first node inserted with last hash col ID ht_col_id */ + if (lyht_find(ht, &sibling, lyb_get_hash(sibling, ht_col_id), (void **)&col_node)) { + /* there is none. valid situation */ + return LY_SUCCESS; + } + + lyht_set_cb(ht, lyb_ptr_equal_cb); + do { + int64_t j; + + for (j = (int64_t)compare_col_id; j > -1; --j) { + if (lyb_get_hash(sibling, j) != lyb_get_hash(*col_node, j)) { + /* one non-colliding hash */ + break; + } + } + if (j == -1) { + /* all whole hash sequences of nodes inserted with last hash col ID compare_col_id collide */ + lyht_set_cb(ht, lyb_hash_equal_cb); + return LY_EEXIST; + } + + /* get next node inserted with last hash col ID ht_col_id */ + } while (!lyht_find_next_with_collision_cb(ht, col_node, lyb_get_hash(*col_node, ht_col_id), lyb_hash_equal_cb, + (void **)&col_node)); + + lyht_set_cb(ht, lyb_hash_equal_cb); + return LY_SUCCESS; +} + +/** + * @brief Hash all the siblings and add them also into a separate hash table. + * + * @param[in] sibling Any sibling in all the siblings on one level. + * @param[out] ht_p Created hash table. + * @return LY_ERR value. + */ +static LY_ERR +lyb_hash_siblings(struct lysc_node *sibling, struct hash_table **ht_p) +{ + struct hash_table *ht; + const struct lysc_node *parent; + const struct lys_module *mod; + LYB_HASH i; + uint32_t getnext_opts; + + ht = lyht_new(1, sizeof(struct lysc_node *), lyb_hash_equal_cb, NULL, 1); + LY_CHECK_ERR_RET(!ht, LOGMEM(sibling->module->ctx), LY_EMEM); + + getnext_opts = 0; + if (sibling->flags & LYS_IS_OUTPUT) { + getnext_opts = LYS_GETNEXT_OUTPUT; + } + + parent = lysc_data_parent(sibling); + mod = sibling->module; + + sibling = NULL; + while ((sibling = (struct lysc_node *)lys_getnext(sibling, parent, mod->compiled, getnext_opts))) { + /* find the first non-colliding hash (or specifically non-colliding hash sequence) */ + for (i = 0; i < LYB_HASH_BITS; ++i) { + /* check that we are not colliding with nodes inserted with a lower collision ID than ours */ + int64_t j; + + for (j = (int64_t)i - 1; j > -1; --j) { + if (lyb_hash_sequence_check(ht, sibling, (LYB_HASH)j, i)) { + break; + } + } + if (j > -1) { + /* some check failed, we must use a higher collision ID */ + continue; + } + + /* try to insert node with the current collision ID */ + if (!lyht_insert_with_resize_cb(ht, &sibling, lyb_get_hash(sibling, i), lyb_ptr_equal_cb, NULL)) { + /* success, no collision */ + break; + } + + /* make sure we really cannot insert it with this hash col ID (meaning the whole hash sequence is colliding) */ + if (i && !lyb_hash_sequence_check(ht, sibling, i, i)) { + /* it can be inserted after all, even though there is already a node with the same last collision ID */ + lyht_set_cb(ht, lyb_ptr_equal_cb); + if (lyht_insert(ht, &sibling, lyb_get_hash(sibling, i), NULL)) { + LOGINT(sibling->module->ctx); + lyht_set_cb(ht, lyb_hash_equal_cb); + lyht_free(ht); + return LY_EINT; + } + lyht_set_cb(ht, lyb_hash_equal_cb); + break; + } + /* there is still another colliding schema node with the same hash sequence, try higher collision ID */ + } + + if (i == LYB_HASH_BITS) { + /* wow */ + LOGINT(sibling->module->ctx); + lyht_free(ht); + return LY_EINT; + } + } + + /* change val equal callback so that the HT is usable for finding value hashes */ + lyht_set_cb(ht, lyb_ptr_equal_cb); + + *ht_p = ht; + return LY_SUCCESS; +} + +/** + * @brief Find node hash in a hash table. + * + * @param[in] ht Hash table to search in. + * @param[in] node Node to find. + * @param[out] hash_p First non-colliding hash found. + * @return LY_ERR value. + */ +static LY_ERR +lyb_hash_find(struct hash_table *ht, struct lysc_node *node, LYB_HASH *hash_p) +{ + LYB_HASH hash; + uint32_t i; + + for (i = 0; i < LYB_HASH_BITS; ++i) { + hash = lyb_get_hash(node, i); + if (!hash) { + LOGINT_RET(node->module->ctx); + } + + if (!lyht_find(ht, &node, hash, NULL)) { + /* success, no collision */ + break; + } + } + /* cannot happen, we already calculated the hash */ + if (i == LYB_HASH_BITS) { + LOGINT_RET(node->module->ctx); + } + + *hash_p = hash; + return LY_SUCCESS; +} + +/** + * @brief Write metadata about siblings. + * + * @param[in] out Out structure. + * @param[in] sib Contains metadata that is written. + */ +static LY_ERR +lyb_write_sibling_meta(struct ly_out *out, struct lyd_lyb_sibling *sib) +{ + uint8_t meta_buf[LYB_META_BYTES]; + uint64_t num = 0; + + /* write the meta chunk information */ + num = htole64((uint64_t)sib->written & LYB_SIZE_MAX); + memcpy(meta_buf, &num, LYB_SIZE_BYTES); + num = htole64((uint64_t)sib->inner_chunks & LYB_INCHUNK_MAX); + memcpy(meta_buf + LYB_SIZE_BYTES, &num, LYB_INCHUNK_BYTES); + + LY_CHECK_RET(ly_write_skipped(out, sib->position, (char *)&meta_buf, LYB_META_BYTES)); + + return LY_SUCCESS; +} + +/** + * @brief Write LYB data fully handling the metadata. + * + * @param[in] out Out structure. + * @param[in] buf Source buffer. + * @param[in] count Number of bytes to write. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_write(struct ly_out *out, const uint8_t *buf, size_t count, struct lylyb_ctx *lybctx) +{ + LY_ARRAY_COUNT_TYPE u; + struct lyd_lyb_sibling *full, *iter; + size_t to_write; + + while (1) { + /* check for full data chunks */ + to_write = count; + full = NULL; + LY_ARRAY_FOR(lybctx->siblings, u) { + /* we want the innermost chunks resolved first, so replace previous full chunks */ + if (lybctx->siblings[u].written + to_write >= LYB_SIZE_MAX) { + /* full chunk, do not write more than allowed */ + to_write = LYB_SIZE_MAX - lybctx->siblings[u].written; + full = &lybctx->siblings[u]; + } + } + + if (!full && !count) { + break; + } + + /* we are actually writing some data, not just finishing another chunk */ + if (to_write) { + LY_CHECK_RET(ly_write_(out, (char *)buf, to_write)); + + LY_ARRAY_FOR(lybctx->siblings, u) { + /* increase all written counters */ + lybctx->siblings[u].written += to_write; + assert(lybctx->siblings[u].written <= LYB_SIZE_MAX); + } + /* decrease count/buf */ + count -= to_write; + buf += to_write; + } + + if (full) { + /* write the meta information (inner chunk count and chunk size) */ + LY_CHECK_RET(lyb_write_sibling_meta(out, full)); + + /* zero written and inner chunks */ + full->written = 0; + full->inner_chunks = 0; + + /* skip space for another chunk size */ + LY_CHECK_RET(ly_write_skip(out, LYB_META_BYTES, &full->position)); + + /* increase inner chunk count */ + for (iter = &lybctx->siblings[0]; iter != full; ++iter) { + if (iter->inner_chunks == LYB_INCHUNK_MAX) { + LOGINT(lybctx->ctx); + return LY_EINT; + } + ++iter->inner_chunks; + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Stop the current "siblings" - write its final metadata. + * + * @param[in] out Out structure. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_write_stop_siblings(struct ly_out *out, struct lylyb_ctx *lybctx) +{ + /* write the meta chunk information */ + lyb_write_sibling_meta(out, &LYB_LAST_SIBLING(lybctx)); + + LY_ARRAY_DECREMENT(lybctx->siblings); + return LY_SUCCESS; +} + +/** + * @brief Start a new "siblings" - skip bytes for its metadata. + * + * @param[in] out Out structure. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_write_start_siblings(struct ly_out *out, struct lylyb_ctx *lybctx) +{ + LY_ARRAY_COUNT_TYPE u; + + u = LY_ARRAY_COUNT(lybctx->siblings); + if (u == lybctx->sibling_size) { + LY_ARRAY_CREATE_RET(lybctx->ctx, lybctx->siblings, u + LYB_SIBLING_STEP, LY_EMEM); + lybctx->sibling_size = u + LYB_SIBLING_STEP; + } + + LY_ARRAY_INCREMENT(lybctx->siblings); + LYB_LAST_SIBLING(lybctx).written = 0; + LYB_LAST_SIBLING(lybctx).inner_chunks = 0; + + /* another inner chunk */ + for (u = 0; u < LY_ARRAY_COUNT(lybctx->siblings) - 1; ++u) { + if (lybctx->siblings[u].inner_chunks == LYB_INCHUNK_MAX) { + LOGINT(lybctx->ctx); + return LY_EINT; + } + ++lybctx->siblings[u].inner_chunks; + } + + LY_CHECK_RET(ly_write_skip(out, LYB_META_BYTES, &LYB_LAST_SIBLING(lybctx).position)); + + return LY_SUCCESS; +} + +/** + * @brief Write a number. + * + * @param[in] num Number to write. + * @param[in] bytes Actual accessible bytes of @p num. + * @param[in] out Out structure. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_write_number(uint64_t num, size_t bytes, struct ly_out *out, struct lylyb_ctx *lybctx) +{ + /* correct byte order */ + num = htole64(num); + + return lyb_write(out, (uint8_t *)&num, bytes, lybctx); +} + +/** + * @brief Write a string. + * + * @param[in] str String to write. + * @param[in] str_len Length of @p str. + * @param[in] len_size Size of @p str_len in bytes. + * @param[in] out Out structure. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_write_string(const char *str, size_t str_len, uint8_t len_size, struct ly_out *out, struct lylyb_ctx *lybctx) +{ + ly_bool error; + + if (!str) { + str = ""; + LY_CHECK_ERR_RET(str_len, LOGINT(lybctx->ctx), LY_EINT); + } + + if (!str_len) { + str_len = strlen(str); + } + + switch (len_size) { + case sizeof(uint8_t): + error = str_len > UINT8_MAX; + break; + case sizeof(uint16_t): + error = str_len > UINT16_MAX; + break; + case sizeof(uint32_t): + error = str_len > UINT32_MAX; + break; + case sizeof(uint64_t): + error = str_len > UINT64_MAX; + break; + default: + error = 1; + } + if (error) { + LOGINT(lybctx->ctx); + return LY_EINT; + } + + LY_CHECK_RET(lyb_write_number(str_len, len_size, out, lybctx)); + + LY_CHECK_RET(lyb_write(out, (const uint8_t *)str, str_len, lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print YANG module info. + * + * @param[in] out Out structure. + * @param[in] mod Module to print. + * @param[in] with_features Whether to also print enabled features or not. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_model(struct ly_out *out, const struct lys_module *mod, ly_bool with_features, struct lylyb_ctx *lybctx) +{ + LY_ERR rc = LY_SUCCESS; + uint16_t revision; + struct ly_set feat_set = {0}; + struct lysp_feature *f = NULL; + uint32_t i = 0; + int r; + + /* model name length and model name */ + LY_CHECK_GOTO(rc = lyb_write_string(mod->name, 0, sizeof(uint16_t), out, lybctx), cleanup); + + /* model revision as XXXX XXXX XXXX XXXX (2B) (year is offset from 2000) + * YYYY YYYM MMMD DDDD */ + revision = 0; + if (mod->revision) { + r = atoi(mod->revision); + r -= LYB_REV_YEAR_OFFSET; + r <<= LYB_REV_YEAR_SHIFT; + + revision |= r; + + r = atoi(mod->revision + ly_strlen_const("YYYY-")); + r <<= LYB_REV_MONTH_SHIFT; + + revision |= r; + + r = atoi(mod->revision + ly_strlen_const("YYYY-MM-")); + + revision |= r; + } + LY_CHECK_GOTO(rc = lyb_write_number(revision, sizeof revision, out, lybctx), cleanup); + + if (with_features) { + /* collect enabled module features */ + while ((f = lysp_feature_next(f, mod->parsed, &i))) { + if (f->flags & LYS_FENABLED) { + LY_CHECK_GOTO(rc = ly_set_add(&feat_set, f, 1, NULL), cleanup); + } + } + + /* print enabled feature count and their names */ + LY_CHECK_GOTO(rc = lyb_write_number(feat_set.count, sizeof(uint16_t), out, lybctx), cleanup); + for (i = 0; i < feat_set.count; ++i) { + f = feat_set.objs[i]; + LY_CHECK_GOTO(rc = lyb_write_string(f->name, 0, sizeof(uint16_t), out, lybctx), cleanup); + } + } + + /* fill cached hashes, if not already */ + lyb_cache_module_hash(mod); + +cleanup: + ly_set_erase(&feat_set, NULL); + return rc; +} + +/** + * @brief Print all used YANG modules. + * + * @param[in] out Out structure. + * @param[in] root Data root. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_data_models(struct ly_out *out, const struct lyd_node *root, struct lylyb_ctx *lybctx) +{ + struct ly_set *set; + LY_ARRAY_COUNT_TYPE u; + LY_ERR ret = LY_SUCCESS; + struct lys_module *mod; + const struct lyd_node *elem, *node; + uint32_t i; + + LY_CHECK_RET(ly_set_new(&set)); + + /* collect all data node modules */ + LY_LIST_FOR(root, elem) { + LYD_TREE_DFS_BEGIN(elem, node) { + if (node->schema) { + mod = node->schema->module; + ret = ly_set_add(set, mod, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* add also their modules deviating or augmenting them */ + LY_ARRAY_FOR(mod->deviated_by, u) { + ret = ly_set_add(set, mod->deviated_by[u], 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + LY_ARRAY_FOR(mod->augmented_by, u) { + ret = ly_set_add(set, mod->augmented_by[u], 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + /* only top-level nodes are processed */ + LYD_TREE_DFS_continue = 1; + } + + LYD_TREE_DFS_END(elem, node); + } + } + + /* now write module count on 2 bytes */ + LY_CHECK_GOTO(ret = lyb_write_number(set->count, 2, out, lybctx), cleanup); + + /* and all the used models */ + for (i = 0; i < set->count; ++i) { + LY_CHECK_GOTO(ret = lyb_print_model(out, set->objs[i], 1, lybctx), cleanup); + } + +cleanup: + ly_set_free(set, NULL); + return ret; +} + +/** + * @brief Print LYB magic number. + * + * @param[in] out Out structure. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_magic_number(struct ly_out *out) +{ + /* 'l', 'y', 'b' - 0x6c7962 */ + char magic_number[] = {'l', 'y', 'b'}; + + LY_CHECK_RET(ly_write_(out, magic_number, 3)); + + return LY_SUCCESS; +} + +/** + * @brief Print LYB header. + * + * @param[in] out Out structure. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_header(struct ly_out *out) +{ + uint8_t byte = 0; + + /* version, future flags */ + byte |= LYB_VERSION_NUM; + + LY_CHECK_RET(ly_write_(out, (char *)&byte, 1)); + + return LY_SUCCESS; +} + +/** + * @brief Print prefix data. + * + * @param[in] out Out structure. + * @param[in] format Value prefix format. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_prefix_data(struct ly_out *out, LY_VALUE_FORMAT format, const void *prefix_data, struct lylyb_ctx *lybctx) +{ + const struct ly_set *set; + const struct lyxml_ns *ns; + uint32_t i; + + switch (format) { + case LY_VALUE_XML: + set = prefix_data; + if (!set) { + /* no prefix data */ + i = 0; + LY_CHECK_RET(lyb_write(out, (uint8_t *)&i, 1, lybctx)); + break; + } + if (set->count > UINT8_MAX) { + LOGERR(lybctx->ctx, LY_EINT, "Maximum supported number of prefixes is %u.", UINT8_MAX); + return LY_EINT; + } + + /* write number of prefixes on 1 byte */ + LY_CHECK_RET(lyb_write_number(set->count, 1, out, lybctx)); + + /* write all the prefixes */ + for (i = 0; i < set->count; ++i) { + ns = set->objs[i]; + + /* prefix */ + LY_CHECK_RET(lyb_write_string(ns->prefix, 0, sizeof(uint16_t), out, lybctx)); + + /* namespace */ + LY_CHECK_RET(lyb_write_string(ns->uri, 0, sizeof(uint16_t), out, lybctx)); + } + break; + case LY_VALUE_JSON: + case LY_VALUE_LYB: + /* nothing to print */ + break; + default: + LOGINT_RET(lybctx->ctx); + } + + return LY_SUCCESS; +} + +/** + * @brief Print term node. + * + * @param[in] term Node to print. + * @param[in] out Out structure. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_term_value(struct lyd_node_term *term, struct ly_out *out, struct lylyb_ctx *lybctx) +{ + LY_ERR ret = LY_SUCCESS; + ly_bool dynamic = 0; + void *value; + size_t value_len = 0; + int32_t lyb_data_len; + lyplg_type_print_clb print; + + assert(term->value.realtype && term->value.realtype->plugin && term->value.realtype->plugin->print && + term->schema); + + /* Get length of LYB data to print. */ + lyb_data_len = term->value.realtype->plugin->lyb_data_len; + + /* Get value and also print its length only if size is not fixed. */ + print = term->value.realtype->plugin->print; + if (lyb_data_len < 0) { + /* Variable-length data. */ + + /* Get value and its length from plugin. */ + value = (void *)print(term->schema->module->ctx, &term->value, + LY_VALUE_LYB, NULL, &dynamic, &value_len); + LY_CHECK_GOTO(ret, cleanup); + + if (value_len > UINT32_MAX) { + LOGERR(lybctx->ctx, LY_EINT, "The maximum length of the LYB data " + "from a term node must not exceed %lu.", UINT32_MAX); + ret = LY_EINT; + goto cleanup; + } + + /* Print the length of the data as 64-bit unsigned integer. */ + ret = lyb_write_number(value_len, sizeof(uint64_t), out, lybctx); + LY_CHECK_GOTO(ret, cleanup); + } else { + /* Fixed-length data. */ + + /* Get value from plugin. */ + value = (void *)print(term->schema->module->ctx, &term->value, + LY_VALUE_LYB, NULL, &dynamic, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* Copy the length from the compiled node. */ + value_len = lyb_data_len; + } + + /* Print value. */ + if (value_len > 0) { + /* Print the value simply as it is. */ + ret = lyb_write(out, value, value_len, lybctx); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (dynamic) { + free(value); + } + + return ret; +} + +/** + * @brief Print YANG node metadata. + * + * @param[in] out Out structure. + * @param[in] node Data node whose metadata to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_metadata(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) +{ + uint8_t count = 0; + const struct lys_module *wd_mod = NULL; + struct lyd_meta *iter; + + /* with-defaults */ + if (node->schema->nodetype & LYD_NODE_TERM) { + if (((node->flags & LYD_DEFAULT) && (lybctx->print_options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) || + ((lybctx->print_options & LYD_PRINT_WD_ALL_TAG) && lyd_is_default(node))) { + /* we have implicit OR explicit default node, print attribute only if context include with-defaults schema */ + wd_mod = ly_ctx_get_module_latest(node->schema->module->ctx, "ietf-netconf-with-defaults"); + } + } + + /* count metadata */ + if (wd_mod) { + ++count; + } + for (iter = node->meta; iter; iter = iter->next) { + if (count == UINT8_MAX) { + LOGERR(lybctx->lybctx->ctx, LY_EINT, "Maximum supported number of data node metadata is %u.", UINT8_MAX); + return LY_EINT; + } + ++count; + } + + /* write number of metadata on 1 byte */ + LY_CHECK_RET(lyb_write(out, &count, 1, lybctx->lybctx)); + + if (wd_mod) { + /* write the "default" metadata */ + LY_CHECK_RET(lyb_print_model(out, wd_mod, 0, lybctx->lybctx)); + LY_CHECK_RET(lyb_write_string("default", 0, sizeof(uint16_t), out, lybctx->lybctx)); + LY_CHECK_RET(lyb_write_string("true", 0, sizeof(uint16_t), out, lybctx->lybctx)); + } + + /* write all the node metadata */ + LY_LIST_FOR(node->meta, iter) { + /* model */ + LY_CHECK_RET(lyb_print_model(out, iter->annotation->module, 0, lybctx->lybctx)); + + /* annotation name with length */ + LY_CHECK_RET(lyb_write_string(iter->name, 0, sizeof(uint16_t), out, lybctx->lybctx)); + + /* metadata value */ + LY_CHECK_RET(lyb_write_string(lyd_get_meta_value(iter), 0, sizeof(uint64_t), out, lybctx->lybctx)); + } + + return LY_SUCCESS; +} + +/** + * @brief Print opaque node attributes. + * + * @param[in] out Out structure. + * @param[in] node Opaque node whose attributes to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_attributes(struct ly_out *out, const struct lyd_node_opaq *node, struct lylyb_ctx *lybctx) +{ + uint8_t count = 0; + struct lyd_attr *iter; + + for (iter = node->attr; iter; iter = iter->next) { + if (count == UINT8_MAX) { + LOGERR(lybctx->ctx, LY_EINT, "Maximum supported number of data node attributes is %u.", UINT8_MAX); + return LY_EINT; + } + ++count; + } + + /* write number of attributes on 1 byte */ + LY_CHECK_RET(lyb_write(out, &count, 1, lybctx)); + + /* write all the attributes */ + LY_LIST_FOR(node->attr, iter) { + /* prefix */ + LY_CHECK_RET(lyb_write_string(iter->name.prefix, 0, sizeof(uint16_t), out, lybctx)); + + /* namespace */ + LY_CHECK_RET(lyb_write_string(iter->name.module_name, 0, sizeof(uint16_t), out, lybctx)); + + /* name */ + LY_CHECK_RET(lyb_write_string(iter->name.name, 0, sizeof(uint16_t), out, lybctx)); + + /* format */ + LY_CHECK_RET(lyb_write_number(iter->format, 1, out, lybctx)); + + /* value prefixes */ + LY_CHECK_RET(lyb_print_prefix_data(out, iter->format, iter->val_prefix_data, lybctx)); + + /* value */ + LY_CHECK_RET(lyb_write_string(iter->value, 0, sizeof(uint64_t), out, lybctx)); + } + + return LY_SUCCESS; +} + +/** + * @brief Print schema node hash. + * + * @param[in] out Out structure. + * @param[in] schema Schema node whose hash to print. + * @param[in,out] sibling_ht Cached hash table for these siblings, created if NULL. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_schema_hash(struct ly_out *out, struct lysc_node *schema, struct hash_table **sibling_ht, struct lylyb_ctx *lybctx) +{ + LY_ARRAY_COUNT_TYPE u; + uint32_t i; + LYB_HASH hash; + struct lyd_lyb_sib_ht *sib_ht; + struct lysc_node *first_sibling; + + if (!schema) { + /* opaque node, write empty hash */ + hash = 0; + LY_CHECK_RET(lyb_write(out, &hash, sizeof hash, lybctx)); + return LY_SUCCESS; + } + + /* create whole sibling HT if not already created and saved */ + if (!*sibling_ht) { + /* get first schema data sibling */ + first_sibling = (struct lysc_node *)lys_getnext(NULL, lysc_data_parent(schema), schema->module->compiled, + (schema->flags & LYS_IS_OUTPUT) ? LYS_GETNEXT_OUTPUT : 0); + LY_ARRAY_FOR(lybctx->sib_hts, u) { + if (lybctx->sib_hts[u].first_sibling == first_sibling) { + /* we have already created a hash table for these siblings */ + *sibling_ht = lybctx->sib_hts[u].ht; + break; + } + } + + if (!*sibling_ht) { + /* we must create sibling hash table */ + LY_CHECK_RET(lyb_hash_siblings(first_sibling, sibling_ht)); + + /* and save it */ + LY_ARRAY_NEW_RET(lybctx->ctx, lybctx->sib_hts, sib_ht, LY_EMEM); + + sib_ht->first_sibling = first_sibling; + sib_ht->ht = *sibling_ht; + } + } + + /* get our hash */ + LY_CHECK_RET(lyb_hash_find(*sibling_ht, schema, &hash)); + + /* write the hash */ + LY_CHECK_RET(lyb_write(out, &hash, sizeof hash, lybctx)); + + if (hash & LYB_HASH_COLLISION_ID) { + /* no collision for this hash, we are done */ + return LY_SUCCESS; + } + + /* written hash was a collision, write also all the preceding hashes */ + for (i = 0; !(hash & (LYB_HASH_COLLISION_ID >> i)); ++i) {} + + for ( ; i; --i) { + hash = lyb_get_hash(schema, i - 1); + if (!hash) { + return LY_EINT; + } + assert(hash & (LYB_HASH_COLLISION_ID >> (i - 1))); + + LY_CHECK_RET(lyb_write(out, &hash, sizeof hash, lybctx)); + } + + return LY_SUCCESS; +} + +/** + * @brief Print header for non-opaq node. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_header(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) +{ + /* write any metadata */ + LY_CHECK_RET(lyb_print_metadata(out, node, lybctx)); + + /* write node flags */ + LY_CHECK_RET(lyb_write_number(node->flags, sizeof node->flags, out, lybctx->lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print LYB node type. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_lyb_type(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) +{ + enum lylyb_node_type lyb_type; + + if (node->flags & LYD_EXT) { + assert(node->schema); + lyb_type = LYB_NODE_EXT; + } else if (!node->schema) { + lyb_type = LYB_NODE_OPAQ; + } else if (!lysc_data_parent(node->schema)) { + lyb_type = LYB_NODE_TOP; + } else { + lyb_type = LYB_NODE_CHILD; + } + + LY_CHECK_RET(lyb_write_number(lyb_type, 1, out, lybctx->lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print inner node. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_inner(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) +{ + /* write necessary basic data */ + LY_CHECK_RET(lyb_print_node_header(out, node, lybctx)); + + /* recursively write all the descendants */ + LY_CHECK_RET(lyb_print_siblings(out, lyd_child(node), lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print opaque node and its descendants. + * + * @param[in] out Out structure. + * @param[in] opaq Node to print. + * @param[in] lyd_lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_opaq(struct ly_out *out, const struct lyd_node_opaq *opaq, struct lyd_lyb_ctx *lyd_lybctx) +{ + struct lylyb_ctx *lybctx = lyd_lybctx->lybctx; + + /* write attributes */ + LY_CHECK_RET(lyb_print_attributes(out, opaq, lybctx)); + + /* write node flags */ + LY_CHECK_RET(lyb_write_number(opaq->flags, sizeof opaq->flags, out, lybctx)); + + /* prefix */ + LY_CHECK_RET(lyb_write_string(opaq->name.prefix, 0, sizeof(uint16_t), out, lybctx)); + + /* module reference */ + LY_CHECK_RET(lyb_write_string(opaq->name.module_name, 0, sizeof(uint16_t), out, lybctx)); + + /* name */ + LY_CHECK_RET(lyb_write_string(opaq->name.name, 0, sizeof(uint16_t), out, lybctx)); + + /* value */ + LY_CHECK_RET(lyb_write_string(opaq->value, 0, sizeof(uint64_t), out, lybctx)); + + /* format */ + LY_CHECK_RET(lyb_write_number(opaq->format, 1, out, lybctx)); + + /* value prefixes */ + LY_CHECK_RET(lyb_print_prefix_data(out, opaq->format, opaq->val_prefix_data, lybctx)); + + /* recursively write all the descendants */ + LY_CHECK_RET(lyb_print_siblings(out, opaq->child, lyd_lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print anydata or anyxml node. + * + * @param[in] anydata Node to print. + * @param[in] out Out structure. + * @param[in] lyd_lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_any(struct ly_out *out, struct lyd_node_any *anydata, struct lyd_lyb_ctx *lyd_lybctx) +{ + LY_ERR ret = LY_SUCCESS; + LYD_ANYDATA_VALUETYPE value_type; + int len; + char *buf = NULL; + const char *str; + struct ly_out *out2 = NULL; + struct lylyb_ctx *lybctx = lyd_lybctx->lybctx; + + if ((anydata->schema->nodetype == LYS_ANYDATA) && (anydata->value_type != LYD_ANYDATA_DATATREE)) { + LOGINT_RET(lybctx->ctx); + } + + if (anydata->value_type == LYD_ANYDATA_DATATREE) { + /* will be printed as a nested LYB data tree because the used modules need to be written */ + value_type = LYD_ANYDATA_LYB; + } else { + value_type = anydata->value_type; + } + + /* write necessary basic data */ + LY_CHECK_RET(lyb_print_node_header(out, (struct lyd_node *)anydata, lyd_lybctx)); + + /* first byte is type */ + LY_CHECK_GOTO(ret = lyb_write_number(value_type, sizeof value_type, out, lybctx), cleanup); + + if (anydata->value_type == LYD_ANYDATA_DATATREE) { + /* print LYB data tree to memory */ + LY_CHECK_GOTO(ret = ly_out_new_memory(&buf, 0, &out2), cleanup); + LY_CHECK_GOTO(ret = lyb_print_data(out2, anydata->value.tree, LYD_PRINT_WITHSIBLINGS), cleanup); + + len = lyd_lyb_data_length(buf); + assert(len != -1); + str = buf; + } else if (anydata->value_type == LYD_ANYDATA_LYB) { + len = lyd_lyb_data_length(anydata->value.mem); + assert(len != -1); + str = anydata->value.mem; + } else { + len = strlen(anydata->value.str); + str = anydata->value.str; + } + + /* followed by the content */ + LY_CHECK_GOTO(ret = lyb_write_string(str, (size_t)len, sizeof(uint64_t), out, lybctx), cleanup); + +cleanup: + ly_out_free(out2, NULL, 1); + return ret; +} + +/** + * @brief Print leaf node. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_leaf(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) +{ + /* write necessary basic data */ + LY_CHECK_RET(lyb_print_node_header(out, node, lybctx)); + + /* write term value */ + LY_CHECK_RET(lyb_print_term_value((struct lyd_node_term *)node, out, lybctx->lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print all leaflist nodes which belong to same schema. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @param[out] printed_node Last node that was printed by this function. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_leaflist(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx, + const struct lyd_node **printed_node) +{ + const struct lysc_node *schema; + + /* register a new sibling */ + LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx)); + + schema = node->schema; + + /* write all the siblings */ + LY_LIST_FOR(node, node) { + if (schema != node->schema) { + /* all leaflist nodes was printed */ + break; + } + + /* write leaf data */ + LY_CHECK_RET(lyb_print_node_leaf(out, node, lybctx)); + *printed_node = node; + } + + /* finish this sibling */ + LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx->lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print all list nodes which belong to same schema. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @param[out] printed_node Last node that was printed by this function. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node_list(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx, + const struct lyd_node **printed_node) +{ + const struct lysc_node *schema; + + /* register a new sibling */ + LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx)); + + schema = node->schema; + + LY_LIST_FOR(node, node) { + if (schema != node->schema) { + /* all list nodes was printed */ + break; + } + + /* write necessary basic data */ + LY_CHECK_RET(lyb_print_node_header(out, node, lybctx)); + + /* recursively write all the descendants */ + LY_CHECK_RET(lyb_print_siblings(out, lyd_child(node), lybctx)); + + *printed_node = node; + } + + /* finish this sibling */ + LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx->lybctx)); + + return LY_SUCCESS; +} + +/** + * @brief Print node. + * + * @param[in] out Out structure. + * @param[in,out] printed_node Current data node to print. Sets to the last printed node. + * @param[in,out] sibling_ht Cached hash table for these siblings, created if NULL. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_node(struct ly_out *out, const struct lyd_node **printed_node, struct hash_table **sibling_ht, + struct lyd_lyb_ctx *lybctx) +{ + const struct lyd_node *node = *printed_node; + + /* write node type */ + LY_CHECK_RET(lyb_print_lyb_type(out, node, lybctx)); + + /* write model info first */ + if (node->schema && ((node->flags & LYD_EXT) || !lysc_data_parent(node->schema))) { + LY_CHECK_RET(lyb_print_model(out, node->schema->module, 0, lybctx->lybctx)); + } + + if (node->flags & LYD_EXT) { + /* write schema node name */ + LY_CHECK_RET(lyb_write_string(node->schema->name, 0, sizeof(uint16_t), out, lybctx->lybctx)); + } else { + /* write schema hash */ + LY_CHECK_RET(lyb_print_schema_hash(out, (struct lysc_node *)node->schema, sibling_ht, lybctx->lybctx)); + } + + if (!node->schema) { + LY_CHECK_RET(lyb_print_node_opaq(out, (struct lyd_node_opaq *)node, lybctx)); + } else if (node->schema->nodetype & LYS_LEAFLIST) { + LY_CHECK_RET(lyb_print_node_leaflist(out, node, lybctx, &node)); + } else if (node->schema->nodetype == LYS_LIST) { + LY_CHECK_RET(lyb_print_node_list(out, node, lybctx, &node)); + } else if (node->schema->nodetype & LYD_NODE_ANY) { + LY_CHECK_RET(lyb_print_node_any(out, (struct lyd_node_any *)node, lybctx)); + } else if (node->schema->nodetype & LYD_NODE_INNER) { + LY_CHECK_RET(lyb_print_node_inner(out, node, lybctx)); + } else { + LY_CHECK_RET(lyb_print_node_leaf(out, node, lybctx)); + } + + *printed_node = node; + + return LY_SUCCESS; +} + +/** + * @brief Print siblings. + * + * @param[in] out Out structure. + * @param[in] node Current data node to print. + * @param[in] lybctx LYB context. + * @return LY_ERR value. + */ +static LY_ERR +lyb_print_siblings(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) +{ + struct hash_table *sibling_ht = NULL; + const struct lys_module *prev_mod = NULL; + ly_bool top_level; + + top_level = !LY_ARRAY_COUNT(lybctx->lybctx->siblings); + + LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx)); + + if (top_level) { + /* write all the siblings */ + LY_LIST_FOR(node, node) { + /* do not reuse sibling hash tables from different modules */ + if (!node->schema || (node->schema->module != prev_mod)) { + sibling_ht = NULL; + prev_mod = node->schema ? node->schema->module : NULL; + } + + LY_CHECK_RET(lyb_print_node(out, &node, &sibling_ht, lybctx)); + + if (!(lybctx->print_options & LYD_PRINT_WITHSIBLINGS)) { + break; + } + } + } else { + LY_LIST_FOR(node, node) { + LY_CHECK_RET(lyb_print_node(out, &node, &sibling_ht, lybctx)); + } + } + + LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx->lybctx)); + + return LY_SUCCESS; +} + +LY_ERR +lyb_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + LY_ERR ret = LY_SUCCESS; + uint8_t zero = 0; + struct lyd_lyb_ctx *lybctx; + const struct ly_ctx *ctx = root ? LYD_CTX(root) : NULL; + + lybctx = calloc(1, sizeof *lybctx); + LY_CHECK_ERR_RET(!lybctx, LOGMEM(ctx), LY_EMEM); + lybctx->lybctx = calloc(1, sizeof *lybctx->lybctx); + LY_CHECK_ERR_RET(!lybctx->lybctx, LOGMEM(ctx), LY_EMEM); + + lybctx->print_options = options; + if (root) { + lybctx->lybctx->ctx = ctx; + + if (root->schema && lysc_data_parent(root->schema)) { + LOGERR(lybctx->lybctx->ctx, LY_EINVAL, "LYB printer supports only printing top-level nodes."); + ret = LY_EINVAL; + goto cleanup; + } + } + + /* LYB magic number */ + LY_CHECK_GOTO(ret = lyb_print_magic_number(out), cleanup); + + /* LYB header */ + LY_CHECK_GOTO(ret = lyb_print_header(out), cleanup); + + /* all used models */ + LY_CHECK_GOTO(ret = lyb_print_data_models(out, root, lybctx->lybctx), cleanup); + + ret = lyb_print_siblings(out, root, lybctx); + LY_CHECK_GOTO(ret, cleanup); + + /* ending zero byte */ + LY_CHECK_GOTO(ret = lyb_write(out, &zero, sizeof zero, lybctx->lybctx), cleanup); + +cleanup: + lyd_lyb_ctx_free((struct lyd_ctx *)lybctx); + return ret; +} diff --git a/src/printer_schema.c b/src/printer_schema.c new file mode 100644 index 0000000..075c519 --- /dev/null +++ b/src/printer_schema.c @@ -0,0 +1,222 @@ +/** + * @file printer_schema.c + * @author Radek Krejci + * @brief Generic schema printers functions. + * + * Copyright (c) 2015 - 2019 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "printer_schema.h" + +#include + +#include "common.h" +#include "compat.h" +#include "log.h" +#include "out_internal.h" +#include "printer_internal.h" +#include "tree_schema.h" + +LIBYANG_API_DEF LY_ERR +lys_print_module(struct ly_out *out, const struct lys_module *module, LYS_OUTFORMAT format, size_t line_length, + uint32_t options) +{ + LY_ERR ret; + + LY_CHECK_ARG_RET(NULL, out, module, LY_EINVAL); + + /* reset number of printed bytes */ + out->func_printed = 0; + + switch (format) { + case LYS_OUT_YANG: + if (!module->parsed) { + LOGERR(module->ctx, LY_EINVAL, "Module \"%s\" parsed module missing.", module->name); + ret = LY_EINVAL; + break; + } + + ret = yang_print_parsed_module(out, module->parsed, options); + break; + case LYS_OUT_YANG_COMPILED: + if (!module->compiled) { + LOGERR(module->ctx, LY_EINVAL, "Module \"%s\" compiled module missing.", module->name); + ret = LY_EINVAL; + break; + } + + ret = yang_print_compiled(out, module, options); + break; + case LYS_OUT_YIN: + if (!module->parsed) { + LOGERR(module->ctx, LY_EINVAL, "Module \"%s\" parsed module missing.", module->name); + ret = LY_EINVAL; + break; + } + + ret = yin_print_parsed_module(out, module->parsed, options); + break; + case LYS_OUT_TREE: + if (!module->parsed) { + LOGERR(module->ctx, LY_EINVAL, "Module \"%s\" parsed module missing.", module->name); + ret = LY_EINVAL; + break; + } + ret = tree_print_module(out, module, options, line_length); + break; + /* TODO not yet implemented + case LYS_OUT_INFO: + ret = info_print_model(out, module, target_node); + break; + */ + default: + LOGERR(module->ctx, LY_EINVAL, "Unsupported output format."); + ret = LY_EINVAL; + break; + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_print_submodule(struct ly_out *out, const struct lysp_submodule *submodule, LYS_OUTFORMAT format, + size_t line_length, uint32_t options) +{ + LY_ERR ret; + + LY_CHECK_ARG_RET(NULL, out, submodule, LY_EINVAL); + + /* reset number of printed bytes */ + out->func_printed = 0; + + switch (format) { + case LYS_OUT_YANG: + ret = yang_print_parsed_submodule(out, submodule, options); + break; + case LYS_OUT_YIN: + ret = yin_print_parsed_submodule(out, submodule, options); + break; + case LYS_OUT_TREE: + ret = tree_print_parsed_submodule(out, submodule, options, line_length); + break; + /* TODO not yet implemented + case LYS_OUT_INFO: + ret = info_print_model(out, module, target_node); + break; + */ + default: + LOGERR(submodule->mod->ctx, LY_EINVAL, "Unsupported output format."); + ret = LY_EINVAL; + break; + } + + return ret; +} + +static LY_ERR +lys_print_(struct ly_out *out, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options) +{ + LY_ERR ret; + + LY_CHECK_ARG_RET(NULL, out, LY_EINVAL); + + ret = lys_print_module(out, module, format, 0, options); + + ly_out_free(out, NULL, 0); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_print_mem(char **strp, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options) +{ + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, strp, module, LY_EINVAL); + + /* init */ + *strp = NULL; + + LY_CHECK_RET(ly_out_new_memory(strp, 0, &out)); + return lys_print_(out, module, format, options); +} + +LIBYANG_API_DEF LY_ERR +lys_print_fd(int fd, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options) +{ + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, fd != -1, module, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_fd(fd, &out)); + return lys_print_(out, module, format, options); +} + +LIBYANG_API_DEF LY_ERR +lys_print_file(FILE *f, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options) +{ + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, f, module, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_file(f, &out)); + return lys_print_(out, module, format, options); +} + +LIBYANG_API_DEF LY_ERR +lys_print_path(const char *path, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options) +{ + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, path, module, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_filepath(path, &out)); + return lys_print_(out, module, format, options); +} + +LIBYANG_API_DEF LY_ERR +lys_print_clb(ly_write_clb writeclb, void *user_data, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options) +{ + struct ly_out *out; + + LY_CHECK_ARG_RET(NULL, writeclb, module, LY_EINVAL); + + LY_CHECK_RET(ly_out_new_clb(writeclb, user_data, &out)); + return lys_print_(out, module, format, options); +} + +LIBYANG_API_DEF LY_ERR +lys_print_node(struct ly_out *out, const struct lysc_node *node, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR ret; + + LY_CHECK_ARG_RET(NULL, out, node, LY_EINVAL); + + /* reset number of printed bytes */ + out->func_printed = 0; + + switch (format) { + case LYS_OUT_YANG_COMPILED: + ret = yang_print_compiled_node(out, node, options); + break; + /* TODO not yet implemented + case LYS_OUT_YIN: + ret = yin_print_parsed(out, module); + break; + */ + case LYS_OUT_TREE: + ret = tree_print_compiled_node(out, node, options, line_length); + break; + default: + LOGERR(NULL, LY_EINVAL, "Unsupported output format."); + ret = LY_EINVAL; + break; + } + + return ret; +} diff --git a/src/printer_schema.h b/src/printer_schema.h new file mode 100644 index 0000000..471d38e --- /dev/null +++ b/src/printer_schema.h @@ -0,0 +1,232 @@ +/** + * @file printer_schema.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Schema printers for libyang + * + * Copyright (c) 2015-2022 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_PRINTER_SCHEMA_H_ +#define LY_PRINTER_SCHEMA_H_ + +#include +#include + +#include "log.h" +#include "out.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_out; +struct lys_module; +struct lysc_node; +struct lysp_submodule; + +/** + * @page howtoSchemaPrinters Module Printers + * + * Schema printers allows to serialize internal representations of a schema module in a specific format. libyang + * supports the following schema formats for printing: + * + * - YANG + * + * Basic YANG schemas format described in [RFC 6020](http://tools.ietf.org/html/rfc6020) and + * [RFC 7951](http://tools.ietf.org/html/rfc7951) (so both YANG 1.0 and YANG 1.1 versions are supported). + * + * - YANG compiled + * + * Syntactically, this format is based on standard YANG format. In contrast to standard YANG format, YANG compiled format + * represents the module how it is used by libyang - with all uses expanded, augments and deviations applied, etc. + * (more details about the compiled modules can be found on @ref howtoContext page). + * + * - YIN + * + * Alternative XML-based format to YANG - YANG Independent Notation. The details can be found in + * [RFC 6020](http://tools.ietf.org/html/rfc6020#section-11) and + * [RFC 7951](http://tools.ietf.org/html/rfc7951#section-13). + * + * - Tree Diagram + * + * Simple tree diagram providing overview of the module. The details can be found in + * [RFC 8340](https://tools.ietf.org/html/rfc8340). + * + * For simpler transition from libyang 1.x (and for some simple use cases), there are functions (::lys_print_clb(), + * ::lys_print_fd(), ::lys_print_file() and ::lys_print_mem()) to print the complete module into the specified output. But note, + * that these functions are limited to print only the complete module. + * + * The full functionality of the schema printers is available via functions using [output handler](@ref howtoOutput). Besides + * the ::lys_print_module() function to print the complete module, there are functions to print a submodule + * (::lys_print_submodule()) or a subtree (::lys_print_node()). Note that these functions might not support all the output + * formats mentioned above. + * + * Functions List + * -------------- + * - ::lys_print_module() + * - ::lys_print_submodule() + * - ::lys_print_node() + * + * - ::lys_print_clb() + * - ::lys_print_fd() + * - ::lys_print_file() + * - ::lys_print_mem() + * - ::lys_print_path() + */ + +/** + * @addtogroup schematree + * @{ + */ + +/** + * @defgroup schemaprinterflags Schema output options + * + * Options to change default behavior of the schema printers. + * + * @{ + */ +#define LYS_PRINT_SHRINK LY_PRINT_SHRINK /**< Flag for output without indentation and formatting new lines. */ +#define LYS_PRINT_NO_SUBSTMT 0x10 /**< Print only top-level/referede node information, + do not print information from the substatements */ + +/** @} schemaprinterflags */ + +/** + * @brief Schema output formats accepted by libyang [printer functions](@ref howtoSchemaPrinters). + */ +typedef enum { + LYS_OUT_UNKNOWN = 0, /**< unknown format, used as return value in case of error */ + LYS_OUT_YANG = 1, /**< YANG schema output format */ + LYS_OUT_YANG_COMPILED = 2, /**< YANG schema output format of the compiled schema tree */ + LYS_OUT_YIN = 3, /**< YIN schema output format */ + LYS_OUT_TREE /**< Tree schema output format */ +} LYS_OUTFORMAT; + +/** + * @brief Schema module printer. + * + * @param[in] out Printer handler for a specific output. Use ly_out_*() functions to create and free the handler. + * @param[in] module Main module with the parsed schema to print. + * @param[in] format Output format. + * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_module(struct ly_out *out, const struct lys_module *module, LYS_OUTFORMAT format, + size_t line_length, uint32_t options); + +/** + * @brief Schema submodule printer. + * + * @param[in] out Printer handler for a specific output. Use ly_out_*() functions to create and free the handler. + * @param[in] submodule Parsed submodule to print. + * @param[in] format Output format (LYS_OUT_YANG_COMPILED is not supported). + * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_submodule(struct ly_out *out, const struct lysp_submodule *submodule, LYS_OUTFORMAT format, + size_t line_length, uint32_t options); + +/** + * @brief Print schema tree in the specified format into a memory block. + * It is up to caller to free the returned string by free(). + * + * This is just a wrapper around ::lys_print_module() for simple use cases. + * In case of a complex use cases, use lys_print with ly_out output handler. + * + * @param[out] strp Pointer to store the resulting dump. + * @param[in] module Schema tree to print. + * @param[in] format Schema output format. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_mem(char **strp, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options); + +/** + * @brief Print schema tree in the specified format into a file descriptor. + * + * This is just a wrapper around ::lys_print_module() for simple use cases. + * In case of a complex use cases, use lys_print with ly_out output handler. + * + * @param[in] fd File descriptor where to print the data. + * @param[in] module Schema tree to print. + * @param[in] format Schema output format. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_fd(int fd, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options); + +/** + * @brief Print schema tree in the specified format into a file stream. + * + * This is just a wrapper around ::lys_print_module() for simple use cases. + * In case of a complex use cases, use lys_print with ly_out output handler. + * + * @param[in] module Schema tree to print. + * @param[in] f File stream where to print the schema. + * @param[in] format Schema output format. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_file(FILE *f, const struct lys_module *module, LYS_OUTFORMAT format, uint32_t options); + +/** + * @brief Print schema tree in the specified format into a file. + * + * This is just a wrapper around ::lys_print_module() for simple use cases. + * In case of a complex use cases, use lys_print with ly_out output handler. + * + * @param[in] path File where to print the schema. + * @param[in] module Schema tree to print. + * @param[in] format Schema output format. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_path(const char *path, const struct lys_module *module, LYS_OUTFORMAT format, + uint32_t options); + +/** + * @brief Print schema tree in the specified format using a provided callback. + * + * This is just a wrapper around ::lys_print_module() for simple use cases. + * In case of a complex use cases, use lys_print with ly_out output handler. + * + * @param[in] module Schema tree to print. + * @param[in] writeclb Callback function to write the data (see write(1)). + * @param[in] user_data Optional caller-specific argument to be passed to the \p writeclb callback. + * @param[in] format Schema output format. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_clb(ly_write_clb writeclb, void *user_data, const struct lys_module *module, + LYS_OUTFORMAT format, uint32_t options); + +/** + * @brief Schema node printer. + * + * @param[in] out Printer handler for a specific output. Use ly_out_*() functions to create and free the handler. + * @param[in] node Schema node to print. + * @param[in] format Output format. + * @param[in] line_length Maximum characters to be printed on a line, 0 for unlimited. Only for #LYS_OUT_TREE printer. + * @param[in] options Schema output options (see @ref schemaprinterflags). + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lys_print_node(struct ly_out *out, const struct lysc_node *node, LYS_OUTFORMAT format, + size_t line_length, uint32_t options); + +/** @} schematree */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_PRINTER_SCHEMA_H_ */ diff --git a/src/printer_tree.c b/src/printer_tree.c new file mode 100644 index 0000000..6a7e7ce --- /dev/null +++ b/src/printer_tree.c @@ -0,0 +1,4673 @@ +/** + * @file printer_tree.c + * @author Adam Piecek + * @brief RFC tree printer for libyang data structure + * + * Copyright (c) 2015 - 2021 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 + * + * @section TRP_DESIGN Design + * + * @code + * +---------+ +---------+ +---------+ + * output | trp | | trb | | tro | + * <---+ Print +<---+ Browse +<-->+ Obtain | + * | | | | | | + * +---------+ +----+----+ +---------+ + * ^ + * | + * +----+----+ + * | trm | + * | Manager | + * | | + * +----+----+ + * ^ + * | input + * + + * @endcode + * + * @subsection TRP_GLOSSARY Glossary + * + * @subsubsection TRP_trm trm + * Manager functions are at the peak of abstraction. They are + * able to print individual sections of the YANG tree diagram + * (eg module, notifications, rpcs ...) and they call + * Browse functions (@ref TRP_trb). + * + * @subsubsection TRP_trb trb + * Browse functions contain a general algorithm (Preorder DFS) + * for traversing the tree. It does not matter what data type + * the tree contains (@ref lysc_node or @ref lysp_node), because it + * requires a ready-made getter functions for traversing the tree + * (@ref trt_fp_all) and transformation function to its own node + * data type (@ref trt_node). These getter functions are generally + * referred to as @ref TRP_tro. Browse functions can repeatedly + * traverse nodes in the tree, for example, to calculate the alignment + * gap before the nodes \ in the YANG Tree Diagram. + * The obtained @ref trt_node is passed to the @ref TRP_trp functions + * to print the Tree diagram. + * + * @subsubsection TRP_tro tro + * Functions that provide an extra wrapper for the libyang library. + * The Obtain functions are further specialized according to whether + * they operate on lysp_tree (@ref TRP_trop) or lysc_tree + * (@ref TRP_troc). If they are general algorithms, then they have the + * prefix \b tro_. The Obtain functions provide information to + * @ref TRP_trb functions for printing the Tree diagram. + * + * @subsubsection TRP_trop trop + * Functions for Obtaining information from Parsed schema tree. + * + * @subsubsection TRP_troc troc + * Functions for Obtaining information from Compiled schema tree. + * + * @subsubsection TRP_trp trp + * Print functions take care of the printing YANG diagram. They can + * also split one node into multiple lines if the node does not fit + * on one line. + * + * @subsubsection TRP_trt trt + * Data type marking in the printer_tree module. + * + * @subsubsection TRP_trg trg + * General functions. + * + * @subsection TRP_ADJUSTMENTS Adjustments + * It is assumed that the changes are likely to take place mainly for + * @ref TRP_tro, @ref TRP_trop or @ref TRP_troc functions because + * they are the only ones dependent on libyang implementation. + * In special cases, changes will also need to be made to the + * @ref TRP_trp functions if a special algorithm is needed to print + * (right now this is prepared for printing list's keys + * and if-features). + */ + +#include +#include + +#include "common.h" +#include "compat.h" +#include "out_internal.h" +#include "plugins_exts.h" +#include "plugins_types.h" +#include "printer_internal.h" +#include "printer_schema.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +/** + * @brief List of available actions. + */ +typedef enum { + TRD_PRINT = 0, /**< Normal behavior. It just prints. */ + TRD_CHAR_COUNT /**< Characters will be counted instead of printing. */ +} trt_ly_out_clb_arg_flag; + +/** + * @brief Structure is passed as 'writeclb' argument + * to the ::ly_out_new_clb(). + */ +struct ly_out_clb_arg { + trt_ly_out_clb_arg_flag mode; /**< flag specifying which action to take. */ + struct ly_out *out; /**< The ly_out pointer delivered to the printer tree module via the main interface. */ + size_t counter; /**< Counter of printed characters. */ + LY_ERR last_error; /**< The last error that occurred. If no error has occurred, it will be ::LY_SUCCESS. */ +}; + +/** + * @brief Initialize struct ly_out_clb_arg with default settings. + */ +#define TRP_INIT_LY_OUT_CLB_ARG(MODE, OUT, COUNTER, LAST_ERROR) \ + (struct ly_out_clb_arg) { \ + .mode = MODE, .out = OUT, \ + .counter = COUNTER, .last_error = LAST_ERROR \ + } + +/********************************************************************** + * Print getters + *********************************************************************/ + +/** + * @brief Callback functions that prints special cases. + * + * It just groups together tree context with trt_fp_print. + */ +struct trt_cf_print { + const struct trt_tree_ctx *ctx; /**< Context of libyang tree. */ + + void (*pf)(const struct trt_tree_ctx *, struct ly_out *); /**< Pointing to function which printing list's keys or features. */ +}; + +/** + * @brief Callback functions for printing special cases. + * + * Functions with the suffix 'trp' can print most of the text on + * output, just by setting the pointer to the string. But in some + * cases, it's not that simple, because its entire string is fragmented + * in memory. For example, for printing list's keys or if-features. + * However, this depends on how the libyang library is implemented. + * This implementation of the printer_tree module goes through + * a lysp tree, but if it goes through a lysc tree, these special cases + * would be different. + * Functions must print including spaces or delimiters between names. + */ +struct trt_fp_print { + void (*print_features_names)(const struct trt_tree_ctx *, struct ly_out *); /**< Print list of features without {}? wrapper. */ + void (*print_keys)(const struct trt_tree_ctx *, struct ly_out *); /**< Print list's keys without [] wrapper. */ +}; + +/** + * @brief Package which only groups getter function. + */ +struct trt_pck_print { + const struct trt_tree_ctx *tree_ctx; /**< Context of libyang tree. */ + struct trt_fp_print fps; /**< Print function. */ +}; + +/** + * @brief Initialize struct trt_pck_print by parameters. + */ +#define TRP_INIT_PCK_PRINT(TREE_CTX, FP_PRINT) \ + (struct trt_pck_print) {.tree_ctx = TREE_CTX, .fps = FP_PRINT} + +/********************************************************************** + * Indent + *********************************************************************/ + +/** + * @brief Constants which are defined in the RFC or are observable + * from the pyang tool. + */ +typedef enum { + TRD_INDENT_EMPTY = 0, /**< If the node is a case node, there is no space before the \. */ + TRD_INDENT_LONG_LINE_BREAK = 2, /**< The new line should be indented so that it starts below \ with + a whitespace offset of at least two characters. */ + TRD_INDENT_LINE_BEGIN = 2, /**< Indent below the keyword (module, augment ...). */ + TRD_INDENT_BTW_SIBLINGS = 2, /**< Indent between | and | characters. */ + TRD_INDENT_BEFORE_KEYS = 1, /**< "..."___\. */ + TRD_INDENT_BEFORE_TYPE = 4, /**< "..."___\, but if mark is set then indent == 3. */ + TRD_INDENT_BEFORE_IFFEATURES = 1 /**< "..."___\. */ +} trt_cnf_indent; + +/** + * @brief Type of indent in node. + */ +typedef enum { + TRD_INDENT_IN_NODE_NORMAL = 0, /**< Node fits on one line. */ + TRD_INDENT_IN_NODE_DIVIDED, /**< The node must be split into multiple rows. */ + TRD_INDENT_IN_NODE_FAILED /**< Cannot be crammed into one line. The condition for the maximum line length is violated. */ +} trt_indent_in_node_type; + +/** Constant to indicate the need to break a line. */ +#define TRD_LINEBREAK -1 + +/** + * @brief Records the alignment between the individual + * elements of the node. + * + * @see trp_default_indent_in_node, trp_try_normal_indent_in_node + */ +struct trt_indent_in_node { + trt_indent_in_node_type type; /**< Type of indent in node. */ + int16_t btw_name_opts; /**< Indent between node name and \. */ + int16_t btw_opts_type; /**< Indent between \ and \. */ + int16_t btw_type_iffeatures; /**< Indent between type and features. Ignored if \ missing. */ +}; + +/** + * @brief Type of wrappers to be printed. + */ +typedef enum { + TRD_WRAPPER_TOP = 0, /**< Related to the module. */ + TRD_WRAPPER_BODY /**< Related to e.g. Augmentations or Groupings */ +} trd_wrapper_type; + +/** + * @brief For resolving sibling symbol ('|') placement. + * + * Bit indicates where the sibling symbol must be printed. + * This place is in multiples of ::TRD_INDENT_BTW_SIBLINGS. + * + * @see TRP_INIT_WRAPPER_TOP, TRP_INIT_WRAPPER_BODY, + * trp_wrapper_set_mark, trp_wrapper_set_shift, + * trp_wrapper_if_last_sibling, trp_wrapper_eq, trp_print_wrapper + */ +struct trt_wrapper { + trd_wrapper_type type; /**< Location of the wrapper. */ + uint64_t bit_marks1; /**< The set bits indicate where the '|' character is to be printed. + It follows that the maximum immersion of the printable node is 64. */ + uint32_t actual_pos; /**< Actual position in bit_marks. */ +}; + +/** + * @brief Get wrapper related to the module section. + * + * @code + * module: + * +-- + * | + * @endcode + */ +#define TRP_INIT_WRAPPER_TOP \ + (struct trt_wrapper) { \ + .type = TRD_WRAPPER_TOP, .actual_pos = 0, .bit_marks1 = 0 \ + } + +/** + * @brief Get wrapper related to subsection + * e.g. Augmenations or Groupings. + * + * @code + * module: + * +-- + * + * augment : + * +-- + * @endcode + */ +#define TRP_INIT_WRAPPER_BODY \ + (struct trt_wrapper) { \ + .type = TRD_WRAPPER_BODY, .actual_pos = 0, .bit_marks1 = 0 \ + } + +/** + * @brief Package which only groups wrapper and indent in node. + */ +struct trt_pck_indent { + struct trt_wrapper wrapper; /**< Coded " | | " sequence. */ + struct trt_indent_in_node in_node; /**< Indent in node. */ +}; + +/** + * @brief Initialize struct trt_pck_indent by parameters. + */ +#define TRP_INIT_PCK_INDENT(WRAPPER, INDENT_IN_NODE) \ + (struct trt_pck_indent){ \ + .wrapper = WRAPPER, .in_node = INDENT_IN_NODE \ + } + +/********************************************************************** + * flags + *********************************************************************/ + +#define TRD_FLAGS_TYPE_EMPTY "--" +#define TRD_FLAGS_TYPE_RW "rw" +#define TRD_FLAGS_TYPE_RO "ro" +#define TRD_FLAGS_TYPE_RPC_INPUT_PARAMS "-w" +#define TRD_FLAGS_TYPE_USES_OF_GROUPING "-u" +#define TRD_FLAGS_TYPE_RPC "-x" +#define TRD_FLAGS_TYPE_NOTIF "-n" +#define TRD_FLAGS_TYPE_MOUNT_POINT "mp" + +/********************************************************************** + * node_name and opts + *********************************************************************/ + +#define TRD_NODE_NAME_PREFIX_CHOICE "(" +#define TRD_NODE_NAME_PREFIX_CASE ":(" +#define TRD_NODE_NAME_TRIPLE_DOT "..." + +/** + * @brief Type of the node. + * + * Used mainly to complete the correct \ next to or + * around the \. + */ +typedef enum { + TRD_NODE_ELSE = 0, /**< For some node which does not require special treatment. \ */ + TRD_NODE_CASE, /**< For case node. :(\) */ + TRD_NODE_CHOICE, /**< For choice node. (\) */ + TRD_NODE_TRIPLE_DOT /**< For collapsed sibling nodes and their children. Special case which doesn't belong here very well. */ +} trt_node_type; + +#define TRD_NODE_OPTIONAL "?" /**< For an optional leaf, anydata, or anyxml. \? */ +#define TRD_NODE_CONTAINER "!" /**< For a presence container. \! */ +#define TRD_NODE_LISTLEAFLIST "*" /**< For a leaf-list or list. \* */ + +/** + * @brief Type of node and his name. + * + * @see TRP_EMPTY_NODE_NAME, TRP_NODE_NAME_IS_EMPTY, + * trp_print_node_name, trp_mark_is_used, trp_print_opts_keys + */ +struct trt_node_name { + trt_node_type type; /**< Type of the node relevant for printing. */ + ly_bool keys; /**< Set to 1 if [\] are to be printed. Valid for some types only. */ + const char *module_prefix; /**< If the node is augmented into the tree from another module, + so this is the prefix of that module. */ + const char *str; /**< Name of the node. */ + const char *add_opts; /**< Additional opts symbol from plugin. */ + const char *opts; /**< The \ symbol. */ +}; + +/** + * @brief Create struct trt_node_name as empty. + */ +#define TRP_EMPTY_NODE_NAME \ + (struct trt_node_name) { \ + .type = TRD_NODE_ELSE, .keys = 0, .module_prefix = NULL, .str = NULL, .opts = NULL, .add_opts = NULL \ + } + +/** + * @brief Check if struct trt_node_name is empty. + */ +#define TRP_NODE_NAME_IS_EMPTY(NODE_NAME) \ + !NODE_NAME.str + +/********************************************************************** + * type + *********************************************************************/ + +/** + * @brief Type of the \ + */ +typedef enum { + TRD_TYPE_NAME = 0, /**< Type is just a name that does not require special treatment. */ + TRD_TYPE_TARGET, /**< Should have a form "-> TARGET", where TARGET is the leafref path. */ + TRD_TYPE_LEAFREF, /**< This type is set automatically by the 'trp' algorithm. + So set type as ::TRD_TYPE_TARGET. */ + TRD_TYPE_EMPTY /**< Type is not used at all. */ +} trt_type_type; + +/** + * @brief \ in the \. + * + * @see TRP_EMPTY_TRT_TYPE, TRP_TRT_TYPE_IS_EMPTY, trp_print_type + */ +struct trt_type { + trt_type_type type; /**< Type of the \. */ + const char *str; /**< Path or name of the type. */ +}; + +/** + * @brief Create empty struct trt_type. + */ +#define TRP_EMPTY_TRT_TYPE \ + (struct trt_type) {.type = TRD_TYPE_EMPTY, .str = NULL} + +/** + * @brief Check if struct trt_type is empty. + */ +#define TRP_TRT_TYPE_IS_EMPTY(TYPE_OF_TYPE) \ + TYPE_OF_TYPE.type == TRD_TYPE_EMPTY + +/** + * @brief Initialize struct trt_type by parameters. + */ +#define TRP_INIT_TRT_TYPE(TYPE_OF_TYPE, STRING) \ + (struct trt_type) {.type = TYPE_OF_TYPE, .str = STRING} + +/** + * @brief If-feature type. + */ +typedef enum { + TRD_IFF_NON_PRESENT = 0, /**< iffeatures are not present. */ + TRD_IFF_PRESENT, /**< iffeatures are present and will be printed by + trt_fp_print.print_features_names callback */ + TRD_IFF_OVERR /**< iffeatures are override by plugin */ +} trt_iffeatures_type; + +/** + * @brief \. + */ +struct trt_iffeatures { + trt_iffeatures_type type; /**< Type of iffeature. */ + char *str; /**< iffeatures string ready to print. Set if TRD_IFF_OVERR is set. */ +}; + +/** + * @brief Create empty iffeatures. + */ +#define TRP_EMPTY_TRT_IFFEATURES \ + (struct trt_iffeatures) {.type = TRD_IFF_NON_PRESENT} + +/** + * @brief Check if iffeatures is empty. + * + * @param[in] IFF_TYPE value from trt_iffeatures.type. + * @return 1 if is empty. + */ +#define TRP_EMPTY_TRT_IFFEATURES_IS_EMPTY(IFF_TYPE) \ + (IFF_TYPE == TRD_IFF_NON_PRESENT) + +/********************************************************************** + * node + *********************************************************************/ + +/** + * @brief \ data for printing. + * + * It contains RFC's: + * \--\ \\ \ \. + * Item \ is moved to part struct trt_node_name. + * For printing [\] and if-features is required special + * functions which prints them. + * + * @see TRP_EMPTY_NODE, trp_node_is_empty, trp_node_body_is_empty, + * trp_print_node_up_to_name, trp_print_divided_node_up_to_name, + * trp_print_node + */ +struct trt_node { + const char *status; /**< \. */ + const char *flags; /**< \. */ + struct trt_node_name name; /**< \ with \ mark or [\]. */ + struct trt_type type; /**< \ contains the name of the type or type for leafref. */ + struct trt_iffeatures iffeatures; /**< \. */ + ly_bool last_one; /**< Information about whether the node is the last. */ +}; + +/** + * @brief Create struct trt_node as empty. + */ +#define TRP_EMPTY_NODE \ + (struct trt_node) { \ + .status = NULL, \ + .flags = NULL, \ + .name = TRP_EMPTY_NODE_NAME, \ + .type = TRP_EMPTY_TRT_TYPE, \ + .iffeatures = TRP_EMPTY_TRT_IFFEATURES, \ + .last_one = 1 \ + } + +/** + * @brief Package which only groups indent and node. + */ +struct trt_pair_indent_node { + struct trt_indent_in_node indent; + struct trt_node node; +}; + +/** + * @brief Initialize struct trt_pair_indent_node by parameters. + */ +#define TRP_INIT_PAIR_INDENT_NODE(INDENT_IN_NODE, NODE) \ + (struct trt_pair_indent_node) { \ + .indent = INDENT_IN_NODE, .node = NODE \ + } + +/********************************************************************** + * statement + *********************************************************************/ + +#define TRD_KEYWORD_MODULE "module" +#define TRD_KEYWORD_SUBMODULE "submodule" +#define TRD_KEYWORD_AUGMENT "augment" +#define TRD_KEYWORD_RPC "rpcs" +#define TRD_KEYWORD_NOTIF "notifications" +#define TRD_KEYWORD_GROUPING "grouping" + +/** + * @brief Main sign of the tree nodes. + * + * @see TRP_EMPTY_KEYWORD_STMT, TRP_KEYWORD_STMT_IS_EMPTY + * trt_print_keyword_stmt_begin, trt_print_keyword_stmt_str, + * trt_print_keyword_stmt_end, trp_print_keyword_stmt + */ +struct trt_keyword_stmt { + const char *section_name; /**< String containing section name. */ + const char *argument; /**< Name or path located begind section name. */ + ly_bool has_node; /**< Flag if section has any nodes. */ +}; + +/** + * @brief Create struct trt_keyword_stmt as empty. + */ +#define TRP_EMPTY_KEYWORD_STMT \ + (struct trt_keyword_stmt) {.section_name = NULL, .argument = NULL, .has_node = 0} + +/********************************************************************** + * Modify getters + *********************************************************************/ + +struct trt_parent_cache; + +/** + * @brief Functions that change the state of the tree_ctx structure. + * + * The 'trop' or 'troc' functions are set here, which provide data + * for the 'trp' printing functions and are also called from the + * 'trb' browsing functions when walking through a tree. These callback + * functions need to be checked or reformulated if changes to the + * libyang library affect the printing tree. For all, if the value + * cannot be returned, its empty version obtained by relevant TRP_EMPTY + * macro is returned. + */ +struct trt_fp_modify_ctx { + ly_bool (*parent)(struct trt_tree_ctx *); /**< Jump to parent node. Return true if parent exists. */ + struct trt_node (*first_sibling)(struct trt_parent_cache, struct trt_tree_ctx *); /**< Jump on the first of the siblings. */ + struct trt_node (*next_sibling)(struct trt_parent_cache, struct trt_tree_ctx *); /**< Jump to next sibling of the current node. */ + struct trt_node (*next_child)(struct trt_parent_cache, struct trt_tree_ctx *); /**< Jump to the child of the current node. */ +}; + +/** + * @brief Create modify functions for compiled tree. + */ +#define TRP_TRT_FP_MODIFY_COMPILED \ + (struct trt_fp_modify_ctx) { \ + .parent = troc_modi_parent, \ + .first_sibling = troc_modi_first_sibling, \ + .next_sibling = troc_modi_next_sibling, \ + .next_child = troc_modi_next_child, \ + } + +/** + * @brief Create modify functions for parsed tree. + */ +#define TRP_TRT_FP_MODIFY_PARSED \ + (struct trt_fp_modify_ctx) { \ + .parent = trop_modi_parent, \ + .first_sibling = trop_modi_first_sibling, \ + .next_sibling = trop_modi_next_sibling, \ + .next_child = trop_modi_next_child, \ + } + +/********************************************************************** + * Read getters + *********************************************************************/ + +/** + * @brief Functions that do not change the state of the tree_structure. + * + * For details see trt_fp_modify_ctx. + */ +struct trt_fp_read { + struct trt_keyword_stmt (*module_name)(const struct trt_tree_ctx *); /**< Get name of the module. */ + struct trt_node (*node)(struct trt_parent_cache, struct trt_tree_ctx *); /**< Get current node. */ + ly_bool (*if_sibling_exists)(const struct trt_tree_ctx *); /**< Check if node's sibling exists. */ + ly_bool (*if_parent_exists)(const struct trt_tree_ctx *); /**< Check if node's parent exists. */ +}; + +/** + * @brief Create read functions for compiled tree. + */ +#define TRP_TRT_FP_READ_COMPILED \ + (struct trt_fp_read) { \ + .module_name = tro_read_module_name, \ + .node = troc_read_node, \ + .if_sibling_exists = troc_read_if_sibling_exists, \ + .if_parent_exists = tro_read_if_sibling_exists \ + } + +/** + * @brief Create read functions for parsed tree. + */ +#define TRP_TRT_FP_READ_PARSED \ + (struct trt_fp_read) { \ + .module_name = tro_read_module_name, \ + .node = trop_read_node, \ + .if_sibling_exists = trop_read_if_sibling_exists, \ + .if_parent_exists = tro_read_if_sibling_exists \ + } + +/********************************************************************** + * All getters + *********************************************************************/ + +/** + * @brief A set of all necessary functions that must be provided + * for the printer. + */ +struct trt_fp_all { + struct trt_fp_modify_ctx modify; /**< Function pointers which modify state of trt_tree_ctx. */ + struct trt_fp_read read; /**< Function pointers which only reads state of trt_tree_ctx. */ + struct trt_fp_print print; /**< Functions pointers for printing special items in node. */ +}; + +/********************************************************************** + * Printer context + *********************************************************************/ + +/** + * @brief Main structure for @ref TRP_trp part. + */ +struct trt_printer_ctx { + struct ly_out *out; /**< Handler to printing. */ + struct trt_fp_all fp; /**< @ref TRP_tro functions callbacks. */ + size_t max_line_length; /**< The maximum number of characters that can be + printed on one line, including the last. */ +}; + +/********************************************************************** + * Tro functions + *********************************************************************/ + +/** + * @brief The name of the section to which the node belongs. + */ +typedef enum { + TRD_SECT_MODULE = 0, /**< The node belongs to the "module: :" label. */ + TRD_SECT_AUGMENT, /**< The node belongs to some "augment :" label. */ + TRD_SECT_RPCS, /**< The node belongs to the "rpcs:" label. */ + TRD_SECT_NOTIF, /**< The node belongs to the "notifications:" label. */ + TRD_SECT_GROUPING, /**< The node belongs to some "grouping :" label. */ + TRD_SECT_PLUG_DATA /**< The node belongs to some plugin section. */ +} trt_actual_section; + +/** + * @brief Types of nodes that have some effect on their children. + */ +typedef enum { + TRD_ANCESTOR_ELSE = 0, /**< Everything not listed. */ + TRD_ANCESTOR_RPC_INPUT, /**< ::LYS_INPUT */ + TRD_ANCESTOR_RPC_OUTPUT, /**< ::LYS_OUTPUT */ + TRD_ANCESTOR_NOTIF /**< ::LYS_NOTIF */ +} trt_ancestor_type; + +/** + * @brief Saved information when browsing the tree downwards. + * + * This structure helps prevent frequent retrieval of information + * from the tree. Functions @ref TRP_trb are designed to preserve + * this structures during their recursive calls. This functions do not + * interfere in any way with this data. This structure + * is used by @ref TRP_trop functions which, thanks to this + * structure, can return a node with the correct data. The word + * \b parent is in the structure name, because this data refers to + * the last parent and at the same time the states of its + * ancestors data. Only the function jumping on the child + * (next_child(...)) creates this structure, because the pointer + * to the current node moves down the tree. It's like passing + * the genetic code to children. Some data must be inherited and + * there are two approaches to this problem. Either it will always + * be determined which inheritance states belong to the current node + * (which can lead to regular travel to the root node) or + * the inheritance states will be stored during the recursive calls. + * So the problem was solved by the second option. Why does + * the structure contain this data? Because it walks through + * the lysp tree. For walks through the lysc tree is trt_parent_cache + * useless. + * + * @see TRO_EMPTY_PARENT_CACHE, tro_parent_cache_for_child + */ +struct trt_parent_cache { + trt_ancestor_type ancestor; /**< Some types of nodes have a special effect on their children. */ + uint16_t lys_status; /**< Inherited status CURR, DEPRC, OBSLT. */ + uint16_t lys_config; /**< Inherited config W or R. */ + const struct lysp_node_list *last_list; /**< The last ::LYS_LIST passed. */ +}; + +/** + * @brief Return trt_parent_cache filled with default values. + */ +#define TRP_EMPTY_PARENT_CACHE \ + (struct trt_parent_cache) { \ + .ancestor = TRD_ANCESTOR_ELSE, .lys_status = LYS_STATUS_CURR, \ + .lys_config = LYS_CONFIG_W, .last_list = NULL \ + } + +/** + * @brief Node override from plugin. + */ +struct lyplg_ext_sprinter_tree_node_override { + const char *flags; /**< Override for \. */ + const char *add_opts; /**< Additional symbols for \. */ +}; + +/** + * @brief Context for plugin extension. + */ +struct trt_plugin_ctx { + struct lyspr_tree_ctx *ctx; /**< Pointer to main context. */ + struct lyspr_tree_schema *schema; /**< Current schema to print. */ + ly_bool filtered; /**< Flag if current node is filtered. */ + struct lyplg_ext_sprinter_tree_node_override node_overr; /**< Current node override. */ + ly_bool last_schema; /**< Flag if schema is last. */ + ly_bool last_error; /**< Last error from plugin. */ +}; + +/** + * @brief Main structure for browsing the libyang tree + */ +struct trt_tree_ctx { + ly_bool lysc_tree; /**< The lysc nodes are used for browsing through the tree. + It is assumed that once set, it does not change. + If it is true then trt_tree_ctx.pn and + trt_tree_ctx.tpn are not used. + If it is false then trt_tree_ctx.cn is not used. */ + trt_actual_section section; /**< To which section pn points. */ + const struct lysp_module *pmod; /**< Parsed YANG schema tree. */ + const struct lysc_module *cmod; /**< Compiled YANG schema tree. */ + const struct lysp_node *pn; /**< Actual pointer to parsed node. */ + const struct lysp_node *tpn; /**< Pointer to actual top-node. */ + const struct lysc_node *cn; /**< Actual pointer to compiled node. */ + LY_ERR last_error; /**< Error value during printing. */ + + struct trt_plugin_ctx plugin_ctx; /**< Context for plugin. */ +}; + +/** + * @brief Create empty node override. + */ +#define TRP_TREE_CTX_EMPTY_NODE_OVERR \ + (struct lyplg_ext_sprinter_tree_node_override) { \ + .flags = NULL, \ + .add_opts = NULL, \ + } + +/** + * @brief Check if lysp node is available from + * the current compiled node. + * + * Use only if trt_tree_ctx.lysc_tree is set to true. + */ +#define TRP_TREE_CTX_LYSP_NODE_PRESENT(CN) \ + (CN->priv) + +/** + * @brief Get lysp_node from trt_tree_ctx.cn. + * + * Use only if :TRP_TREE_CTX_LYSP_NODE_PRESENT returns true + * for that node. + */ +#define TRP_TREE_CTX_GET_LYSP_NODE(CN) \ + ((const struct lysp_node *)CN->priv) + +/** Getter function for ::trop_node_charptr(). */ +typedef const char *(*trt_get_charptr_func)(const struct lysp_node *pn); + +/** + * @brief Simple getter functions for lysp and lysc nodes. + * + * This structure is useful if we have a general algorithm + * (tro function) that can be used for both lysc and lysp nodes. + * Thanks to this structure, we prevent code redundancy. + * We don't have to write basically the same algorithm twice + * for lysp and lysc trees. + */ +struct tro_getters { + uint16_t (*nodetype)(const void *); /**< Get nodetype. */ + const void *(*next)(const void *); /**< Get sibling. */ + const void *(*parent)(const void *); /**< Get parent. */ + const void *(*child)(const void *); /**< Get child. */ + const void *(*actions)(const void *); /**< Get actions. */ + const void *(*action_input)(const void *); /**< Get input action from action node. */ + const void *(*action_output)(const void *); /**< Get output action from action node. */ + const void *(*notifs)(const void *); /**< Get notifs. */ +}; + +/********************************************************************** + * Definition of the general Trg functions + *********************************************************************/ + +/** + * @brief Print a substring but limited to the maximum length. + * @param[in] str is pointer to source. + * @param[in] len is number of characters to be printed. + * @param[in,out] out is output handler. + * @return str parameter shifted by len. + */ +static const char * +trg_print_substr(const char *str, size_t len, struct ly_out *out) +{ + for (size_t i = 0; i < len; i++) { + ly_print_(out, "%c", str[0]); + str++; + } + return str; +} + +/** + * @brief Pointer is not NULL and does not point to an empty string. + * @param[in] str is pointer to string to be checked. + * @return 1 if str pointing to non empty string otherwise 0. + */ +static ly_bool +trg_charptr_has_data(const char *str) +{ + return (str) && (str[0] != '\0'); +} + +/** + * @brief Check if @p word in @p src is present where words are + * delimited by @p delim. + * @param[in] src is source where words are separated by @p delim. + * @param[in] word to be searched. + * @param[in] delim is delimiter between @p words in @p src. + * @return 1 if src contains @p word otherwise 0. + */ +static ly_bool +trg_word_is_present(const char *src, const char *word, char delim) +{ + const char *hit; + + if ((!src) || (src[0] == '\0') || (!word)) { + return 0; + } + + hit = strstr(src, word); + + if (hit) { + /* word was founded at the begin of src + * OR it match somewhere after delim + */ + if ((hit == src) || (hit[-1] == delim)) { + /* end of word was founded at the end of src + * OR end of word was match somewhere before delim + */ + char delim_or_end = (hit + strlen(word))[0]; + + if ((delim_or_end == '\0') || (delim_or_end == delim)) { + return 1; + } + } + /* after -> hit is just substr and it's not the whole word */ + /* jump to the next word */ + for ( ; (src[0] != '\0') && (src[0] != delim); src++) {} + /* skip delim */ + src = src[0] == '\0' ? src : src + 1; + /* continue with searching */ + return trg_word_is_present(src, word, delim); + } else { + return 0; + } +} + +/********************************************************************** + * Definition of printer functions + *********************************************************************/ + +/** + * @brief Write callback for ::ly_out_new_clb(). + * + * @param[in] user_data is type of struct ly_out_clb_arg. + * @param[in] buf contains input characters + * @param[in] count is number of characters in buf. + * @return Number of printed bytes. + * @return Negative value in case of error. + */ +static ssize_t +trp_ly_out_clb_func(void *user_data, const void *buf, size_t count) +{ + LY_ERR erc = LY_SUCCESS; + struct ly_out_clb_arg *data = (struct ly_out_clb_arg *)user_data; + + switch (data->mode) { + case TRD_PRINT: + erc = ly_write_(data->out, buf, count); + break; + case TRD_CHAR_COUNT: + data->counter = data->counter + count; + break; + default: + break; + } + + if (erc != LY_SUCCESS) { + data->last_error = erc; + return -1; + } else { + return count; + } +} + +/** + * @brief Check that indent in node can be considered as equivalent. + * @param[in] first is the first indent in node. + * @param[in] second is the second indent in node. + * @return 1 if indents are equivalent otherwise 0. + */ +static ly_bool +trp_indent_in_node_are_eq(struct trt_indent_in_node first, struct trt_indent_in_node second) +{ + const ly_bool a = first.type == second.type; + const ly_bool b = first.btw_name_opts == second.btw_name_opts; + const ly_bool c = first.btw_opts_type == second.btw_opts_type; + const ly_bool d = first.btw_type_iffeatures == second.btw_type_iffeatures; + + return a && b && c && d; +} + +/** + * @brief Setting space character because node is last sibling. + * @param[in] wr is wrapper over which the shift operation + * is to be performed. + * @return New shifted wrapper. + */ +static struct trt_wrapper +trp_wrapper_set_shift(struct trt_wrapper wr) +{ + assert(wr.actual_pos < 64); + /* +-- + * +-- + */ + wr.actual_pos++; + return wr; +} + +/** + * @brief Setting '|' symbol because node is divided or + * it is not last sibling. + * @param[in] wr is source of wrapper. + * @return New wrapper which is marked at actual position and shifted. + */ +static struct trt_wrapper +trp_wrapper_set_mark(struct trt_wrapper wr) +{ + assert(wr.actual_pos < 64); + wr.bit_marks1 |= 1U << wr.actual_pos; + return trp_wrapper_set_shift(wr); +} + +/** + * @brief Setting ' ' symbol if node is last sibling otherwise set '|'. + * @param[in] wr is actual wrapper. + * @param[in] last_one is flag. Value 1 saying if the node is the last + * and has no more siblings. + * @return New wrapper for the actual node. + */ +static struct trt_wrapper +trp_wrapper_if_last_sibling(struct trt_wrapper wr, ly_bool last_one) +{ + return last_one ? trp_wrapper_set_shift(wr) : trp_wrapper_set_mark(wr); +} + +/** + * @brief Test if the wrappers are equivalent. + * @param[in] first is the first wrapper. + * @param[in] second is the second wrapper. + * @return 1 if the wrappers are equivalent otherwise 0. + */ +static ly_bool +trp_wrapper_eq(struct trt_wrapper first, struct trt_wrapper second) +{ + const ly_bool a = first.type == second.type; + const ly_bool b = first.bit_marks1 == second.bit_marks1; + const ly_bool c = first.actual_pos == second.actual_pos; + + return a && b && c; +} + +/** + * @brief Print " | " sequence on line. + * @param[in] wr is wrapper to be printed. + * @param[in,out] out is output handler. + */ +static void +trp_print_wrapper(struct trt_wrapper wr, struct ly_out *out) +{ + uint32_t lb; + + if (wr.type == TRD_WRAPPER_TOP) { + lb = TRD_INDENT_LINE_BEGIN; + } else if (wr.type == TRD_WRAPPER_BODY) { + lb = TRD_INDENT_LINE_BEGIN * 2; + } else { + lb = TRD_INDENT_LINE_BEGIN; + } + + ly_print_(out, "%*c", lb, ' '); + + if (trp_wrapper_eq(wr, TRP_INIT_WRAPPER_TOP)) { + return; + } + + for (uint32_t i = 0; i < wr.actual_pos; i++) { + /** Test if the bit on the index is set. */ + if ((wr.bit_marks1 >> i) & 1U) { + ly_print_(out, "|"); + } else { + ly_print_(out, " "); + } + + if (i != wr.actual_pos) { + ly_print_(out, "%*c", TRD_INDENT_BTW_SIBLINGS, ' '); + } + } +} + +/** + * @brief Check if struct trt_node is empty. + * @param[in] node is item to test. + * @return 1 if node is considered empty otherwise 0. + */ +static ly_bool +trp_node_is_empty(const struct trt_node *node) +{ + const ly_bool a = TRP_EMPTY_TRT_IFFEATURES_IS_EMPTY(node->iffeatures.type); + const ly_bool b = TRP_TRT_TYPE_IS_EMPTY(node->type); + const ly_bool c = TRP_NODE_NAME_IS_EMPTY(node->name); + const ly_bool d = node->flags == NULL; + const ly_bool e = node->status == NULL; + + return a && b && c && d && e; +} + +/** + * @brief Check if [\], \ and + * \ are empty/not_set. + * @param[in] node is item to test. + * @return 1 if node has no \ \ or \ + * otherwise 0. + */ +static ly_bool +trp_node_body_is_empty(const struct trt_node *node) +{ + const ly_bool a = TRP_EMPTY_TRT_IFFEATURES_IS_EMPTY(node->iffeatures.type); + const ly_bool b = TRP_TRT_TYPE_IS_EMPTY(node->type); + const ly_bool c = !node->name.keys; + + return a && b && c; +} + +/** + * @brief Print entire struct trt_node_name structure. + * @param[in] node_name is item to print. + * @param[in,out] out is output handler. + */ +static void +trp_print_node_name(struct trt_node_name node_name, struct ly_out *out) +{ + const char *mod_prefix; + const char *colon; + const char trd_node_name_suffix_choice[] = ")"; + const char trd_node_name_suffix_case[] = ")"; + + if (TRP_NODE_NAME_IS_EMPTY(node_name)) { + return; + } + + if (node_name.module_prefix) { + mod_prefix = node_name.module_prefix; + colon = ":"; + } else { + mod_prefix = ""; + colon = ""; + } + + switch (node_name.type) { + case TRD_NODE_ELSE: + ly_print_(out, "%s%s%s", mod_prefix, colon, node_name.str); + break; + case TRD_NODE_CASE: + ly_print_(out, "%s%s%s%s%s", TRD_NODE_NAME_PREFIX_CASE, mod_prefix, colon, node_name.str, trd_node_name_suffix_case); + break; + case TRD_NODE_CHOICE: + ly_print_(out, "%s%s%s%s%s", TRD_NODE_NAME_PREFIX_CHOICE, mod_prefix, colon, node_name.str, trd_node_name_suffix_choice); + break; + case TRD_NODE_TRIPLE_DOT: + ly_print_(out, "%s", TRD_NODE_NAME_TRIPLE_DOT); + break; + default: + break; + } + + if (node_name.add_opts) { + ly_print_(out, "%s", node_name.add_opts); + } + if (node_name.opts) { + ly_print_(out, "%s", node_name.opts); + } +} + +/** + * @brief Check if mark (?, !, *, /, @) is implicitly contained in + * struct trt_node_name. + * @param[in] node_name is structure containing the 'mark'. + * @return 1 if contain otherwise 0. + */ +static ly_bool +trp_mark_is_used(struct trt_node_name node_name) +{ + if (TRP_NODE_NAME_IS_EMPTY(node_name)) { + return 0; + } else if (node_name.keys) { + return 0; + } + + switch (node_name.type) { + case TRD_NODE_ELSE: + case TRD_NODE_CASE: + return 0; + default: + if (node_name.add_opts || node_name.opts) { + return 1; + } else { + return 0; + } + } +} + +/** + * @brief Print opts keys. + * @param[in] node_name contains type of the node with his name. + * @param[in] btw_name_opts is number of spaces between name and [keys]. + * @param[in] cf is basically a pointer to the function that prints + * the keys. + * @param[in,out] out is output handler. + */ +static void +trp_print_opts_keys(struct trt_node_name node_name, int16_t btw_name_opts, struct trt_cf_print cf, struct ly_out *out) +{ + if (!node_name.keys) { + return; + } + + /* ___*/ + if (btw_name_opts > 0) { + ly_print_(out, "%*c", btw_name_opts, ' '); + } + ly_print_(out, "["); + cf.pf(cf.ctx, out); + ly_print_(out, "]"); +} + +/** + * @brief Print entire struct trt_type structure. + * @param[in] type is item to print. + * @param[in,out] out is output handler. + */ +static void +trp_print_type(struct trt_type type, struct ly_out *out) +{ + if (TRP_TRT_TYPE_IS_EMPTY(type)) { + return; + } + + switch (type.type) { + case TRD_TYPE_NAME: + ly_print_(out, "%s", type.str); + break; + case TRD_TYPE_TARGET: + ly_print_(out, "-> %s", type.str); + break; + case TRD_TYPE_LEAFREF: + ly_print_(out, "leafref"); + default: + break; + } +} + +/** + * @brief Print all iffeatures of node + * + * @param[in] iff is iffeatures to print. + * @param[in] cf is basically a pointer to the function that prints the list of features. + * @param[in,out] out is output handler. + */ +static void +trp_print_iffeatures(struct trt_iffeatures iff, struct trt_cf_print cf, struct ly_out *out) +{ + if (iff.type == TRD_IFF_PRESENT) { + ly_print_(out, "{"); + cf.pf(cf.ctx, out); + ly_print_(out, "}?"); + } else if (iff.type == TRD_IFF_OVERR) { + ly_print_(out, "%s", iff.str); + } +} + +/** + * @brief Print just \--\ \ with opts mark. + * @param[in] node contains items to print. + * @param[in] out is output handler. + */ +static void +trp_print_node_up_to_name(const struct trt_node *node, struct ly_out *out) +{ + if (node->name.type == TRD_NODE_TRIPLE_DOT) { + trp_print_node_name(node->name, out); + return; + } + /* -- */ + ly_print_(out, "%s", node->status); + ly_print_(out, "--"); + /* If the node is a case node, there is no space before the + * also case node has no flags. + */ + if (node->flags && (node->name.type != TRD_NODE_CASE)) { + ly_print_(out, "%s", node->flags); + ly_print_(out, " "); + } + /* */ + trp_print_node_name(node->name, out); +} + +/** + * @brief Print alignment (spaces) instead of + * \--\ \ for divided node. + * @param[in] node contains items to print. + * @param[in] out is output handler. + */ +static void +trp_print_divided_node_up_to_name(const struct trt_node *node, struct ly_out *out) +{ + uint32_t space = strlen(node->flags); + + if (node->name.type == TRD_NODE_CASE) { + /* :( */ + space += strlen(TRD_NODE_NAME_PREFIX_CASE); + } else if (node->name.type == TRD_NODE_CHOICE) { + /* ( */ + space += strlen(TRD_NODE_NAME_PREFIX_CHOICE); + } else { + /* _ */ + space += strlen(" "); + } + + /* + * __ + */ + space += TRD_INDENT_LONG_LINE_BREAK; + + ly_print_(out, "%*c", space, ' '); +} + +/** + * @brief Print struct trt_node structure. + * @param[in] node is item to print. + * @param[in] pck package of functions for + * printing [\] and \. + * @param[in] indent is the indent in node. + * @param[in,out] out is output handler. + */ +static void +trp_print_node(const struct trt_node *node, struct trt_pck_print pck, struct trt_indent_in_node indent, struct ly_out *out) +{ + ly_bool triple_dot; + ly_bool divided; + struct trt_cf_print cf_print_keys; + struct trt_cf_print cf_print_iffeatures; + + if (trp_node_is_empty(node)) { + return; + } + + /* -- */ + triple_dot = node->name.type == TRD_NODE_TRIPLE_DOT; + divided = indent.type == TRD_INDENT_IN_NODE_DIVIDED; + + if (triple_dot) { + trp_print_node_name(node->name, out); + return; + } else if (!divided) { + trp_print_node_up_to_name(node, out); + } else { + trp_print_divided_node_up_to_name(node, out); + } + + /* */ + /* ___*/ + cf_print_keys.ctx = pck.tree_ctx; + cf_print_keys.pf = pck.fps.print_keys; + + trp_print_opts_keys(node->name, indent.btw_name_opts, cf_print_keys, out); + + /* __ */ + if (indent.btw_opts_type > 0) { + ly_print_(out, "%*c", indent.btw_opts_type, ' '); + } + + /* */ + trp_print_type(node->type, out); + + /* __ */ + if (indent.btw_type_iffeatures > 0) { + ly_print_(out, "%*c", indent.btw_type_iffeatures, ' '); + } + + /* */ + cf_print_iffeatures.ctx = pck.tree_ctx; + cf_print_iffeatures.pf = pck.fps.print_features_names; + + trp_print_iffeatures(node->iffeatures, cf_print_iffeatures, out); +} + +/** + * @brief Print keyword based on trt_keyword_stmt.type. + * @param[in] ks is keyword statement to print. + * @param[in,out] out is output handler + */ +static void +trt_print_keyword_stmt_begin(struct trt_keyword_stmt ks, struct ly_out *out) +{ + if (!strcmp(ks.section_name, TRD_KEYWORD_MODULE) || + !strcmp(ks.section_name, TRD_KEYWORD_SUBMODULE)) { + ly_print_(out, "%s: ", ks.section_name); + return; + } + + ly_print_(out, "%*c", TRD_INDENT_LINE_BEGIN, ' '); + if (ks.argument) { + ly_print_(out, "%s ", ks.section_name); + } else { + ly_print_(out, "%s", ks.section_name); + } +} + +/** + * @brief Print trt_keyword_stmt.str which is string of name or path. + * @param[in] ks is keyword statement structure. + * @param[in] mll is max line length. + * @param[in,out] out is output handler. + */ +static void +trt_print_keyword_stmt_str(struct trt_keyword_stmt ks, size_t mll, struct ly_out *out) +{ + uint32_t ind_initial; + uint32_t ind_divided; + /* flag if path must be splitted to more lines */ + ly_bool linebreak_was_set; + /* flag if at least one subpath was printed */ + ly_bool subpath_printed; + /* the sum of the sizes of the substrings on the current line */ + uint32_t how_far; + /* pointer to start of the subpath */ + const char *sub_ptr; + /* size of subpath from sub_ptr */ + size_t sub_len; + + if ((!ks.argument) || (ks.argument[0] == '\0')) { + return; + } + + /* module name cannot be splitted */ + if (!strcmp(ks.section_name, TRD_KEYWORD_MODULE) || !strcmp(ks.section_name, TRD_KEYWORD_SUBMODULE)) { + ly_print_(out, "%s", ks.argument); + return; + } + + /* after -> for trd_keyword_stmt_body do */ + + /* set begin indentation */ + ind_initial = TRD_INDENT_LINE_BEGIN + strlen(ks.section_name) + 1; + ind_divided = ind_initial + TRD_INDENT_LONG_LINE_BREAK; + linebreak_was_set = 0; + subpath_printed = 0; + how_far = 0; + sub_ptr = ks.argument; + sub_len = 0; + + while (sub_ptr[0] != '\0') { + uint32_t ind; + /* skip slash */ + const char *tmp = sub_ptr[0] == '/' ? sub_ptr + 1 : sub_ptr; + + /* get position of the end of substr */ + tmp = strchr(tmp, '/'); + /* set correct size if this is a last substring */ + sub_len = !tmp ? strlen(sub_ptr) : (size_t)(tmp - sub_ptr); + /* actualize sum of the substring's sizes on the current line */ + how_far += sub_len; + /* correction due to colon character if it this is last substring */ + how_far = *(sub_ptr + sub_len) == '\0' ? how_far + 1 : how_far; + /* choose indentation which depends on + * whether the string is printed on multiple lines or not + */ + ind = linebreak_was_set ? ind_divided : ind_initial; + if (ind + how_far <= mll) { + /* printing before max line length */ + sub_ptr = trg_print_substr(sub_ptr, sub_len, out); + subpath_printed = 1; + } else { + /* printing on new line */ + if (subpath_printed == 0) { + /* first subpath is too long + * but print it at first line anyway + */ + sub_ptr = trg_print_substr(sub_ptr, sub_len, out); + subpath_printed = 1; + continue; + } + ly_print_(out, "\n"); + ly_print_(out, "%*c", ind_divided, ' '); + linebreak_was_set = 1; + sub_ptr = trg_print_substr(sub_ptr, sub_len, out); + how_far = sub_len; + subpath_printed = 1; + } + } +} + +/** + * @brief Print separator based on trt_keyword_stmt.type + * @param[in] ks is keyword statement structure. + * @param[in,out] out is output handler. + */ +static void +trt_print_keyword_stmt_end(struct trt_keyword_stmt ks, struct ly_out *out) +{ + if (!strcmp(ks.section_name, TRD_KEYWORD_MODULE) || !strcmp(ks.section_name, TRD_KEYWORD_SUBMODULE)) { + return; + } else if (ks.has_node) { + ly_print_(out, ":"); + } +} + +/** + * @brief Print entire struct trt_keyword_stmt structure. + * @param[in] ks is item to print. + * @param[in] mll is max line length. + * @param[in,out] out is output handler. + */ +static void +trp_print_keyword_stmt(struct trt_keyword_stmt ks, size_t mll, struct ly_out *out) +{ + assert(ks.section_name); + trt_print_keyword_stmt_begin(ks, out); + trt_print_keyword_stmt_str(ks, mll, out); + trt_print_keyword_stmt_end(ks, out); +} + +/********************************************************************** + * Main trp functions + *********************************************************************/ + +/** + * @brief Printing one line including wrapper and node + * which can be incomplete (divided). + * @param[in] node is \ representation. + * @param[in] pck contains special printing functions callback. + * @param[in] indent contains wrapper and indent in node numbers. + * @param[in,out] out is output handler. + */ +static void +trp_print_line(const struct trt_node *node, struct trt_pck_print pck, struct trt_pck_indent indent, struct ly_out *out) +{ + trp_print_wrapper(indent.wrapper, out); + trp_print_node(node, pck, indent.in_node, out); +} + +/** + * @brief Printing one line including wrapper and + * \--\ \\. + * @param[in] node is \ representation. + * @param[in] wr is wrapper for printing indentation before node. + * @param[in] out is output handler. + */ +static void +trp_print_line_up_to_node_name(const struct trt_node *node, struct trt_wrapper wr, struct ly_out *out) +{ + trp_print_wrapper(wr, out); + trp_print_node_up_to_name(node, out); +} + +/** + * @brief Check if leafref target must be change to string 'leafref' + * because his target string is too long. + * @param[in] node containing leafref target. + * @param[in] wr is wrapper for printing indentation before node. + * @param[in] mll is max line length. + * @param[in] out is output handler. + * @return true if leafref must be changed to string 'leafref'. + */ +static ly_bool +trp_leafref_target_is_too_long(const struct trt_node *node, struct trt_wrapper wr, size_t mll, struct ly_out *out) +{ + size_t type_len; + struct ly_out_clb_arg *data; + + if (node->type.type != TRD_TYPE_TARGET) { + return 0; + } + + /* set ly_out to counting characters */ + data = out->method.clb.arg; + + data->counter = 0; + data->mode = TRD_CHAR_COUNT; + /* count number of printed bytes */ + trp_print_wrapper(wr, out); + ly_print_(out, "%*c", TRD_INDENT_BTW_SIBLINGS, ' '); + trp_print_divided_node_up_to_name(node, out); + data->mode = TRD_PRINT; + type_len = strlen(node->type.str); + + return data->counter + type_len > mll; +} + +/** + * @brief Get default indent in node based on node values. + * @param[in] node is \ representation. + * @return Default indent in node assuming that the node + * will not be divided. + */ +static struct trt_indent_in_node +trp_default_indent_in_node(const struct trt_node *node) +{ + struct trt_indent_in_node ret; + uint32_t opts_len = 0; + + ret.type = TRD_INDENT_IN_NODE_NORMAL; + + /* btw_name_opts */ + ret.btw_name_opts = node->name.keys ? TRD_INDENT_BEFORE_KEYS : 0; + + /* btw_opts_type */ + if (!(TRP_TRT_TYPE_IS_EMPTY(node->type))) { + if (trp_mark_is_used(node->name)) { + opts_len += node->name.add_opts ? strlen(node->name.add_opts) : 0; + opts_len += node->name.opts ? strlen(node->name.opts) : 0; + ret.btw_opts_type = TRD_INDENT_BEFORE_TYPE > opts_len ? 1 : TRD_INDENT_BEFORE_TYPE - opts_len; + } else { + ret.btw_opts_type = TRD_INDENT_BEFORE_TYPE; + } + } else { + ret.btw_opts_type = 0; + } + + /* btw_type_iffeatures */ + ret.btw_type_iffeatures = node->iffeatures.type == TRD_IFF_PRESENT ? TRD_INDENT_BEFORE_IFFEATURES : 0; + + return ret; +} + +/** + * @brief Setting linebreaks in trt_indent_in_node. + * + * The order where the linebreak tag can be placed is from the end. + * + * @param[in] indent containing alignment lengths + * or already linebreak marks. + * @return indent with a newly placed linebreak tag. + * @return .type set to TRD_INDENT_IN_NODE_FAILED if it is not possible + * to place a more linebreaks. + */ +static struct trt_indent_in_node +trp_indent_in_node_place_break(struct trt_indent_in_node indent) +{ + /* somewhere must be set a line break in node */ + struct trt_indent_in_node ret = indent; + + /* gradually break the node from the end */ + if ((indent.btw_type_iffeatures != TRD_LINEBREAK) && (indent.btw_type_iffeatures != 0)) { + ret.btw_type_iffeatures = TRD_LINEBREAK; + } else if ((indent.btw_opts_type != TRD_LINEBREAK) && (indent.btw_opts_type != 0)) { + ret.btw_opts_type = TRD_LINEBREAK; + } else if ((indent.btw_name_opts != TRD_LINEBREAK) && (indent.btw_name_opts != 0)) { + /* set line break between name and opts */ + ret.btw_name_opts = TRD_LINEBREAK; + } else { + /* it is not possible to place a more line breaks, + * unfortunately the max_line_length constraint is violated + */ + ret.type = TRD_INDENT_IN_NODE_FAILED; + } + return ret; +} + +/** + * @brief Set the first half of the node based on the linebreak mark. + * + * Items in the second half of the node will be empty. + * + * @param[in,out] innod contains information in which part of the \ + * the first half ends. Set first half of the node, indent is unchanged. + */ +static void +trp_first_half_node(struct trt_pair_indent_node *innod) +{ + if (innod->indent.btw_name_opts == TRD_LINEBREAK) { + innod->node.type = TRP_EMPTY_TRT_TYPE; + innod->node.iffeatures = TRP_EMPTY_TRT_IFFEATURES; + } else if (innod->indent.btw_opts_type == TRD_LINEBREAK) { + innod->node.type = TRP_EMPTY_TRT_TYPE; + innod->node.iffeatures = TRP_EMPTY_TRT_IFFEATURES; + } else if (innod->indent.btw_type_iffeatures == TRD_LINEBREAK) { + innod->node.iffeatures = TRP_EMPTY_TRT_IFFEATURES; + } +} + +/** + * @brief Set the second half of the node based on the linebreak mark. + * + * Items in the first half of the node will be empty. + * Indentations belonging to the first node will be reset to zero. + * + * @param[in,out] innod contains information in which part of the \ + * the second half starts. Set second half of the node, indent is newly set. + */ +static void +trp_second_half_node(struct trt_pair_indent_node *innod) +{ + if (innod->indent.btw_name_opts < 0) { + /* Logically, the information up to token should + * be deleted, but the the trp_print_node function needs it to + * create the correct indent. + */ + innod->indent.btw_name_opts = 0; + innod->indent.btw_opts_type = TRP_TRT_TYPE_IS_EMPTY(innod->node.type) ? 0 : TRD_INDENT_BEFORE_TYPE; + innod->indent.btw_type_iffeatures = innod->node.iffeatures.type == TRD_IFF_NON_PRESENT ? 0 : TRD_INDENT_BEFORE_IFFEATURES; + } else if (innod->indent.btw_opts_type == TRD_LINEBREAK) { + innod->indent.btw_name_opts = 0; + innod->indent.btw_opts_type = 0; + innod->indent.btw_type_iffeatures = innod->node.iffeatures.type == TRD_IFF_NON_PRESENT ? 0 : TRD_INDENT_BEFORE_IFFEATURES; + } else if (innod->indent.btw_type_iffeatures == TRD_LINEBREAK) { + innod->node.type = TRP_EMPTY_TRT_TYPE; + innod->indent.btw_name_opts = 0; + innod->indent.btw_opts_type = 0; + innod->indent.btw_type_iffeatures = 0; + } +} + +/** + * @brief Get the correct alignment for the node. + * + * This function is recursively called itself. It's like a backend + * function for a function ::trp_try_normal_indent_in_node(). + * + * @param[in] pck contains speciall callback functions for printing. + * @param[in] wrapper contains information about '|' context. + * @param[in] mll is max line length. + * @param[in,out] cnt counting number of characters to print. + * @param[in,out] out is output handler. + * @param[in,out] innod pair of node and indentation numbers of that node. + */ +static void +trp_try_normal_indent_in_node_(struct trt_pck_print pck, struct trt_wrapper wrapper, size_t mll, size_t *cnt, + struct ly_out *out, struct trt_pair_indent_node *innod) +{ + trp_print_line(&innod->node, pck, TRP_INIT_PCK_INDENT(wrapper, innod->indent), out); + + if (*cnt <= mll) { + /* success */ + return; + } else { + innod->indent = trp_indent_in_node_place_break(innod->indent); + if (innod->indent.type != TRD_INDENT_IN_NODE_FAILED) { + /* erase information in node due to line break */ + trp_first_half_node(innod); + /* check if line fits, recursive call */ + *cnt = 0; + trp_try_normal_indent_in_node_(pck, wrapper, mll, cnt, out, innod); + /* make sure that the result will be with the status divided + * or eventually with status failed */ + innod->indent.type = innod->indent.type == TRD_INDENT_IN_NODE_FAILED ? TRD_INDENT_IN_NODE_FAILED : TRD_INDENT_IN_NODE_DIVIDED; + } + return; + } +} + +/** + * @brief Get the correct alignment for the node. + * + * @param[in] node is \ representation. + * @param[in] pck contains speciall callback functions for printing. + * @param[in] indent contains wrapper and indent in node numbers. + * @param[in] mll is max line length. + * @param[in,out] out is output handler. + * @param[out] innod If the node does not fit in the line, some indent variable has negative value as a line break sign + * and therefore ::TRD_INDENT_IN_NODE_DIVIDED is set. + * If the node fits into the line, all indent variables values has non-negative number and therefore + * ::TRD_INDENT_IN_NODE_NORMAL is set. + * If the node does not fit into the line, all indent variables has negative or zero values, function failed + * and therefore ::TRD_INDENT_IN_NODE_FAILED is set. + */ +static void +trp_try_normal_indent_in_node(const struct trt_node *node, struct trt_pck_print pck, struct trt_pck_indent indent, + size_t mll, struct ly_out *out, struct trt_pair_indent_node *innod) +{ + struct ly_out_clb_arg *data; + + *innod = TRP_INIT_PAIR_INDENT_NODE(indent.in_node, *node); + + /* set ly_out to counting characters */ + data = out->method.clb.arg; + + data->counter = 0; + data->mode = TRD_CHAR_COUNT; + trp_try_normal_indent_in_node_(pck, indent.wrapper, mll, &data->counter, out, innod); + data->mode = TRD_PRINT; +} + +/** + * @brief Auxiliary function for ::trp_print_entire_node() + * that prints split nodes. + * @param[in] node is node representation. + * @param[in] ppck contains speciall callback functions for printing. + * @param[in] ipck contains wrapper and indent in node numbers. + * @param[in] mll is max line length. + * @param[in,out] out is output handler. + */ +static void +trp_print_divided_node(const struct trt_node *node, struct trt_pck_print ppck, struct trt_pck_indent ipck, size_t mll, struct ly_out *out) +{ + ly_bool entire_node_was_printed; + struct trt_pair_indent_node innod; + + trp_try_normal_indent_in_node(node, ppck, ipck, mll, out, &innod); + + if (innod.indent.type == TRD_INDENT_IN_NODE_FAILED) { + /* nothing can be done, continue as usual */ + innod.indent.type = TRD_INDENT_IN_NODE_DIVIDED; + } + + trp_print_line(&innod.node, ppck, TRP_INIT_PCK_INDENT(ipck.wrapper, innod.indent), out); + entire_node_was_printed = trp_indent_in_node_are_eq(ipck.in_node, innod.indent); + + if (!entire_node_was_printed) { + ly_print_(out, "\n"); + /* continue with second half node */ + innod.node = *node; + trp_second_half_node(&innod); + /* continue with printing node */ + trp_print_divided_node(&innod.node, ppck, TRP_INIT_PCK_INDENT(ipck.wrapper, innod.indent), mll, out); + } else { + return; + } +} + +/** + * @brief Printing of the wrapper and the whole node, + * which can be divided into several lines. + * @param[in] node_p is node representation. + * @param[in] ppck contains speciall callback functions for printing. + * @param[in] ipck contains wrapper and indent in node numbers. + * @param[in] mll is max line length. + * @param[in,out] out is output handler. + */ +static void +trp_print_entire_node(const struct trt_node *node_p, struct trt_pck_print ppck, struct trt_pck_indent ipck, size_t mll, + struct ly_out *out) +{ + struct trt_pair_indent_node innod; + struct trt_pck_indent tmp; + struct trt_node node; + + node = *node_p; + if (trp_leafref_target_is_too_long(&node, ipck.wrapper, mll, out)) { + node.type.type = TRD_TYPE_LEAFREF; + } + + /* check if normal indent is possible */ + trp_try_normal_indent_in_node(&node, ppck, ipck, mll, out, &innod); + + if (innod.indent.type == TRD_INDENT_IN_NODE_NORMAL) { + /* node fits to one line */ + trp_print_line(&node, ppck, ipck, out); + } else if (innod.indent.type == TRD_INDENT_IN_NODE_DIVIDED) { + /* node will be divided */ + /* print first half */ + tmp = TRP_INIT_PCK_INDENT(ipck.wrapper, innod.indent); + /* pretend that this is normal node */ + tmp.in_node.type = TRD_INDENT_IN_NODE_NORMAL; + + trp_print_line(&innod.node, ppck, tmp, out); + ly_print_(out, "\n"); + + /* continue with second half on new line */ + innod.node = node; + trp_second_half_node(&innod); + tmp = TRP_INIT_PCK_INDENT(trp_wrapper_if_last_sibling(ipck.wrapper, node.last_one), innod.indent); + + trp_print_divided_node(&innod.node, ppck, tmp, mll, out); + } else if (innod.indent.type == TRD_INDENT_IN_NODE_FAILED) { + /* node name is too long */ + trp_print_line_up_to_node_name(&node, ipck.wrapper, out); + + if (trp_node_body_is_empty(&node)) { + return; + } else { + ly_print_(out, "\n"); + + innod.node = node; + trp_second_half_node(&innod); + innod.indent.type = TRD_INDENT_IN_NODE_DIVIDED; + tmp = TRP_INIT_PCK_INDENT(trp_wrapper_if_last_sibling(ipck.wrapper, node.last_one), innod.indent); + + trp_print_divided_node(&innod.node, ppck, tmp, mll, out); + } + } +} + +/** + * @brief Check if parent-stmt is valid for printing extensinon. + * + * @param[in] lysc_tree flag if ext is from compiled tree. + * @param[in] ext Extension to check. + * @return 1 if extension is valid. + */ +static ly_bool +trp_ext_parent_is_valid(ly_bool lysc_tree, void *ext) +{ + enum ly_stmt parent_stmt; + + if (lysc_tree) { + parent_stmt = ((struct lysc_ext_instance *)ext)->parent_stmt; + } else { + parent_stmt = ((struct lysp_ext_instance *)ext)->parent_stmt; + } + if ((parent_stmt & LY_STMT_OP_MASK) || (parent_stmt & LY_STMT_DATA_NODE_MASK) || + (parent_stmt & LY_STMT_SUBMODULE) || parent_stmt & LY_STMT_MODULE) { + return 1; + } else { + return 0; + } +} + +/** + * @brief Check if printer_tree can use node extension. + * + * @param[in] lysc_tree Flag if @p node is compiled. + * @param[in] node to check. Its type is lysc_node or lysp_node. + * @return Pointer to extension instance which printer_tree can used. + */ +static void * +trp_ext_is_present(ly_bool lysc_tree, const void *node) +{ + const struct lysp_node *pn; + const struct lysc_node *cn; + LY_ARRAY_COUNT_TYPE i; + void *ret = NULL; + + if (!node) { + return NULL; + } + + if (lysc_tree) { + cn = (const struct lysc_node *)node; + LY_ARRAY_FOR(cn->exts, i) { + if (!(cn->exts && cn->exts->def->plugin && cn->exts->def->plugin->printer_ctree)) { + continue; + } + if (!trp_ext_parent_is_valid(1, &cn->exts[i])) { + continue; + } + ret = &cn->exts[i]; + break; + } + } else { + pn = (const struct lysp_node *)node; + LY_ARRAY_FOR(pn->exts, i) { + if (!(pn->exts && pn->exts->record->plugin.printer_ptree)) { + continue; + } + if (!trp_ext_parent_is_valid(0, &pn->exts[i])) { + continue; + } + ret = &pn->exts[i]; + break; + } + } + + return ret; +} + +/** + * @brief Check if printer_tree can use node extension. + * + * @param[in] tc Context with current node. + * @return 1 if some extension for printer_tree is valid. + */ +static ly_bool +trp_ext_is_present_in_node(struct trt_tree_ctx *tc) +{ + if (tc->lysc_tree && trp_ext_is_present(tc->lysc_tree, tc->cn)) { + return 1; + } else if (trp_ext_is_present(tc->lysc_tree, tc->pn)) { + return 1; + } + + return 0; +} + +/** + * @brief Release allocated memory and set pointers to NULL. + * + * @param[in,out] overr is override structure to release. + * @param[out] filtered is flag to reset. + */ +static void +trp_ext_free_node_override(struct lyplg_ext_sprinter_tree_node_override *overr, ly_bool *filtered) +{ + *filtered = 0; + overr->flags = NULL; + overr->add_opts = NULL; +} + +/** + * @brief Release private plugin data. + * + * @param[in,out] plug_ctx is plugin context. + */ +static void +trp_ext_free_plugin_ctx(struct lyspr_tree_ctx *plug_ctx) +{ + LY_ARRAY_FREE(plug_ctx->schemas); + if (plug_ctx->free_plugin_priv) { + plug_ctx->free_plugin_priv(plug_ctx->plugin_priv); + } +} + +/********************************************************************** + * trop and troc getters + *********************************************************************/ + +/** + * @brief Get nodetype. + * @param[in] node is any lysp_node. + */ +static uint16_t +trop_nodetype(const void *node) +{ + return ((const struct lysp_node *)node)->nodetype; +} + +/** + * @brief Get sibling. + * @param[in] node is any lysp_node. + */ +static const void * +trop_next(const void *node) +{ + return ((const struct lysp_node *)node)->next; +} + +/** + * @brief Get parent. + * @param[in] node is any lysp_node. + */ +static const void * +trop_parent(const void *node) +{ + return ((const struct lysp_node *)node)->parent; +} + +/** + * @brief Try to get child. + * @param[in] node is any lysp_node. + */ +static const void * +trop_child(const void *node) +{ + return lysp_node_child(node); +} + +/** + * @brief Try to get action. + * @param[in] node is any lysp_node. + */ +static const void * +trop_actions(const void *node) +{ + return lysp_node_actions(node); +} + +/** + * @brief Try to get action. + * @param[in] node must be of type lysp_node_action. + */ +static const void * +trop_action_input(const void *node) +{ + return &((const struct lysp_node_action *)node)->input; +} + +/** + * @brief Try to get action. + * @param[in] node must be of type lysp_node_action. + */ +static const void * +trop_action_output(const void *node) +{ + return &((const struct lysp_node_action *)node)->output; +} + +/** + * @brief Try to get action. + * @param[in] node is any lysp_node. + */ +static const void * +trop_notifs(const void *node) +{ + return lysp_node_notifs(node); +} + +/** + * @brief Fill struct tro_getters with @ref TRP_trop getters + * which are adapted to lysp nodes. + */ +static struct tro_getters +trop_init_getters(void) +{ + return (struct tro_getters) { + .nodetype = trop_nodetype, + .next = trop_next, + .parent = trop_parent, + .child = trop_child, + .actions = trop_actions, + .action_input = trop_action_input, + .action_output = trop_action_output, + .notifs = trop_notifs + }; +} + +/** + * @brief Get nodetype. + * @param[in] node is any lysc_node. + */ +static uint16_t +troc_nodetype(const void *node) +{ + return ((const struct lysc_node *)node)->nodetype; +} + +/** + * @brief Get sibling. + * @param[in] node is any lysc_node. + */ +static const void * +troc_next(const void *node) +{ + return ((const struct lysc_node *)node)->next; +} + +/** + * @brief Get parent. + * @param[in] node is any lysc_node. + */ +static const void * +troc_parent(const void *node) +{ + return ((const struct lysc_node *)node)->parent; +} + +/** + * @brief Try to get child. + * @param[in] node is any lysc_node. + */ +static const void * +troc_child(const void *node) +{ + return lysc_node_child(node); +} + +/** + * @brief Try to get action. + * @param[in] node is any lysc_node. + */ +static const void * +troc_actions(const void *node) +{ + return lysc_node_actions(node); +} + +/** + * @brief Try to get action. + * @param[in] node must be of type lysc_node_action. + */ +static const void * +troc_action_input(const void *node) +{ + return &((const struct lysc_node_action *)node)->input; +} + +/** + * @brief Try to get action. + * @param[in] node must be of type lysc_node_action. + */ +static const void * +troc_action_output(const void *node) +{ + return &((const struct lysc_node_action *)node)->output; +} + +/** + * @brief Try to get action. + * @param[in] node is any lysc_node. + */ +static const void * +troc_notifs(const void *node) +{ + return lysc_node_notifs(node); +} + +/** + * @brief Fill struct tro_getters with @ref TRP_troc getters + * which are adapted to lysc nodes. + */ +static struct tro_getters +troc_init_getters(void) +{ + return (struct tro_getters) { + .nodetype = troc_nodetype, + .next = troc_next, + .parent = troc_parent, + .child = troc_child, + .actions = troc_actions, + .action_input = troc_action_input, + .action_output = troc_action_output, + .notifs = troc_notifs + }; +} + +/********************************************************************** + * tro functions + *********************************************************************/ + +/** + * @brief Call override function for @p node. + * + * @param[in] lysc_tree if @p node is compiled. + * @param[in] node to create override. + * @param[in] erase_node_overr if override structure must be reseted. + * @param[in,out] plc current plugin context. + * @return pointer to override structure or NULL. Override structure in @p plc is updated too. + */ +static struct lyplg_ext_sprinter_tree_node_override * +tro_set_node_overr(ly_bool lysc_tree, const void *node, ly_bool erase_node_overr, struct trt_plugin_ctx *plc) +{ + LY_ERR rc = LY_SUCCESS; + struct lyplg_ext_sprinter_tree_node_override *no; + struct lyspr_tree_ctx *plug_ctx; + struct lysc_ext_instance *ce; + struct lysp_ext_instance *pe; + + if (erase_node_overr) { + trp_ext_free_node_override(&plc->node_overr, &plc->filtered); + } + no = &plc->node_overr; + if (!plc->ctx && lysc_tree && (ce = trp_ext_is_present(lysc_tree, node))) { + rc = ce->def->plugin->printer_ctree(ce, NULL, &no->flags, &no->add_opts); + } else if (!plc->ctx && (pe = trp_ext_is_present(lysc_tree, node))) { + rc = pe->record->plugin.printer_ptree(pe, NULL, &no->flags, &no->add_opts); + } else if (plc->ctx) { + if (plc->schema && plc->schema->compiled && plc->schema->cn_overr) { + rc = plc->schema->cn_overr(node, plc->ctx->plugin_priv, &plc->filtered, &no->flags, &no->add_opts); + } else if (plc->schema && plc->schema->pn_overr) { + rc = plc->schema->pn_overr(node, plc->ctx->plugin_priv, &plc->filtered, &no->flags, &no->add_opts); + } else { + no = NULL; + } + if (trp_ext_is_present(lysc_tree, node)) { + plug_ctx = plc->ctx; + plc->ctx = NULL; + tro_set_node_overr(lysc_tree, node, 0, plc); + plc->ctx = plug_ctx; + } + } else { + no = NULL; + } + + if (rc) { + plc->last_error = rc; + no = NULL; + } + + return no; +} + +/** + * @brief Get next sibling of the current node. + * + * This is a general algorithm that is able to + * work with lysp_node or lysc_node. + * + * @param[in] node points to lysp_node or lysc_node. + * @param[in] tc current tree context. + * @return next sibling node. + */ +static const void * +tro_next_sibling(const void *node, const struct trt_tree_ctx *tc) +{ + struct tro_getters get; + struct trt_plugin_ctx plugin_ctx; + const void *tmp, *parent, *sibl; + + assert(node); + + get = tc->lysc_tree ? troc_init_getters() : trop_init_getters(); + + if (get.nodetype(node) & (LYS_RPC | LYS_ACTION)) { + if ((tmp = get.next(node))) { + /* next action exists */ + sibl = tmp; + } else if ((parent = get.parent(node))) { + /* maybe if notif exists as sibling */ + sibl = get.notifs(parent); + } else { + sibl = NULL; + } + } else if (get.nodetype(node) & LYS_INPUT) { + if ((parent = get.parent(node))) { + /* if output action has data */ + if (get.child(get.action_output(parent))) { + /* then next sibling is output action */ + sibl = get.action_output(parent); + } else { + /* input action cannot have siblings other + * than output action. + */ + sibl = NULL; + } + } else { + /* there is no way how to get output action */ + sibl = NULL; + } + } else if (get.nodetype(node) & LYS_OUTPUT) { + /* output action cannot have siblings */ + sibl = NULL; + } else if (get.nodetype(node) & LYS_NOTIF) { + /* must have as a sibling only notif */ + sibl = get.next(node); + } else { + /* for rest of nodes */ + if ((tmp = get.next(node))) { + /* some sibling exists */ + sibl = tmp; + } else if ((parent = get.parent(node))) { + /* Action and notif are siblings too. + * They can be reached through parent. + */ + if ((tmp = get.actions(parent))) { + /* next sibling is action */ + sibl = tmp; + } else if ((tmp = get.notifs(parent))) { + /* next sibling is notif */ + sibl = tmp; + } else { + /* sibling not exists */ + sibl = NULL; + } + } else { + /* sibling not exists */ + sibl = NULL; + } + } + + plugin_ctx = tc->plugin_ctx; + if (sibl && tro_set_node_overr(tc->lysc_tree, sibl, 1, &plugin_ctx) && plugin_ctx.filtered) { + return tro_next_sibling(sibl, tc); + } + + return sibl; +} + +/** + * @brief Get child of the current node. + * + * This is a general algorithm that is able to + * work with lysp_node or lysc_node. + * + * @param[in] node points to lysp_node or lysc_node. + * @param[in] tc current tree context. + * @return child node. + */ +static const void * +tro_next_child(const void *node, const struct trt_tree_ctx *tc) +{ + struct tro_getters get; + struct trt_plugin_ctx plugin_ctx; + const void *tmp, *child; + + assert(node); + + get = tc->lysc_tree ? troc_init_getters() : trop_init_getters(); + + if (get.nodetype(node) & (LYS_ACTION | LYS_RPC)) { + if (get.child(get.action_input(node))) { + /* go to LYS_INPUT */ + child = get.action_input(node); + } else if (get.child(get.action_output(node))) { + /* go to LYS_OUTPUT */ + child = get.action_output(node); + } else { + /* input action and output action have no data */ + child = NULL; + } + } else { + if ((tmp = get.child(node))) { + child = tmp; + } else { + /* current node can't have children or has no children */ + /* but maybe has some actions or notifs */ + if ((tmp = get.actions(node))) { + child = tmp; + } else if ((tmp = get.notifs(node))) { + child = tmp; + } else { + child = NULL; + } + } + } + + plugin_ctx = tc->plugin_ctx; + if (child && tro_set_node_overr(tc->lysc_tree, child, 1, &plugin_ctx) && plugin_ctx.filtered) { + return tro_next_sibling(child, tc); + } + + return child; +} + +/** + * @brief Get new trt_parent_cache if we apply the transfer + * to the child node in the tree. + * @param[in] ca is parent cache for current node. + * @param[in] tc contains current tree node. + * @return Cache for the current node. + */ +static struct trt_parent_cache +tro_parent_cache_for_child(struct trt_parent_cache ca, const struct trt_tree_ctx *tc) +{ + struct trt_parent_cache ret = TRP_EMPTY_PARENT_CACHE; + + if (!tc->lysc_tree) { + const struct lysp_node *pn = tc->pn; + + ret.ancestor = + pn->nodetype & (LYS_INPUT) ? TRD_ANCESTOR_RPC_INPUT : + pn->nodetype & (LYS_OUTPUT) ? TRD_ANCESTOR_RPC_OUTPUT : + pn->nodetype & (LYS_NOTIF) ? TRD_ANCESTOR_NOTIF : + ca.ancestor; + + ret.lys_status = + pn->flags & (LYS_STATUS_CURR | LYS_STATUS_DEPRC | LYS_STATUS_OBSLT) ? pn->flags : + ca.lys_status; + + ret.lys_config = + ca.ancestor == TRD_ANCESTOR_RPC_INPUT ? 0 : /* because will be -w */ + ca.ancestor == TRD_ANCESTOR_RPC_OUTPUT ? LYS_CONFIG_R : + pn->flags & (LYS_CONFIG_R | LYS_CONFIG_W) ? pn->flags : + ca.lys_config; + + ret.last_list = + pn->nodetype & (LYS_LIST) ? (struct lysp_node_list *)pn : + ca.last_list; + } + + return ret; +} + +/** + * @brief Transformation of the Schema nodes flags to + * Tree diagram \. + * @param[in] flags is node's flags obtained from the tree. + */ +static char * +tro_flags2status(uint16_t flags) +{ + return flags & LYS_STATUS_OBSLT ? "o" : + flags & LYS_STATUS_DEPRC ? "x" : + "+"; +} + +/** + * @brief Transformation of the Schema nodes flags to Tree diagram + * \ but more specifically 'ro' or 'rw'. + * @param[in] flags is node's flags obtained from the tree. + */ +static char * +tro_flags2config(uint16_t flags) +{ + return flags & LYS_CONFIG_R ? TRD_FLAGS_TYPE_RO : + flags & LYS_CONFIG_W ? TRD_FLAGS_TYPE_RW : + TRD_FLAGS_TYPE_EMPTY; +} + +/** + * @brief Print current node's iffeatures. + * @param[in] tc is tree context. + * @param[in,out] out is output handler. + */ +static void +tro_print_features_names(const struct trt_tree_ctx *tc, struct ly_out *out) +{ + const struct lysp_qname *iffs; + + if (tc->lysc_tree) { + assert(TRP_TREE_CTX_LYSP_NODE_PRESENT(tc->cn)); + iffs = TRP_TREE_CTX_GET_LYSP_NODE(tc->cn)->iffeatures; + } else { + iffs = tc->pn->iffeatures; + } + LY_ARRAY_COUNT_TYPE i; + + LY_ARRAY_FOR(iffs, i) { + if (i == 0) { + ly_print_(out, "%s", iffs[i].str); + } else { + ly_print_(out, ",%s", iffs[i].str); + } + } + +} + +/** + * @brief Print current list's keys. + * + * Well, actually printing keys in the lysp_tree is trivial, + * because char* points to all keys. However, special functions have + * been reserved for this, because in principle the list of elements + * can have more implementations. + * + * @param[in] tc is tree context. + * @param[in,out] out is output handler. + */ +static void +tro_print_keys(const struct trt_tree_ctx *tc, struct ly_out *out) +{ + const struct lysp_node_list *list; + + if (tc->lysc_tree) { + assert(TRP_TREE_CTX_LYSP_NODE_PRESENT(tc->cn)); + list = (const struct lysp_node_list *)TRP_TREE_CTX_GET_LYSP_NODE(tc->cn); + } else { + list = (const struct lysp_node_list *)tc->pn; + } + assert(list->nodetype & LYS_LIST); + + if (trg_charptr_has_data(list->key)) { + ly_print_(out, "%s", list->key); + } +} + +/** + * @brief Get address of the current node. + * @param[in] tc contains current node. + * @return Address of lysc_node or lysp_node, or NULL. + */ +static const void * +tro_tree_ctx_get_node(const struct trt_tree_ctx *tc) +{ + return tc->lysc_tree ? + (const void *)tc->cn : + (const void *)tc->pn; +} + +/** + * @brief Get address of current node's child. + * @param[in,out] tc contains current node. + */ +static const void * +tro_tree_ctx_get_child(const struct trt_tree_ctx *tc) +{ + if (!tro_tree_ctx_get_node(tc)) { + return NULL; + } + + if (tc->lysc_tree) { + return lysc_node_child(tc->cn); + } else { + return lysp_node_child(tc->pn); + } +} + +/** + * @brief Get rpcs section if exists. + * @param[in,out] tc is tree context. + * @return Section representation if it exists. The @p tc is modified + * and his pointer points to the first node in rpcs section. + * @return Empty section representation otherwise. + */ +static struct trt_keyword_stmt +tro_modi_get_rpcs(struct trt_tree_ctx *tc) +{ + assert(tc); + const void *actions; + struct trt_keyword_stmt ret = {0}; + + if (tc->lysc_tree) { + actions = tc->cmod->rpcs; + if (actions) { + tc->cn = actions; + } + } else { + actions = tc->pmod->rpcs; + if (actions) { + tc->pn = actions; + tc->tpn = tc->pn; + } + } + + if (actions) { + tc->section = TRD_SECT_RPCS; + ret.section_name = TRD_KEYWORD_RPC; + ret.has_node = tro_tree_ctx_get_node(tc) ? 1 : 0; + } + + return ret; +} + +/** + * @brief Get notification section if exists + * @param[in,out] tc is tree context. + * @return Section representation if it exists. + * The @p tc is modified and his pointer points to the + * first node in notification section. + * @return Empty section representation otherwise. + */ +static struct trt_keyword_stmt +tro_modi_get_notifications(struct trt_tree_ctx *tc) +{ + assert(tc); + const void *notifs; + struct trt_keyword_stmt ret = {0}; + + if (tc->lysc_tree) { + notifs = tc->cmod->notifs; + if (notifs) { + tc->cn = notifs; + } + } else { + notifs = tc->pmod->notifs; + if (notifs) { + tc->pn = notifs; + tc->tpn = tc->pn; + } + } + + if (notifs) { + tc->section = TRD_SECT_NOTIF; + ret.section_name = TRD_KEYWORD_NOTIF; + ret.has_node = tro_tree_ctx_get_node(tc) ? 1 : 0; + } + + return ret; +} + +static struct trt_keyword_stmt +tro_get_ext_section(struct trt_tree_ctx *tc, void *ext, struct lyspr_tree_ctx *plug_ctx) +{ + struct trt_keyword_stmt ret = {0}; + struct lysc_ext_instance *ce = NULL; + struct lysp_ext_instance *pe = NULL; + + if (tc->lysc_tree) { + ce = ext; + ret.section_name = ce->def->name; + ret.argument = ce->argument; + ret.has_node = plug_ctx->schemas->ctree ? 1 : 0; + } else { + pe = ext; + ret.section_name = pe->def->name; + ret.argument = pe->argument; + ret.has_node = plug_ctx->schemas->ptree ? 1 : 0; + } + + return ret; +} + +/** + * @brief Get name of the module. + * @param[in] tc is context of the tree. + */ +static struct trt_keyword_stmt +tro_read_module_name(const struct trt_tree_ctx *tc) +{ + assert(tc); + struct trt_keyword_stmt ret; + + ret.section_name = !tc->lysc_tree && tc->pmod->is_submod ? + TRD_KEYWORD_SUBMODULE : + TRD_KEYWORD_MODULE; + + ret.argument = !tc->lysc_tree ? + LYSP_MODULE_NAME(tc->pmod) : + tc->cmod->mod->name; + + ret.has_node = tro_tree_ctx_get_node(tc) ? 1 : 0; + + return ret; +} + +static ly_bool +tro_read_if_sibling_exists(const struct trt_tree_ctx *tc) +{ + const void *parent; + + if (tc->lysc_tree) { + parent = troc_parent(tc->cn); + } else { + parent = trop_parent(tc->pn); + } + + return parent ? 1 : 0; +} + +/** + * @brief Create implicit "case" node as parent of @p node. + * @param[in] node child of implicit case node. + * @param[out] case_node created case node. + */ +static void +tro_create_implicit_case_node(const struct trt_node *node, struct trt_node *case_node) +{ + case_node->status = node->status; + case_node->flags = TRD_FLAGS_TYPE_EMPTY; + case_node->name.type = TRD_NODE_CASE; + case_node->name.keys = node->name.keys; + case_node->name.module_prefix = node->name.module_prefix; + case_node->name.str = node->name.str; + case_node->name.opts = node->name.opts; + case_node->name.add_opts = node->name.add_opts; + case_node->type = TRP_EMPTY_TRT_TYPE; + case_node->iffeatures = TRP_EMPTY_TRT_IFFEATURES; + case_node->last_one = node->last_one; +} + +/********************************************************************** + * Definition of trop reading functions + *********************************************************************/ + +/** + * @brief Check if list statement has keys. + * @param[in] pn is pointer to the list. + * @return 1 if has keys, otherwise 0. + */ +static ly_bool +trop_list_has_keys(const struct lysp_node *pn) +{ + return trg_charptr_has_data(((const struct lysp_node_list *)pn)->key); +} + +/** + * @brief Check if it contains at least one feature. + * @param[in] pn is current node. + * @return 1 if has if-features, otherwise 0. + */ +static ly_bool +trop_node_has_iffeature(const struct lysp_node *pn) +{ + LY_ARRAY_COUNT_TYPE u; + const struct lysp_qname *iffs; + + ly_bool ret = 0; + + iffs = pn->iffeatures; + LY_ARRAY_FOR(iffs, u) { + ret = 1; + break; + } + return ret; +} + +/** + * @brief Find out if leaf is also the key in last list. + * @param[in] pn is pointer to leaf. + * @param[in] ca_last_list is pointer to last visited list. + * Obtained from trt_parent_cache. + * @return 1 if leaf is also the key, otherwise 0. + */ +static ly_bool +trop_leaf_is_key(const struct lysp_node *pn, const struct lysp_node_list *ca_last_list) +{ + const struct lysp_node_leaf *leaf = (const struct lysp_node_leaf *)pn; + const struct lysp_node_list *list = ca_last_list; + + if (!list) { + return 0; + } + return trg_charptr_has_data(list->key) ? + trg_word_is_present(list->key, leaf->name, ' ') : 0; +} + +/** + * @brief Check if container's type is presence. + * @param[in] pn is pointer to container. + * @return 1 if container has presence statement, otherwise 0. + */ +static ly_bool +trop_container_has_presence(const struct lysp_node *pn) +{ + return trg_charptr_has_data(((struct lysp_node_container *)pn)->presence); +} + +/** + * @brief Get leaflist's path without lysp_node type control. + * @param[in] pn is pointer to the leaflist. + */ +static const char * +trop_leaflist_refpath(const struct lysp_node *pn) +{ + const struct lysp_node_leaflist *list = (const struct lysp_node_leaflist *)pn; + + return list->type.path ? list->type.path->expr : NULL; +} + +/** + * @brief Get leaflist's type name without lysp_node type control. + * @param[in] pn is pointer to the leaflist. + */ +static const char * +trop_leaflist_type_name(const struct lysp_node *pn) +{ + const struct lysp_node_leaflist *list = (const struct lysp_node_leaflist *)pn; + + return list->type.name; +} + +/** + * @brief Get leaf's path without lysp_node type control. + * @param[in] pn is pointer to the leaf node. + */ +static const char * +trop_leaf_refpath(const struct lysp_node *pn) +{ + const struct lysp_node_leaf *leaf = (const struct lysp_node_leaf *)pn; + + return leaf->type.path ? leaf->type.path->expr : NULL; +} + +/** + * @brief Get leaf's type name without lysp_node type control. + * @param[in] pn is pointer to the leaf's type name. + */ +static const char * +trop_leaf_type_name(const struct lysp_node *pn) +{ + const struct lysp_node_leaf *leaf = (const struct lysp_node_leaf *)pn; + + return leaf->type.name; +} + +/** + * @brief Get pointer to data using node type specification + * and getter function. + * + * @param[in] flags is node type specification. + * If it is the correct node, the getter function is called. + * @param[in] f is getter function which provides the desired + * char pointer from the structure. + * @param[in] pn pointer to node. + * @return NULL if node has wrong type or getter function return + * pointer to NULL. + * @return Pointer to desired char pointer obtained from the node. + */ +static const char * +trop_node_charptr(uint16_t flags, trt_get_charptr_func f, const struct lysp_node *pn) +{ + if (pn->nodetype & flags) { + const char *ret = f(pn); + + return trg_charptr_has_data(ret) ? ret : NULL; + } else { + return NULL; + } +} + +/** + * @brief Resolve \ of the current node. + * @param[in] nodetype is node's type obtained from the tree. + * @param[in] flags is node's flags obtained from the tree. + * @param[in] ca_lys_status is inherited status obtained from trt_parent_cache. + * @return The status type. + */ +static char * +trop_resolve_status(uint16_t nodetype, uint16_t flags, uint16_t ca_lys_status) +{ + if (nodetype & (LYS_INPUT | LYS_OUTPUT)) { + /* LYS_INPUT and LYS_OUTPUT is special case */ + return tro_flags2status(ca_lys_status); + /* if ancestor's status is deprc or obslt + * and also node's status is not set + */ + } else if ((ca_lys_status & (LYS_STATUS_DEPRC | LYS_STATUS_OBSLT)) && !(flags & (LYS_STATUS_CURR | LYS_STATUS_DEPRC | LYS_STATUS_OBSLT))) { + /* get ancestor's status */ + return tro_flags2status(ca_lys_status); + } else { + /* else get node's status */ + return tro_flags2status(flags); + } +} + +/** + * @brief Resolve \ of the current node. + * @param[in] nodetype is node's type obtained from the tree. + * @param[in] flags is node's flags obtained from the tree. + * @param[in] ca_ancestor is ancestor type obtained from trt_parent_cache. + * @param[in] ca_lys_config is inherited config item obtained from trt_parent_cache. + * @param[in] no Override structure for flags. + * @return The flags type. + */ +static const char * +trop_resolve_flags(uint16_t nodetype, uint16_t flags, trt_ancestor_type ca_ancestor, uint16_t ca_lys_config, + struct lyplg_ext_sprinter_tree_node_override *no) +{ + if (no && no->flags) { + return no->flags; + } else if ((nodetype & LYS_INPUT) || (ca_ancestor == TRD_ANCESTOR_RPC_INPUT)) { + return TRD_FLAGS_TYPE_RPC_INPUT_PARAMS; + } else if ((nodetype & LYS_OUTPUT) || (ca_ancestor == TRD_ANCESTOR_RPC_OUTPUT)) { + return TRD_FLAGS_TYPE_RO; + } else if (ca_ancestor == TRD_ANCESTOR_NOTIF) { + return TRD_FLAGS_TYPE_RO; + } else if (nodetype & LYS_NOTIF) { + return TRD_FLAGS_TYPE_NOTIF; + } else if (nodetype & LYS_USES) { + return TRD_FLAGS_TYPE_USES_OF_GROUPING; + } else if (nodetype & (LYS_RPC | LYS_ACTION)) { + return TRD_FLAGS_TYPE_RPC; + } else if (!(flags & (LYS_CONFIG_R | LYS_CONFIG_W))) { + /* config is not set. Look at ancestor's config */ + return tro_flags2config(ca_lys_config); + } else { + return tro_flags2config(flags); + } +} + +/** + * @brief Resolve node type of the current node. + * @param[in] pn is pointer to the current node in the tree. + * @param[in] ca_last_list is pointer to the last visited list. Obtained from the trt_parent_cache. + * @param[out] type Resolved type of node. + * @param[out] opts Resolved opts of node. + */ +static void +trop_resolve_node_opts(const struct lysp_node *pn, const struct lysp_node_list *ca_last_list, trt_node_type *type, + const char **opts) +{ + if (pn->nodetype & (LYS_INPUT | LYS_OUTPUT)) { + *type = TRD_NODE_ELSE; + } else if (pn->nodetype & LYS_CASE) { + *type = TRD_NODE_CASE; + } else if ((pn->nodetype & LYS_CHOICE) && !(pn->flags & LYS_MAND_TRUE)) { + *type = TRD_NODE_CHOICE; + *opts = TRD_NODE_OPTIONAL; + } else if (pn->nodetype & LYS_CHOICE) { + *type = TRD_NODE_CHOICE; + } else if ((pn->nodetype & LYS_CONTAINER) && (trop_container_has_presence(pn))) { + *opts = TRD_NODE_CONTAINER; + } else if (pn->nodetype & (LYS_LIST | LYS_LEAFLIST)) { + *opts = TRD_NODE_LISTLEAFLIST; + } else if ((pn->nodetype & (LYS_ANYDATA | LYS_ANYXML)) && !(pn->flags & LYS_MAND_TRUE)) { + *opts = TRD_NODE_OPTIONAL; + } else if ((pn->nodetype & LYS_LEAF) && !(pn->flags & LYS_MAND_TRUE) && (!trop_leaf_is_key(pn, ca_last_list))) { + *opts = TRD_NODE_OPTIONAL; + } else { + *type = TRD_NODE_ELSE; + } +} + +/** + * @brief Resolve \ of the current node. + * @param[in] pn is current node. + * @return Resolved type. + */ +static struct trt_type +trop_resolve_type(const struct lysp_node *pn) +{ + const char *tmp = NULL; + + if (!pn) { + return TRP_EMPTY_TRT_TYPE; + } else if ((tmp = trop_node_charptr(LYS_LEAFLIST, trop_leaflist_refpath, pn))) { + return TRP_INIT_TRT_TYPE(TRD_TYPE_TARGET, tmp); + } else if ((tmp = trop_node_charptr(LYS_LEAFLIST, trop_leaflist_type_name, pn))) { + return TRP_INIT_TRT_TYPE(TRD_TYPE_NAME, tmp); + } else if ((tmp = trop_node_charptr(LYS_LEAF, trop_leaf_refpath, pn))) { + return TRP_INIT_TRT_TYPE(TRD_TYPE_TARGET, tmp); + } else if ((tmp = trop_node_charptr(LYS_LEAF, trop_leaf_type_name, pn))) { + return TRP_INIT_TRT_TYPE(TRD_TYPE_NAME, tmp); + } else if (pn->nodetype == LYS_ANYDATA) { + return TRP_INIT_TRT_TYPE(TRD_TYPE_NAME, "anydata"); + } else if (pn->nodetype & LYS_ANYXML) { + return TRP_INIT_TRT_TYPE(TRD_TYPE_NAME, "anyxml"); + } else { + return TRP_EMPTY_TRT_TYPE; + } +} + +/** + * @brief Resolve iffeatures. + * + * @param[in] pn is current parsed node. + * @return Resolved iffeatures. + */ +static struct trt_iffeatures +trop_resolve_iffeatures(const struct lysp_node *pn) +{ + struct trt_iffeatures iff; + + if (pn && trop_node_has_iffeature(pn)) { + iff.type = TRD_IFF_PRESENT; + iff.str = NULL; + } else { + iff.type = TRD_IFF_NON_PRESENT; + iff.str = NULL; + } + + return iff; +} + +/** + * @brief Transformation of current lysp_node to struct trt_node. + * @param[in] ca contains stored important data + * when browsing the tree downwards. + * @param[in] tc is context of the tree. + */ +static struct trt_node +trop_read_node(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + const struct lysp_node *pn; + struct trt_node ret; + struct lyplg_ext_sprinter_tree_node_override *no; + + assert(tc && tc->pn && tc->pn->nodetype != LYS_UNKNOWN); + + no = tro_set_node_overr(tc->lysc_tree, tc->pn, 1, &tc->plugin_ctx); + + pn = tc->pn; + ret = TRP_EMPTY_NODE; + + /* */ + ret.status = trop_resolve_status(pn->nodetype, pn->flags, ca.lys_status); + + /* */ + ret.flags = trop_resolve_flags(pn->nodetype, pn->flags, ca.ancestor, ca.lys_config, no); + + /* set type of the node */ + trop_resolve_node_opts(pn, ca.last_list, &ret.name.type, &ret.name.opts); + ret.name.add_opts = no && no->add_opts ? no->add_opts : NULL; + ret.name.keys = (tc->pn->nodetype & LYS_LIST) && trop_list_has_keys(tc->pn); + + /* The parsed tree is not compiled, so no node can be augmented + * from another module. This means that nodes from the parsed tree + * will never have the prefix. + */ + ret.name.module_prefix = NULL; + + /* set node's name */ + ret.name.str = pn->name; + + /* */ + ret.type = trop_resolve_type(pn); + + /* */ + ret.iffeatures = trop_resolve_iffeatures(pn); + + ret.last_one = !tro_next_sibling(pn, tc); + + return ret; +} + +/** + * @brief Find out if the current node has siblings. + * @param[in] tc is context of the tree. + * @return 1 if sibling exists otherwise 0. + */ +static ly_bool +trop_read_if_sibling_exists(const struct trt_tree_ctx *tc) +{ + return tro_next_sibling(tc->pn, tc) != NULL; +} + +/********************************************************************** + * Modify trop getters + *********************************************************************/ + +/** + * @brief Change current node pointer to its parent + * but only if parent exists. + * @param[in,out] tc is tree context. + * Contains pointer to the current node. + * @return 1 if the node had parents and the change was successful. + * @return 0 if the node did not have parents. + * The pointer to the current node did not change. + */ +static ly_bool +trop_modi_parent(struct trt_tree_ctx *tc) +{ + assert(tc && tc->pn); + /* If no parent exists, stay in actual node. */ + if ((tc->pn != tc->tpn) && (tc->pn->parent)) { + tc->pn = tc->pn->parent; + return 1; + } else { + return 0; + } +} + +/** + * @brief Change the current node pointer to its child + * but only if exists. + * @param[in] ca contains inherited data from ancestors. + * @param[in,out] tc is context of the tree. + * Contains pointer to the current node. + * @return Non-empty \ representation of the current + * node's child. The @p tc is modified. + * @return Empty \ representation if child don't exists. + * The @p tc is not modified. + */ +static struct trt_node +trop_modi_next_child(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + const struct lysp_node *tmp; + + assert(tc && tc->pn); + + if ((tmp = tro_next_child(tc->pn, tc))) { + tc->pn = tmp; + return trop_read_node(ca, tc); + } else { + return TRP_EMPTY_NODE; + } +} + +/** + * @brief Change the pointer to the current node to its next sibling + * only if exists. + * @param[in] ca contains inherited data from ancestors. + * @param[in,out] tc is tree context. + * Contains pointer to the current node. + * @return Non-empty \ representation if sibling exists. + * The @p tc is modified. + * @return Empty \ representation otherwise. + * The @p tc is not modified. + */ +static struct trt_node +trop_modi_next_sibling(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + const struct lysp_node *pn; + + assert(tc && tc->pn); + + pn = tro_next_sibling(tc->pn, tc); + + if (pn) { + if ((tc->tpn == tc->pn) && (tc->section != TRD_SECT_PLUG_DATA)) { + tc->tpn = pn; + } + tc->pn = pn; + return trop_read_node(ca, tc); + } else { + return TRP_EMPTY_NODE; + } +} + +/** + * @brief Change the current node pointer to the first child of node's + * parent. If current node is already first sibling/child then nothing + * will change. + * @param[in] ca Settings of parent. + * @param[in,out] tc is tree context. + * @return node for printing. + */ +static struct trt_node +trop_modi_first_sibling(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + struct trt_node node; + + assert(tc && tc->pn); + + if (trop_modi_parent(tc)) { + node = trop_modi_next_child(ca, tc); + } else if (tc->plugin_ctx.schema) { + tc->pn = tc->plugin_ctx.schema->ptree; + tc->tpn = tc->pn; + node = trop_read_node(ca, tc); + } else { + /* current node is top-node */ + switch (tc->section) { + case TRD_SECT_MODULE: + tc->pn = tc->pmod->data; + tc->tpn = tc->pn; + break; + case TRD_SECT_AUGMENT: + tc->pn = (const struct lysp_node *)tc->pmod->augments; + tc->tpn = tc->pn; + break; + case TRD_SECT_RPCS: + tc->pn = (const struct lysp_node *)tc->pmod->rpcs; + tc->tpn = tc->pn; + break; + case TRD_SECT_NOTIF: + tc->pn = (const struct lysp_node *)tc->pmod->notifs; + tc->tpn = tc->pn; + break; + case TRD_SECT_GROUPING: + tc->pn = (const struct lysp_node *)tc->pmod->groupings; + tc->tpn = tc->pn; + break; + case TRD_SECT_PLUG_DATA: + /* Nothing to do. */ + break; + default: + assert(0); + } + node = trop_read_node(ca, tc); + } + + if (tc->plugin_ctx.filtered) { + node = trop_modi_next_sibling(ca, tc); + } + + return node; +} + +/** + * @brief Get next (or first) augment section if exists. + * @param[in,out] tc is tree context. It is modified and his current + * node is set to the lysp_node_augment. + * @return Section's representation if (next augment) section exists. + * @return Empty section structure otherwise. + */ +static struct trt_keyword_stmt +trop_modi_next_augment(struct trt_tree_ctx *tc) +{ + assert(tc); + const struct lysp_node_augment *augs; + struct trt_keyword_stmt ret = {0}; + + /* if next_augment func was called for the first time */ + if (tc->section != TRD_SECT_AUGMENT) { + tc->section = TRD_SECT_AUGMENT; + augs = tc->pmod->augments; + } else { + /* get augment sibling from top-node pointer */ + augs = (const struct lysp_node_augment *)tc->tpn->next; + } + + if (augs) { + tc->pn = &augs->node; + tc->tpn = tc->pn; + ret.section_name = TRD_KEYWORD_AUGMENT; + ret.argument = augs->nodeid; + ret.has_node = tro_tree_ctx_get_node(tc) ? 1 : 0; + } + + return ret; +} + +/** + * @brief Get next (or first) grouping section if exists + * @param[in,out] tc is tree context. It is modified and his current + * node is set to the lysp_node_grp. + * @return The next (or first) section representation if it exists. + * @return Empty section representation otherwise. + */ +static struct trt_keyword_stmt +trop_modi_next_grouping(struct trt_tree_ctx *tc) +{ + assert(tc); + const struct lysp_node_grp *grps; + struct trt_keyword_stmt ret = {0}; + + if (tc->section != TRD_SECT_GROUPING) { + tc->section = TRD_SECT_GROUPING; + grps = tc->pmod->groupings; + } else { + grps = (const struct lysp_node_grp *)tc->tpn->next; + } + + if (grps) { + tc->pn = &grps->node; + tc->tpn = tc->pn; + ret.section_name = TRD_KEYWORD_GROUPING; + ret.argument = grps->name; + ret.has_node = tro_tree_ctx_get_child(tc) ? 1 : 0; + } + + return ret; +} + +/********************************************************************** + * Definition of troc reading functions + *********************************************************************/ + +/** + * @copydoc trop_read_if_sibling_exists + */ +static ly_bool +troc_read_if_sibling_exists(const struct trt_tree_ctx *tc) +{ + return tro_next_sibling(tc->cn, tc) != NULL; +} + +/** + * @brief Resolve \ of the current node. + * + * Use this function only if trt_tree_ctx.lysc_tree is true. + * + * @param[in] nodetype is current lysc_node.nodetype. + * @param[in] flags is current lysc_node.flags. + * @param[in] no Override structure for flags. + * @return The flags type. + */ +static const char * +troc_resolve_flags(uint16_t nodetype, uint16_t flags, struct lyplg_ext_sprinter_tree_node_override *no) +{ + if (no && no->flags) { + return no->flags; + } else if ((nodetype & LYS_INPUT) || (flags & LYS_IS_INPUT)) { + return TRD_FLAGS_TYPE_RPC_INPUT_PARAMS; + } else if ((nodetype & LYS_OUTPUT) || (flags & LYS_IS_OUTPUT)) { + return TRD_FLAGS_TYPE_RO; + } else if (nodetype & LYS_IS_NOTIF) { + return TRD_FLAGS_TYPE_RO; + } else if (nodetype & LYS_NOTIF) { + return TRD_FLAGS_TYPE_NOTIF; + } else if (nodetype & LYS_USES) { + return TRD_FLAGS_TYPE_USES_OF_GROUPING; + } else if (nodetype & (LYS_RPC | LYS_ACTION)) { + return TRD_FLAGS_TYPE_RPC; + } else { + return tro_flags2config(flags); + } +} + +/** + * @brief Resolve node type of the current node. + * + * Use this function only if trt_tree_ctx.lysc_tree is true. + * + * @param[in] nodetype is current lysc_node.nodetype. + * @param[in] flags is current lysc_node.flags. + * @param[out] type Resolved type of node. + * @param[out] opts Resolved opts. + */ +static void +troc_resolve_node_opts(uint16_t nodetype, uint16_t flags, trt_node_type *type, const char **opts) +{ + if (nodetype & (LYS_INPUT | LYS_OUTPUT)) { + *type = TRD_NODE_ELSE; + } else if (nodetype & LYS_CASE) { + *type = TRD_NODE_CASE; + } else if ((nodetype & LYS_CHOICE) && !(flags & LYS_MAND_TRUE)) { + *type = TRD_NODE_CHOICE; + *opts = TRD_NODE_OPTIONAL; + } else if (nodetype & LYS_CHOICE) { + *type = TRD_NODE_CHOICE; + } else if ((nodetype & LYS_CONTAINER) && (flags & LYS_PRESENCE)) { + *opts = TRD_NODE_CONTAINER; + } else if (nodetype & (LYS_LIST | LYS_LEAFLIST)) { + *opts = TRD_NODE_LISTLEAFLIST; + } else if ((nodetype & (LYS_ANYDATA | LYS_ANYXML)) && !(flags & LYS_MAND_TRUE)) { + *opts = TRD_NODE_OPTIONAL; + } else if ((nodetype & LYS_LEAF) && !(flags & (LYS_MAND_TRUE | LYS_KEY))) { + *opts = TRD_NODE_OPTIONAL; + } else { + *type = TRD_NODE_ELSE; + } +} + +/** + * @brief Resolve prefix (\:\) of node that has been + * placed from another module via an augment statement. + * + * @param[in] cn is current compiled node. + * @param[in] current_compiled_module is module whose nodes are + * currently being printed. + * @return Prefix of foreign module or NULL. + */ +static const char * +troc_resolve_node_prefix(const struct lysc_node *cn, const struct lysc_module *current_compiled_module) +{ + const struct lys_module *node_module; + const char *ret = NULL; + + node_module = cn->module; + if (!node_module || !current_compiled_module) { + return NULL; + } else if (node_module->compiled != current_compiled_module) { + ret = node_module->prefix; + } + + return ret; +} + +/** + * @brief Transformation of current lysc_node to struct trt_node. + * @param[in] ca is not used. + * @param[in] tc is context of the tree. + */ +static struct trt_node +troc_read_node(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + (void) ca; + const struct lysc_node *cn; + struct trt_node ret; + struct lyplg_ext_sprinter_tree_node_override *no; + + assert(tc && tc->cn); + + no = tro_set_node_overr(tc->lysc_tree, tc->cn, 1, &tc->plugin_ctx); + + cn = tc->cn; + ret = TRP_EMPTY_NODE; + + /* */ + ret.status = tro_flags2status(cn->flags); + + /* */ + ret.flags = troc_resolve_flags(cn->nodetype, cn->flags, no); + + /* set type of the node */ + troc_resolve_node_opts(cn->nodetype, cn->flags, &ret.name.type, &ret.name.opts); + ret.name.add_opts = no && no->add_opts ? no->add_opts : NULL; + ret.name.keys = (cn->nodetype & LYS_LIST) && !(cn->flags & LYS_KEYLESS); + + /* */ + ret.name.module_prefix = troc_resolve_node_prefix(cn, tc->cmod); + + /* set node's name */ + ret.name.str = cn->name; + + /* */ + ret.type = trop_resolve_type(TRP_TREE_CTX_GET_LYSP_NODE(cn)); + + /* */ + ret.iffeatures = trop_resolve_iffeatures(TRP_TREE_CTX_GET_LYSP_NODE(cn)); + + ret.last_one = !tro_next_sibling(cn, tc); + + return ret; +} + +/********************************************************************** + * Modify troc getters + *********************************************************************/ + +/** + * @copydoc ::trop_modi_parent() + */ +static ly_bool +troc_modi_parent(struct trt_tree_ctx *tc) +{ + assert(tc && tc->cn); + /* If no parent exists, stay in actual node. */ + if (tc->cn->parent) { + tc->cn = tc->cn->parent; + return 1; + } else { + return 0; + } +} + +/** + * @copydoc ::trop_modi_next_sibling() + */ +static struct trt_node +troc_modi_next_sibling(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + const struct lysc_node *cn; + + assert(tc && tc->cn); + + cn = tro_next_sibling(tc->cn, tc); + + /* if next sibling exists */ + if (cn) { + /* update trt_tree_ctx */ + tc->cn = cn; + return troc_read_node(ca, tc); + } else { + return TRP_EMPTY_NODE; + } +} + +/** + * @copydoc trop_modi_next_child() + */ +static struct trt_node +troc_modi_next_child(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + const struct lysc_node *tmp; + + assert(tc && tc->cn); + + if ((tmp = tro_next_child(tc->cn, tc))) { + tc->cn = tmp; + return troc_read_node(ca, tc); + } else { + return TRP_EMPTY_NODE; + } +} + +/** + * @copydoc ::trop_modi_first_sibling() + */ +static struct trt_node +troc_modi_first_sibling(struct trt_parent_cache ca, struct trt_tree_ctx *tc) +{ + struct trt_node node; + + assert(tc && tc->cn); + + if (troc_modi_parent(tc)) { + node = troc_modi_next_child(ca, tc); + } else if (tc->plugin_ctx.schema) { + tc->cn = tc->plugin_ctx.schema->ctree; + node = troc_read_node(ca, tc); + } else { + /* current node is top-node */ + switch (tc->section) { + case TRD_SECT_MODULE: + tc->cn = tc->cmod->data; + break; + case TRD_SECT_RPCS: + tc->cn = (const struct lysc_node *)tc->cmod->rpcs; + break; + case TRD_SECT_NOTIF: + tc->cn = (const struct lysc_node *)tc->cmod->notifs; + break; + case TRD_SECT_PLUG_DATA: + /* nothing to do */ + break; + default: + assert(0); + } + node = troc_read_node(ca, tc); + } + + if (tc->plugin_ctx.filtered) { + node = troc_modi_next_sibling(ca, tc); + } + + return node; +} + +/********************************************************************** + * Definition of tree browsing functions + *********************************************************************/ + +static uint32_t +trb_gap_to_opts(const struct trt_node *node) +{ + uint32_t len = 0; + + if (node->name.keys) { + return 0; + } + + if (node->flags) { + len += strlen(node->flags); + /* space between flags and name */ + len += 1; + } else { + /* space between -- and name */ + len += 1; + } + + switch (node->name.type) { + case TRD_NODE_CASE: + /* ':' is already counted. Plus parentheses. */ + len += 2; + break; + case TRD_NODE_CHOICE: + /* Plus parentheses. */ + len += 2; + break; + default: + break; + } + + if (node->name.module_prefix) { + len += strlen(node->name.module_prefix); + } + if (node->name.str) { + len += strlen(node->name.str); + } + if (node->name.add_opts) { + len += strlen(node->name.add_opts); + } + if (node->name.opts) { + len += strlen(node->name.opts); + } + + return len; +} + +static uint32_t +trb_gap_to_type(const struct trt_node *node) +{ + uint32_t len, opts_len; + + if (node->name.keys) { + return 0; + } + + len = trb_gap_to_opts(node); + /* Gap between opts and type. */ + opts_len = 0; + opts_len += node->name.add_opts ? strlen(node->name.add_opts) : 0; + opts_len += node->name.opts ? strlen(node->name.opts) : 0; + if (opts_len >= TRD_INDENT_BEFORE_TYPE) { + /* At least one space should be there. */ + len += 1; + } else if (node->name.add_opts || node->name.opts) { + len += TRD_INDENT_BEFORE_TYPE - opts_len; + } else { + len += TRD_INDENT_BEFORE_TYPE; + } + + return len; +} + +/** + * @brief Calculate the trt_indent_in_node.btw_opts_type indent size + * for a particular node. + * @param[in] node for which we get btw_opts_type. + * @param[in] max_gap_before_type is the maximum value of btw_opts_type + * that it can have. + * @return Indent between \ and \ for node. + */ +static int16_t +trb_calc_btw_opts_type(const struct trt_node *node, int16_t max_gap_before_type) +{ + uint32_t to_opts_len; + + to_opts_len = trb_gap_to_opts(node); + if (to_opts_len == 0) { + return 1; + } else { + return max_gap_before_type - to_opts_len; + } +} + +/** + * @brief Print node. + * + * This function is wrapper for ::trp_print_entire_node(). + * But difference is that take @p max_gap_before_type which will be + * used to set the unified alignment. + * + * @param[in] node to print. + * @param[in] max_gap_before_type is number of indent before \. + * @param[in] wr is wrapper for printing indentation before node. + * @param[in] pc contains mainly functions for printing. + * @param[in] tc is tree context. + */ +static void +trb_print_entire_node(const struct trt_node *node, uint32_t max_gap_before_type, struct trt_wrapper wr, + struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + struct trt_indent_in_node ind = trp_default_indent_in_node(node); + + if ((max_gap_before_type > 0) && (node->type.type != TRD_TYPE_EMPTY)) { + /* print actual node with unified indent */ + ind.btw_opts_type = trb_calc_btw_opts_type(node, max_gap_before_type); + } + /* after -> print actual node with default indent */ + trp_print_entire_node(node, TRP_INIT_PCK_PRINT(tc, pc->fp.print), + TRP_INIT_PCK_INDENT(wr, ind), pc->max_line_length, pc->out); +} + +/** + * @brief Check if parent of the current node is the last + * of his siblings. + * + * To mantain stability use this function only if the current node is + * the first of the siblings. + * Side-effect -> current node is set to the first sibling + * if node has a parent otherwise no side-effect. + * + * @param[in] fp contains all @ref TRP_tro callback functions. + * @param[in,out] tc is tree context. + * @return 1 if parent is last sibling otherwise 0. + */ +static ly_bool +trb_node_is_last_sibling(const struct trt_fp_all *fp, struct trt_tree_ctx *tc) +{ + if (fp->read.if_parent_exists(tc)) { + return !fp->read.if_sibling_exists(tc); + } else { + return !fp->read.if_sibling_exists(tc) && tc->plugin_ctx.last_schema; + } +} + +/** + * @brief For all siblings find maximal space from '--' to \. + * + * Side-effect -> Current node is set to the first sibling. + * + * @param[in] ca contains inherited data from ancestors. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is tree context. + * @return max space. + */ +static uint32_t +trb_max_gap_to_type(struct trt_parent_cache ca, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + struct trt_node node; + int32_t maxlen, len; + + maxlen = 0; + for (node = pc->fp.modify.first_sibling(ca, tc); + !trp_node_is_empty(&node); + node = pc->fp.modify.next_sibling(ca, tc)) { + len = trb_gap_to_type(&node); + maxlen = maxlen < len ? len : maxlen; + } + pc->fp.modify.first_sibling(ca, tc); + + return maxlen; +} + +/** + * @brief Find out if it is possible to unify + * the alignment before \. + * + * The goal is for all node siblings to have the same alignment + * for \ as if they were in a column. All siblings who cannot + * adapt because they do not fit on the line at all are ignored. + * Side-effect -> Current node is set to the first sibling. + * + * @param[in] ca contains inherited data from ancestors. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is tree context. + * @return positive number indicating the maximum number of spaces + * before \ if the length of the flags, node name and opts is 0. To calculate + * the trt_indent_in_node.btw_opts_type indent size for a particular + * node, use the ::trb_calc_btw_opts_type(). +*/ +static uint32_t +trb_try_unified_indent(struct trt_parent_cache ca, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + return trb_max_gap_to_type(ca, pc, tc); +} + +/** + * @brief Check if there is no case statement + * under the choice statement. + * + * It can return true only if the Parsed schema tree + * is used for browsing. + * + * @param[in] tc is tree context. + * @return 1 if implicit case statement is present otherwise 0. + */ +static ly_bool +trb_need_implicit_node_case(struct trt_tree_ctx *tc) +{ + return !tc->lysc_tree && tc->pn->parent && + (tc->pn->parent->nodetype & LYS_CHOICE) && + (tc->pn->nodetype & (LYS_ANYDATA | LYS_CHOICE | LYS_CONTAINER | + LYS_LEAF | LYS_LEAFLIST)); +} + +static void trb_print_subtree_nodes(struct trt_node *node, uint32_t max_gap_before_type, + struct trt_wrapper wr, struct trt_parent_cache ca, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc); + +/** + * @brief Print implicit case node and his subtree. + * + * @param[in] node is child of implicit case. + * @param[in] wr is wrapper for printing identation before node. + * @param[in] pc contains mainly functions for printing. + * @param[in] tc is tree context. Its settings should be the same as + * before the function call. + * @return new indentation wrapper for @p node. + */ +static struct trt_wrapper +trb_print_implicit_node(const struct trt_node *node, struct trt_wrapper wr, struct trt_printer_ctx *pc, + struct trt_tree_ctx *tc) +{ + struct trt_node case_node; + struct trt_wrapper wr_case_child; + + tro_create_implicit_case_node(node, &case_node); + ly_print_(pc->out, "\n"); + trb_print_entire_node(&case_node, 0, wr, pc, tc); + ly_print_(pc->out, "\n"); + wr_case_child = pc->fp.read.if_sibling_exists(tc) ? + trp_wrapper_set_mark(wr) : trp_wrapper_set_shift(wr); + return wr_case_child; +} + +/** + * @brief Calculate the wrapper about how deep in the tree the node is. + * @param[in] wr_in A wrapper to use as a starting point + * @param[in] node from which to count. + * @return wrapper for @p node. + */ +static struct trt_wrapper +trb_count_depth(const struct trt_wrapper *wr_in, const struct lysc_node *node) +{ + struct trt_wrapper wr = wr_in ? *wr_in : TRP_INIT_WRAPPER_TOP; + const struct lysc_node *parent; + + if (!node) { + return wr; + } + + for (parent = node->parent; parent; parent = parent->parent) { + wr = trp_wrapper_set_shift(wr); + } + + return wr; +} + +/** + * @brief Print all parent nodes of @p node and the @p node itself. + * + * Side-effect -> trt_tree_ctx.cn will be set to @p node. + * + * @param[in] node on which the function is focused. + * @param[in] wr_in for printing identation before node. + * @param[in] pc is @ref TRP_trp settings. + * @param[in,out] tc is context of tree printer. + */ +static void +trb_print_parents(const struct lysc_node *node, struct trt_wrapper *wr_in, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + uint32_t max_gap_before_type; + struct trt_wrapper wr; + struct trt_node print_node; + + assert(pc && tc && tc->section == TRD_SECT_MODULE); + + /* stop recursion */ + if (!node) { + return; + } + trb_print_parents(node->parent, wr_in, pc, tc); + + /* setup for printing */ + tc->cn = node; + wr = trb_count_depth(wr_in, node); + + /* print node */ + ly_print_(pc->out, "\n"); + print_node = pc->fp.read.node(TRP_EMPTY_PARENT_CACHE, tc); + max_gap_before_type = trb_max_gap_to_type(TRP_EMPTY_PARENT_CACHE, pc, tc); + trb_print_entire_node(&print_node, max_gap_before_type, wr, pc, tc); +} + +/** + * @brief Set current node on its child. + * @param[in,out] tc contains current node. + */ +static void +trb_tree_ctx_set_child(struct trt_tree_ctx *tc) +{ + const void *node = tro_tree_ctx_get_child(tc); + + if (tc->lysc_tree) { + tc->cn = node; + } else { + tc->pn = node; + } +} + +/** + * @brief Move extension iterator to the next position. + * + * @param[in] lysc_tree flag if exts is from compiled tree. + * @param[in] exts is current array of extensions. + * @param[in,out] i is state of iterator. + * @return Pointer to the first/next extension. + */ +static void * +trb_ext_iter_next(ly_bool lysc_tree, void *exts, uint64_t *i) +{ + void *ext = NULL; + struct lysc_ext_instance *ce; + struct lysp_ext_instance *pe; + + if (!exts) { + return NULL; + } + + if (lysc_tree) { + ce = exts; + while (*i < LY_ARRAY_COUNT(ce)) { + if (ce->def->plugin && trp_ext_parent_is_valid(1, &ce[*i])) { + ext = &ce[*i]; + break; + } + ++(*i); + } + } else { + pe = exts; + while (*i < LY_ARRAY_COUNT(pe)) { + if (trp_ext_parent_is_valid(0, &pe[*i])) { + ext = &pe[*i]; + break; + } + ++(*i); + } + } + ++(*i); + + return ext; +} + +/** + * @brief Iterate over extensions in module. + * + * @param[in] tc contains current node. + * @param[in,out] i is state of iterator. + * @return First/next extension or NULL. + */ +static void * +trb_mod_ext_iter(const struct trt_tree_ctx *tc, uint64_t *i) +{ + if (tc->lysc_tree) { + return trb_ext_iter_next(1, tc->cmod->exts, i); + } else { + return trb_ext_iter_next(0, tc->pmod->exts, i); + } +} + +/** + * @brief Iterate over extensions in node. + * + * @param[in] tc contains current node. + * @param[in,out] i is state of iterator. + * @return First/next extension or NULL. + */ +static void * +trb_ext_iter(const struct trt_tree_ctx *tc, uint64_t *i) +{ + if (tc->lysc_tree) { + return trb_ext_iter_next(1, tc->cn->exts, i); + } else { + return trb_ext_iter_next(0, tc->pn->exts, i); + } +} + +/** + * @brief Initialize plugin context. + * + * @param[in] compiled if @p ext is lysc structure. + * @param[in] ext current processed extension. + * @param[out] plug_ctx is plugin context which will be initialized. + * @return LY_ERR value. + */ +static LY_ERR +tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx) +{ + struct lysc_ext_instance *ext_comp; + struct lysp_ext_instance *ext_pars; + const char *flags = NULL, *add_opts = NULL; + + if (compiled) { + ext_comp = ext; + return ext_comp->def->plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts); + } else { + ext_pars = ext; + return ext_pars->record->plugin.printer_ptree(ext, plug_ctx, &flags, &add_opts); + } +} + +/** + * @brief Reset tree context by plugin context. + * + * @param[in] plug_ctx is plugin context. + * @param[in] i which index in schemas should be used. + * @param[in] pc are printing functions. + * @param[out] tc tree context which will be updated. + */ +static void +trm_reset_tree_ctx_by_plugin(struct lyspr_tree_ctx *plug_ctx, LY_ARRAY_COUNT_TYPE i, struct trt_printer_ctx *pc, + struct trt_tree_ctx *tc) +{ + tc->plugin_ctx.ctx = plug_ctx; + tc->pmod = NULL; + tc->cmod = NULL; + if (plug_ctx->schemas[i].compiled) { + tc->lysc_tree = 1; + tc->cn = plug_ctx->schemas[i].ctree; + tc->plugin_ctx.schema = &plug_ctx->schemas[i]; + pc->fp.modify = TRP_TRT_FP_MODIFY_COMPILED; + pc->fp.read = TRP_TRT_FP_READ_COMPILED; + } else { + tc->lysc_tree = 0; + tc->pn = plug_ctx->schemas[i].ptree; + tc->tpn = tc->pn; + tc->plugin_ctx.schema = &plug_ctx->schemas[i]; + pc->fp.modify = TRP_TRT_FP_MODIFY_PARSED; + pc->fp.read = TRP_TRT_FP_READ_PARSED; + } +} + +/** + * @brief Print schemas from plugin context. + * + * @param[in] plug_ctx is plugin context. + * @param[in] last_nodes if this schemas will be the last. + * @param[in] max_gap_before_type is gap before type. + * @param[in] wr is indentation wrapper. + * @param[in] ca containing information from parent. + * @param[in] pc functions for tree traversing. + * @param[in] tc current tree context. + */ +static void +trb_ext_print_schemas(struct lyspr_tree_ctx *plug_ctx, ly_bool last_nodes, uint32_t max_gap_before_type, + struct trt_wrapper wr, struct trt_parent_cache ca, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + LY_ARRAY_COUNT_TYPE i; + struct trt_printer_ctx pc_dupl; + struct trt_tree_ctx tc_dupl; + struct trt_node node; + + tc_dupl = *tc; + pc_dupl = *pc; + + LY_ARRAY_FOR(plug_ctx->schemas, i) { + trm_reset_tree_ctx_by_plugin(plug_ctx, i, pc, tc); + tc->plugin_ctx.last_schema = last_nodes && ((i + 1) == LY_ARRAY_COUNT(plug_ctx->schemas)); + node = TRP_EMPTY_NODE; + trb_print_subtree_nodes(&node, max_gap_before_type, wr, ca, pc, tc); + *tc = tc_dupl; + } + + *pc = pc_dupl; +} + +/** + * @brief Count unified indentation across schemas from extension instance. + * + * @param[in] plug_ctx is plugin context. + * @param[in] ca containing parent settings. + * @param[out] max_gap_before_type is result of unified indent. + * @param[in] pc functions for tree traversing. + * @param[in] tc is tree context. + */ +static void +trb_ext_try_unified_indent(struct lyspr_tree_ctx *plug_ctx, struct trt_parent_cache ca, uint32_t *max_gap_before_type, + struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + LY_ARRAY_COUNT_TYPE i; + struct trt_printer_ctx pc_dupl; + struct trt_tree_ctx tc_dupl; + uint32_t max; + + tc_dupl = *tc; + pc_dupl = *pc; + + LY_ARRAY_FOR(plug_ctx->schemas, i) { + trm_reset_tree_ctx_by_plugin(plug_ctx, i, pc, tc); + max = trb_try_unified_indent(ca, pc, tc); + *max_gap_before_type = max > *max_gap_before_type ? max : *max_gap_before_type; + *tc = tc_dupl; + } + + *pc = pc_dupl; +} + +/** + * @brief For every extension instance print all schemas. + * + * @param[in] wr indentation wrapper for node. + * @param[in] ca parent settings. + * @param[in] pc function used for tree traversing. + * @param[in] tc tree context. + */ +static void +trb_ext_print_instances(struct trt_wrapper wr, struct trt_parent_cache ca, struct trt_printer_ctx *pc, + struct trt_tree_ctx *tc) +{ + LY_ERR rc; + LY_ARRAY_COUNT_TYPE i; + uint64_t last_instance = UINT64_MAX; + void *ext; + ly_bool child_exists; + uint32_t max, max_gap_before_type = 0; + + ca = tro_parent_cache_for_child(ca, tc); + /* if node is last sibling, then do not add '|' to wrapper */ + wr = trb_node_is_last_sibling(&pc->fp, tc) ? + trp_wrapper_set_shift(wr) : trp_wrapper_set_mark(wr); + + if (tc->lysc_tree) { + child_exists = tro_next_child(tc->cn, tc) ? 1 : 0; + } else { + child_exists = tro_next_child(tc->pn, tc) ? 1 : 0; + } + + i = 0; + while ((ext = trb_ext_iter(tc, &i))) { + struct lyspr_tree_ctx plug_ctx = {0}; + + rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx); + LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end); + trb_ext_try_unified_indent(&plug_ctx, ca, &max_gap_before_type, pc, tc); + if (plug_ctx.schemas) { + last_instance = i; + } + trp_ext_free_plugin_ctx(&plug_ctx); + } + + if (child_exists) { + pc->fp.modify.next_child(ca, tc); + max = trb_try_unified_indent(ca, pc, tc); + max_gap_before_type = max > max_gap_before_type ? max : max_gap_before_type; + pc->fp.modify.parent(tc); + } + + i = 0; + while ((ext = trb_ext_iter(tc, &i))) { + struct lyspr_tree_ctx plug_ctx = {0}; + + rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx); + LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end); + if (!child_exists && (last_instance == i)) { + trb_ext_print_schemas(&plug_ctx, 1, max_gap_before_type, wr, ca, pc, tc); + } else { + trb_ext_print_schemas(&plug_ctx, 0, max_gap_before_type, wr, ca, pc, tc); + } + trp_ext_free_plugin_ctx(&plug_ctx); + } + +end: + return; +} + +/** + * @brief Print subtree of nodes. + * + * The current node is expected to be the root of the subtree. + * Before root node is no linebreak printing. This must be addressed by + * the caller. Root node will also be printed. Behind last printed node + * is no linebreak. + * + * @param[in,out] node current processed node used as iterator. + * @param[in] max_gap_before_type is result from + * ::trb_try_unified_indent() function for root node. + * Set parameter to 0 if distance does not matter. + * @param[in] wr is wrapper saying how deep in the whole tree + * is the root of the subtree. + * @param[in] ca is parent_cache from root's parent. + * If root is top-level node, insert ::TRP_EMPTY_PARENT_CACHE. + * @param[in] pc is @ref TRP_trp settings. + * @param[in,out] tc is context of tree printer. + */ +static void +trb_print_subtree_nodes(struct trt_node *node, uint32_t max_gap_before_type, struct trt_wrapper wr, + struct trt_parent_cache ca, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + if (!trp_node_is_empty(node)) { + /* Print root node. */ + trb_print_entire_node(node, max_gap_before_type, wr, pc, tc); + if (trp_ext_is_present_in_node(tc)) { + trb_ext_print_instances(wr, ca, pc, tc); + } + /* if node is last sibling, then do not add '|' to wrapper */ + wr = trb_node_is_last_sibling(&pc->fp, tc) ? + trp_wrapper_set_shift(wr) : trp_wrapper_set_mark(wr); + /* go to the child */ + ca = tro_parent_cache_for_child(ca, tc); + *node = pc->fp.modify.next_child(ca, tc); + if (trp_node_is_empty(node)) { + return; + } + /* TODO comment browse through instances + filtered. try unified indentation for children */ + max_gap_before_type = trb_try_unified_indent(ca, pc, tc); + } else { + /* Root node is ignored, continue with child. */ + *node = pc->fp.modify.first_sibling(ca, tc); + } + + do { + if (!tc->plugin_ctx.filtered && !trb_need_implicit_node_case(tc)) { + /* normal behavior */ + ly_print_(pc->out, "\n"); + trb_print_subtree_nodes(node, max_gap_before_type, wr, ca, pc, tc); + } else if (!tc->plugin_ctx.filtered) { + struct trt_wrapper wr_case_child; + + wr_case_child = trb_print_implicit_node(node, wr, pc, tc); + trb_print_subtree_nodes(node, max_gap_before_type, wr_case_child, ca, pc, tc); + } + /* go to the actual node's sibling */ + *node = pc->fp.modify.next_sibling(ca, tc); + } while (!trp_node_is_empty(node)); + + /* get back from child node to root node */ + pc->fp.modify.parent(tc); +} + +/** + * @brief Print all parents and their children. + * + * This function is suitable for printing top-level nodes that + * do not have ancestors. Function call ::trb_print_subtree_nodes() + * for all top-level siblings. Use this function after 'module' keyword + * or 'augment' and so. The nodes may not be exactly top-level in the + * tree, but the function considers them that way. + * + * @param[in] wr is wrapper saying how deeply the top-level nodes are + * immersed in the tree. + * @param[pc] pc contains mainly functions for printing. + * @param[in,out] tc is tree context. + */ +static void +trb_print_family_tree(struct trt_wrapper wr, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + struct trt_parent_cache ca; + struct trt_node node; + uint32_t max_gap_before_type; + + if (!tro_tree_ctx_get_node(tc)) { + return; + } + + ca = TRP_EMPTY_PARENT_CACHE; + max_gap_before_type = trb_try_unified_indent(ca, pc, tc); + + if (!tc->lysc_tree) { + if ((tc->section == TRD_SECT_GROUPING) && (tc->tpn == tc->pn->parent)) { + ca.lys_config = 0x0; + } + } + + for (node = pc->fp.modify.first_sibling(ca, tc); + !trp_node_is_empty(&node); + node = pc->fp.modify.next_sibling(ca, tc)) { + ly_print_(pc->out, "\n"); + trb_print_subtree_nodes(&node, max_gap_before_type, wr, ca, pc, tc); + } +} + +/********************************************************************** + * Definition of trm main functions + *********************************************************************/ + +/** + * @brief Settings if lysp_node are used for browsing through the tree. + * + * @param[in] module YANG schema tree structure representing + * YANG module. + * @param[in] out is output handler. + * @param[in] max_line_length is the maximum line length limit + * that should not be exceeded. + * @param[in,out] pc will be adapted to lysp_tree. + * @param[in,out] tc will be adapted to lysp_tree. + */ +static void +trm_lysp_tree_ctx(const struct lys_module *module, struct ly_out *out, size_t max_line_length, + struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + *tc = (struct trt_tree_ctx) { + .lysc_tree = 0, + .section = TRD_SECT_MODULE, + .pmod = module->parsed, + .cmod = NULL, + .pn = module->parsed ? module->parsed->data : NULL, + .tpn = module->parsed ? module->parsed->data : NULL, + .cn = NULL, + .last_error = 0, + .plugin_ctx.ctx = NULL, + .plugin_ctx.schema = NULL, + .plugin_ctx.filtered = 0, + .plugin_ctx.node_overr = TRP_TREE_CTX_EMPTY_NODE_OVERR, + .plugin_ctx.last_schema = 1, + .plugin_ctx.last_error = 0 + }; + + pc->out = out; + + pc->fp.modify = TRP_TRT_FP_MODIFY_PARSED; + pc->fp.read = TRP_TRT_FP_READ_PARSED; + + pc->fp.print = (struct trt_fp_print) { + .print_features_names = tro_print_features_names, + .print_keys = tro_print_keys + }; + + pc->max_line_length = max_line_length; +} + +/** + * @brief Settings if lysc_node are used for browsing through the tree. + * + * Pointers to current nodes will be set to module data. + * + * @param[in] module YANG schema tree structure representing + * YANG module. + * @param[in] out is output handler. + * @param[in] max_line_length is the maximum line length limit + * that should not be exceeded. + * @param[in,out] pc will be adapted to lysc_tree. + * @param[in,out] tc will be adapted to lysc_tree. + */ +static void +trm_lysc_tree_ctx(const struct lys_module *module, struct ly_out *out, size_t max_line_length, + struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + *tc = (struct trt_tree_ctx) { + .lysc_tree = 1, + .section = TRD_SECT_MODULE, + .pmod = module->parsed, + .cmod = module->compiled, + .tpn = NULL, + .pn = NULL, + .cn = module->compiled->data, + .last_error = 0, + .plugin_ctx.ctx = NULL, + .plugin_ctx.schema = NULL, + .plugin_ctx.filtered = 0, + .plugin_ctx.node_overr = TRP_TREE_CTX_EMPTY_NODE_OVERR, + .plugin_ctx.last_schema = 1, + .plugin_ctx.last_error = 0 + }; + + pc->out = out; + + pc->fp.modify = TRP_TRT_FP_MODIFY_COMPILED; + pc->fp.read = TRP_TRT_FP_READ_COMPILED; + + pc->fp.print = (struct trt_fp_print) { + .print_features_names = tro_print_features_names, + .print_keys = tro_print_keys + }; + + pc->max_line_length = max_line_length; +} + +/** + * @brief Reset settings to browsing through the lysc tree. + * @param[in,out] pc resets to @ref TRP_troc functions. + * @param[in,out] tc resets to lysc browsing. + */ +static void +trm_reset_to_lysc_tree_ctx(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + LY_ERR erc; + + erc = tc->last_error; + trp_ext_free_node_override(&tc->plugin_ctx.node_overr, &tc->plugin_ctx.filtered); + trm_lysc_tree_ctx(tc->pmod->mod, pc->out, pc->max_line_length, pc, tc); + tc->last_error = erc; +} + +/** + * @brief Reset settings to browsing through the lysp tree. + * @param[in,out] pc resets to @ref TRP_trop functions. + * @param[in,out] tc resets to lysp browsing. + */ +static void +trm_reset_to_lysp_tree_ctx(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + LY_ERR erc; + + erc = tc->last_error; + trp_ext_free_node_override(&tc->plugin_ctx.node_overr, &tc->plugin_ctx.filtered); + trm_lysp_tree_ctx(tc->pmod->mod, pc->out, pc->max_line_length, pc, tc); + tc->last_error = erc; +} + +/** + * @brief If augment's target node is located on the current module. + * @param[in] pn is examined augment. + * @param[in] pmod is current module. + * @return 1 if nodeid refers to the local node, otherwise 0. + */ +static ly_bool +trm_nodeid_target_is_local(const struct lysp_node_augment *pn, const struct lysp_module *pmod) +{ + const char *id, *prefix, *name; + size_t prefix_len, name_len; + const struct lys_module *mod; + ly_bool ret = 0; + + if (pn == NULL) { + return ret; + } + + id = pn->nodeid; + if (!id) { + return ret; + } + /* only absolute-schema-nodeid is taken into account */ + assert(id[0] == '/'); + ++id; + + ly_parse_nodeid(&id, &prefix, &prefix_len, &name, &name_len); + if (prefix) { + mod = ly_resolve_prefix(pmod->mod->ctx, prefix, prefix_len, LY_VALUE_SCHEMA, pmod); + ret = mod ? (mod->parsed == pmod) : 0; + } else { + ret = 1; + } + + return ret; +} + +/** + * @brief Printing section module, rpcs, notifications or yang-data. + * + * First node must be the first child of 'module', + * 'rpcs', 'notifications' or 'yang-data'. + * + * @param[in] ks is section representation. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_section_as_family_tree(struct trt_keyword_stmt ks, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + assert(ks.section_name); + + trp_print_keyword_stmt(ks, pc->max_line_length, pc->out); + if (!strcmp(ks.section_name, TRD_KEYWORD_MODULE) || !strcmp(ks.section_name, TRD_KEYWORD_SUBMODULE)) { + trb_print_family_tree(TRP_INIT_WRAPPER_TOP, pc, tc); + } else { + trb_print_family_tree(TRP_INIT_WRAPPER_BODY, pc, tc); + } +} + +/** + * @brief Printing section augment or grouping. + * + * First node is 'augment' or 'grouping' itself. + * + * @param[in] ks is section representation. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_section_as_subtree(struct trt_keyword_stmt ks, struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + assert(ks.section_name); + trp_print_keyword_stmt(ks, pc->max_line_length, pc->out); + trb_tree_ctx_set_child(tc); + trb_print_family_tree(TRP_INIT_WRAPPER_BODY, pc, tc); +} + +/** + * @brief Print 'module' keyword, its name and all nodes. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_module_section(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + trm_print_section_as_family_tree(pc->fp.read.module_name(tc), pc, tc); +} + +/** + * @brief For all augment sections: print 'augment' keyword, + * its target node and all nodes. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_augmentations(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + ly_bool once; + ly_bool origin_was_lysc_tree = 0; + struct trt_keyword_stmt ks; + + if (tc->lysc_tree) { + origin_was_lysc_tree = 1; + trm_reset_to_lysp_tree_ctx(pc, tc); + } + + once = 1; + for (ks = trop_modi_next_augment(tc); ks.section_name; ks = trop_modi_next_augment(tc)) { + + if (origin_was_lysc_tree) { + /* if lysc tree is used, then only augments targeting + * another module are printed + */ + if (trm_nodeid_target_is_local((const struct lysp_node_augment *)tc->tpn, tc->pmod)) { + continue; + } + } + + if (once) { + ly_print_(pc->out, "\n"); + ly_print_(pc->out, "\n"); + once = 0; + } else { + ly_print_(pc->out, "\n"); + } + + trm_print_section_as_subtree(ks, pc, tc); + } + + if (origin_was_lysc_tree) { + trm_reset_to_lysc_tree_ctx(pc, tc); + } +} + +/** + * @brief For rpcs section: print 'rpcs' keyword and all its nodes. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_rpcs(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + struct trt_keyword_stmt rpc; + + rpc = tro_modi_get_rpcs(tc); + + if (rpc.section_name) { + ly_print_(pc->out, "\n"); + ly_print_(pc->out, "\n"); + trm_print_section_as_family_tree(rpc, pc, tc); + } +} + +/** + * @brief For notifications section: print 'notifications' keyword + * and all its nodes. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_notifications(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + struct trt_keyword_stmt notifs; + + notifs = tro_modi_get_notifications(tc); + + if (notifs.section_name) { + ly_print_(pc->out, "\n"); + ly_print_(pc->out, "\n"); + trm_print_section_as_family_tree(notifs, pc, tc); + } +} + +/** + * @brief For all grouping sections: print 'grouping' keyword, its name + * and all nodes. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_groupings(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + ly_bool once; + struct trt_keyword_stmt ks; + + if (tc->lysc_tree) { + return; + } + + once = 1; + for (ks = trop_modi_next_grouping(tc); ks.section_name; ks = trop_modi_next_grouping(tc)) { + if (once) { + ly_print_(pc->out, "\n"); + ly_print_(pc->out, "\n"); + once = 0; + } else { + ly_print_(pc->out, "\n"); + } + trm_print_section_as_subtree(ks, pc, tc); + } +} + +/** + * @brief Print all sections defined in plugins. + * + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_plugin_ext(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + LY_ERR rc; + ly_bool once; + LY_ARRAY_COUNT_TYPE i = 0, j; + struct trt_keyword_stmt ks, prev_ks = {0}; + struct trt_printer_ctx pc_dupl; + struct trt_tree_ctx tc_dupl; + struct trt_node node; + uint32_t max_gap_before_type; + void *ext; + + tc->section = TRD_SECT_PLUG_DATA; + + tc_dupl = *tc; + pc_dupl = *pc; + + once = 1; + + while ((ext = trb_mod_ext_iter(tc, &i))) { + struct lyspr_tree_ctx plug_ctx = {0}; + + rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx); + LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end); + if (!plug_ctx.schemas) { + continue; + } + + ks = tro_get_ext_section(tc, ext, &plug_ctx); + if (once || (prev_ks.section_name && strcmp(prev_ks.section_name, ks.section_name))) { + ly_print_(pc->out, "\n"); + ly_print_(pc->out, "\n"); + once = 0; + } else { + ly_print_(pc->out, "\n"); + } + trp_print_keyword_stmt(ks, pc->max_line_length, pc->out); + + max_gap_before_type = 0; + trb_ext_try_unified_indent(&plug_ctx, TRP_EMPTY_PARENT_CACHE, &max_gap_before_type, pc, tc); + LY_ARRAY_FOR(plug_ctx.schemas, j) { + trm_reset_tree_ctx_by_plugin(&plug_ctx, j, pc, tc); + node = TRP_EMPTY_NODE; + trb_print_subtree_nodes(&node, max_gap_before_type, TRP_INIT_WRAPPER_BODY, TRP_EMPTY_PARENT_CACHE, pc, tc); + } + + *tc = tc_dupl; + trp_ext_free_plugin_ctx(&plug_ctx); + prev_ks = ks; + } + +end: + *pc = pc_dupl; + return; +} + +/** + * @brief Print sections module, augment, rpcs, notifications, + * grouping, yang-data. + * @param[in] pc contains mainly functions for printing. + * @param[in,out] tc is the tree context. + */ +static void +trm_print_sections(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) +{ + trm_print_module_section(pc, tc); + trm_print_augmentations(pc, tc); + trm_print_rpcs(pc, tc); + trm_print_notifications(pc, tc); + trm_print_groupings(pc, tc); + trm_print_plugin_ext(pc, tc); + ly_print_(pc->out, "\n"); +} + +static LY_ERR +tree_print_check_error(struct ly_out_clb_arg *out, struct trt_tree_ctx *tc) +{ + if (out->last_error) { + return out->last_error; + } else if (tc->last_error) { + return tc->last_error; + } else { + return LY_SUCCESS; + } +} + +/********************************************************************** + * Definition of module interface + *********************************************************************/ + +LY_ERR +tree_print_module(struct ly_out *out, const struct lys_module *module, uint32_t UNUSED(options), size_t line_length) +{ + struct trt_printer_ctx pc; + struct trt_tree_ctx tc; + struct ly_out *new_out; + LY_ERR erc; + struct ly_out_clb_arg clb_arg = TRP_INIT_LY_OUT_CLB_ARG(TRD_PRINT, out, 0, LY_SUCCESS); + + LY_CHECK_ARG_RET3(module->ctx, out, module, module->parsed, LY_EINVAL); + + if ((erc = ly_out_new_clb(&trp_ly_out_clb_func, &clb_arg, &new_out))) { + return erc; + } + + line_length = line_length == 0 ? SIZE_MAX : line_length; + if ((module->ctx->flags & LY_CTX_SET_PRIV_PARSED) && module->compiled) { + trm_lysc_tree_ctx(module, new_out, line_length, &pc, &tc); + } else { + trm_lysp_tree_ctx(module, new_out, line_length, &pc, &tc); + } + + trm_print_sections(&pc, &tc); + erc = tree_print_check_error(&clb_arg, &tc); + + ly_out_free(new_out, NULL, 1); + + return erc; +} + +LY_ERR +tree_print_compiled_node(struct ly_out *out, const struct lysc_node *node, uint32_t options, size_t line_length) +{ + struct trt_printer_ctx pc; + struct trt_tree_ctx tc; + struct ly_out *new_out; + struct trt_wrapper wr; + LY_ERR erc; + struct ly_out_clb_arg clb_arg = TRP_INIT_LY_OUT_CLB_ARG(TRD_PRINT, out, 0, LY_SUCCESS); + + assert(out && node); + + if (!(node->module->ctx->flags & LY_CTX_SET_PRIV_PARSED)) { + return LY_EINVAL; + } + + if ((erc = ly_out_new_clb(&trp_ly_out_clb_func, &clb_arg, &new_out))) { + return erc; + } + + line_length = line_length == 0 ? SIZE_MAX : line_length; + trm_lysc_tree_ctx(node->module, new_out, line_length, &pc, &tc); + + trp_print_keyword_stmt(pc.fp.read.module_name(&tc), pc.max_line_length, pc.out); + trb_print_parents(node, NULL, &pc, &tc); + + if (!(options & LYS_PRINT_NO_SUBSTMT)) { + tc.cn = lysc_node_child(node); + wr = trb_count_depth(NULL, tc.cn); + trb_print_family_tree(wr, &pc, &tc); + } + ly_print_(out, "\n"); + + erc = tree_print_check_error(&clb_arg, &tc); + ly_out_free(new_out, NULL, 1); + + return erc; +} + +LY_ERR +tree_print_parsed_submodule(struct ly_out *out, const struct lysp_submodule *submodp, uint32_t UNUSED(options), + size_t line_length) +{ + struct trt_printer_ctx pc; + struct trt_tree_ctx tc; + struct ly_out *new_out; + LY_ERR erc; + struct ly_out_clb_arg clb_arg = TRP_INIT_LY_OUT_CLB_ARG(TRD_PRINT, out, 0, LY_SUCCESS); + + assert(submodp); + LY_CHECK_ARG_RET(submodp->mod->ctx, out, LY_EINVAL); + + if ((erc = ly_out_new_clb(&trp_ly_out_clb_func, &clb_arg, &new_out))) { + return erc; + } + + line_length = line_length == 0 ? SIZE_MAX : line_length; + trm_lysp_tree_ctx(submodp->mod, new_out, line_length, &pc, &tc); + tc.pmod = (struct lysp_module *)submodp; + tc.tpn = submodp->data; + tc.pn = tc.tpn; + + trm_print_sections(&pc, &tc); + erc = tree_print_check_error(&clb_arg, &tc); + + ly_out_free(new_out, NULL, 1); + + return erc; +} diff --git a/src/printer_xml.c b/src/printer_xml.c new file mode 100644 index 0000000..a7f4c73 --- /dev/null +++ b/src/printer_xml.c @@ -0,0 +1,607 @@ +/** + * @file printer_xml.c + * @author Michal Vasko + * @author Radek Krejci + * @brief XML printer for libyang data structure + * + * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include +#include + +#include "common.h" +#include "context.h" +#include "dict.h" +#include "log.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 "xml.h" + +/** + * @brief XML printer context. + */ +struct xmlpr_ctx { + struct ly_out *out; /**< output specification */ + uint16_t level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */ + uint32_t options; /**< [Data printer flags](@ref dataprinterflags) */ + const struct ly_ctx *ctx; /**< libyang context */ + struct ly_set prefix; /**< printed namespace prefixes */ + struct ly_set ns; /**< printed namespaces */ +}; + +#define LYXML_PREFIX_REQUIRED 0x01 /**< The prefix is not just a suggestion but a requirement. */ +#define LYXML_PREFIX_DEFAULT 0x02 /**< The namespace is required to be a default (without prefix) */ + +/** + * @brief Print a namespace if not already printed. + * + * @param[in] ctx XML printer context. + * @param[in] ns Namespace to print, expected to be in dictionary. + * @param[in] new_prefix Suggested new prefix, NULL for a default namespace without prefix. Stored in the dictionary. + * @param[in] prefix_opts Prefix options changing the meaning of parameters. + * @return Printed prefix of the namespace to use. + */ +static const char * +xml_print_ns(struct xmlpr_ctx *pctx, const char *ns, const char *new_prefix, uint32_t prefix_opts) +{ + uint32_t i; + + for (i = pctx->ns.count; i > 0; --i) { + if (!new_prefix) { + /* find default namespace */ + if (!pctx->prefix.objs[i - 1]) { + if (!strcmp(pctx->ns.objs[i - 1], ns)) { + /* matching default namespace */ + return pctx->prefix.objs[i - 1]; + } + /* not matching default namespace */ + break; + } + } else { + /* find prefixed namespace */ + if (!strcmp(pctx->ns.objs[i - 1], ns)) { + if (!pctx->prefix.objs[i - 1]) { + /* default namespace is not interesting */ + continue; + } + + if (!strcmp(pctx->prefix.objs[i - 1], new_prefix) || !(prefix_opts & LYXML_PREFIX_REQUIRED)) { + /* the same prefix or can be any */ + return pctx->prefix.objs[i - 1]; + } + } + } + } + + /* suitable namespace not found, must be printed */ + ly_print_(pctx->out, " xmlns%s%s=\"%s\"", new_prefix ? ":" : "", new_prefix ? new_prefix : "", ns); + + /* and added into namespaces */ + if (new_prefix) { + LY_CHECK_RET(lydict_insert(pctx->ctx, new_prefix, 0, &new_prefix), NULL); + } + LY_CHECK_RET(ly_set_add(&pctx->prefix, (void *)new_prefix, 1, NULL), NULL); + LY_CHECK_RET(ly_set_add(&pctx->ns, (void *)ns, 1, &i), NULL); + + /* return it */ + return pctx->prefix.objs[i]; +} + +static const char * +xml_print_ns_opaq(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, const struct ly_opaq_name *name, uint32_t prefix_opts) +{ + switch (format) { + case LY_VALUE_XML: + return xml_print_ns(pctx, name->module_ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts); + break; + case LY_VALUE_JSON: + if (name->module_name) { + const struct lys_module *mod = ly_ctx_get_module_latest(pctx->ctx, name->module_name); + + if (mod) { + return xml_print_ns(pctx, mod->ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts); + } + } + break; + default: + /* cannot be created */ + LOGINT(pctx->ctx); + } + + return NULL; +} + +/** + * @brief Print prefix data. + * + * @param[in] ctx XML printer context. + * @param[in] format Value prefix format, only ::LY_VALUE_XML supported. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] prefix_opts Prefix options changing the meaning of parameters. + * @return LY_ERR value. + */ +static void +xml_print_ns_prefix_data(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, void *prefix_data, uint32_t prefix_opts) +{ + const struct ly_set *set; + const struct lyxml_ns *ns; + uint32_t i; + + switch (format) { + case LY_VALUE_XML: + set = prefix_data; + for (i = 0; i < set->count; ++i) { + ns = set->objs[i]; + if (!ns->prefix) { + /* default namespace is not for the element */ + continue; + } + + xml_print_ns(pctx, ns->uri, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : ns->prefix, prefix_opts); + } + break; + default: + /* cannot be created */ + LOGINT(pctx->ctx); + } +} + +/** + * @brief Print metadata of a node. + * + * @param[in] pctx XML printer context. + * @param[in] node Data node with metadata. + */ +static void +xml_print_meta(struct xmlpr_ctx *pctx, const struct lyd_node *node) +{ + struct lyd_meta *meta; + const struct lys_module *mod; + struct ly_set ns_list = {0}; + LY_ARRAY_COUNT_TYPE u; + ly_bool dynamic, filter_attrs = 0; + + /* with-defaults */ + if (node->schema->nodetype & LYD_NODE_TERM) { + 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, print attribute only if context include with-defaults schema */ + mod = ly_ctx_get_module_latest(LYD_CTX(node), "ietf-netconf-with-defaults"); + if (mod) { + ly_print_(pctx->out, " %s:default=\"true\"", xml_print_ns(pctx, mod->ns, mod->prefix, 0)); + } + } + } + + /* check for NETCONF filter unqualified attributes */ + if (!strcmp(node->schema->module->name, "notifications")) { + filter_attrs = 1; + } else { + LY_ARRAY_FOR(node->schema->exts, u) { + if (!strcmp(node->schema->exts[u].def->name, "get-filter-element-attributes") && + !strcmp(node->schema->exts[u].def->module->name, "ietf-netconf")) { + filter_attrs = 1; + break; + } + } + } + + for (meta = node->meta; meta; meta = meta->next) { + const char *value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list, + &dynamic, NULL); + + /* print namespaces connected with the value's prefixes */ + for (uint32_t i = 0; i < ns_list.count; ++i) { + mod = ns_list.objs[i]; + xml_print_ns(pctx, mod->ns, mod->prefix, 1); + } + ly_set_erase(&ns_list, NULL); + + mod = meta->annotation->module; + if (filter_attrs && !strcmp(mod->name, "ietf-netconf") && (!strcmp(meta->name, "type") || + !strcmp(meta->name, "select"))) { + /* print special NETCONF filter unqualified attributes */ + ly_print_(pctx->out, " %s=\"", meta->name); + } else { + /* print the metadata with its namespace */ + ly_print_(pctx->out, " %s:%s=\"", xml_print_ns(pctx, mod->ns, mod->prefix, 1), meta->name); + } + + /* print metadata value */ + if (value && value[0]) { + lyxml_dump_text(pctx->out, value, 1); + } + ly_print_(pctx->out, "\""); + if (dynamic) { + free((void *)value); + } + } +} + +/** + * @brief Print generic XML element despite of the data node type. + * + * Prints the element name, attributes and necessary namespaces. + * + * @param[in] ctx XML printer context. + * @param[in] node Data node to be printed. + */ +static void +xml_print_node_open(struct xmlpr_ctx *pctx, const struct lyd_node *node) +{ + /* print node name */ + ly_print_(pctx->out, "%*s<%s", INDENT, node->schema->name); + + /* print default namespace */ + xml_print_ns(pctx, node->schema->module->ns, NULL, 0); + + /* print metadata */ + xml_print_meta(pctx, node); +} + +static LY_ERR +xml_print_attr(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node) +{ + const struct lyd_attr *attr; + const char *pref; + + LY_LIST_FOR(node->attr, attr) { + pref = NULL; + if (attr->name.prefix) { + /* print attribute namespace */ + pref = xml_print_ns_opaq(pctx, attr->format, &attr->name, 0); + } + + /* print namespaces connected with the value's prefixes */ + if (attr->val_prefix_data) { + xml_print_ns_prefix_data(pctx, attr->format, attr->val_prefix_data, LYXML_PREFIX_REQUIRED); + } + + /* print the attribute with its prefix and value */ + ly_print_(pctx->out, " %s%s%s=\"", pref ? pref : "", pref ? ":" : "", attr->name.name); + lyxml_dump_text(pctx->out, attr->value, 1); + ly_print_(pctx->out, "\""); /* print attribute value terminator */ + + } + + return LY_SUCCESS; +} + +static LY_ERR +xml_print_opaq_open(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node) +{ + /* print node name */ + ly_print_(pctx->out, "%*s<%s", INDENT, node->name.name); + + if (node->name.prefix || node->name.module_ns) { + /* print default namespace */ + xml_print_ns_opaq(pctx, node->format, &node->name, LYXML_PREFIX_DEFAULT); + } + + /* print attributes */ + LY_CHECK_RET(xml_print_attr(pctx, node)); + + return LY_SUCCESS; +} + +static LY_ERR xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node); + +/** + * @brief Print XML element representing lyd_node_term. + * + * @param[in] ctx XML printer context. + * @param[in] node Data node to be printed. + * @return LY_ERR value. + */ +static LY_ERR +xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node) +{ + struct ly_set ns_list = {0}; + ly_bool dynamic; + const char *value; + + xml_print_node_open(pctx, &node->node); + value = ((struct lysc_node_leaf *)node->schema)->type->plugin->print(LYD_CTX(node), &node->value, LY_VALUE_XML, + &ns_list, &dynamic, NULL); + LY_CHECK_RET(!value, LY_EINVAL); + + /* print namespaces connected with the values's prefixes */ + for (uint32_t u = 0; u < ns_list.count; ++u) { + const struct lys_module *mod = (const struct lys_module *)ns_list.objs[u]; + + ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns); + } + ly_set_erase(&ns_list, NULL); + + if (!value[0]) { + ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : ""); + } else { + ly_print_(pctx->out, ">"); + lyxml_dump_text(pctx->out, value, 0); + ly_print_(pctx->out, "%s", node->schema->name, DO_FORMAT ? "\n" : ""); + } + if (dynamic) { + free((void *)value); + } + + return LY_SUCCESS; +} + +/** + * @brief Print XML element representing lyd_node_inner. + * + * @param[in] ctx XML printer context. + * @param[in] node Data node to be printed. + * @return LY_ERR value. + */ +static LY_ERR +xml_print_inner(struct xmlpr_ctx *pctx, const struct lyd_node_inner *node) +{ + LY_ERR ret; + struct lyd_node *child; + + xml_print_node_open(pctx, &node->node); + + LY_LIST_FOR(node->child, child) { + if (lyd_node_should_print(child, pctx->options)) { + break; + } + } + if (!child) { + /* there are no children that will be printed */ + ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : ""); + return LY_SUCCESS; + } + + /* children */ + ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : ""); + + LEVEL_INC; + LY_LIST_FOR(node->child, child) { + ret = xml_print_node(pctx, child); + LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret); + } + LEVEL_DEC; + + ly_print_(pctx->out, "%*s%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : ""); + + return LY_SUCCESS; +} + +static LY_ERR +xml_print_anydata(struct xmlpr_ctx *pctx, const struct lyd_node_any *node) +{ + struct lyd_node_any *any = (struct lyd_node_any *)node; + struct lyd_node *iter; + uint32_t prev_opts, temp_lo = 0; + LY_ERR ret; + + if ((node->schema->nodetype == LYS_ANYDATA) && (node->value_type != LYD_ANYDATA_DATATREE)) { + LOGINT_RET(pctx->ctx); + } + + xml_print_node_open(pctx, &node->node); + + if (!any->value.tree) { + /* no content */ +no_content: + ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : ""); + return LY_SUCCESS; + } else { + if (any->value_type == LYD_ANYDATA_LYB) { + /* turn logging off */ + ly_temp_log_options(&temp_lo); + + /* try to parse it into a data tree */ + if (lyd_parse_data_mem((struct ly_ctx *)LYD_CTX(node), any->value.mem, LYD_LYB, + LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_STRICT, 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(NULL); + } + + switch (any->value_type) { + case LYD_ANYDATA_DATATREE: + /* close opening tag and print data */ + prev_opts = pctx->options; + pctx->options &= ~LYD_PRINT_WITHSIBLINGS; + LEVEL_INC; + + ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : ""); + LY_LIST_FOR(any->value.tree, iter) { + ret = xml_print_node(pctx, iter); + LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret); + } + + LEVEL_DEC; + pctx->options = prev_opts; + break; + case LYD_ANYDATA_STRING: + /* escape XML-sensitive characters */ + if (!any->value.str[0]) { + goto no_content; + } + /* close opening tag and print data */ + ly_print_(pctx->out, ">"); + lyxml_dump_text(pctx->out, any->value.str, 0); + break; + case LYD_ANYDATA_XML: + /* print without escaping special characters */ + if (!any->value.str[0]) { + goto no_content; + } + ly_print_(pctx->out, ">%s", any->value.str); + break; + case LYD_ANYDATA_JSON: + case LYD_ANYDATA_LYB: + /* JSON and LYB format is not supported */ + LOGWRN(pctx->ctx, "Unable to print anydata content (type %d) as XML.", any->value_type); + goto no_content; + } + + /* closing tag */ + if (any->value_type == LYD_ANYDATA_DATATREE) { + ly_print_(pctx->out, "%*s%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : ""); + } else { + ly_print_(pctx->out, "%s", node->schema->name, DO_FORMAT ? "\n" : ""); + } + } + + return LY_SUCCESS; +} + +static LY_ERR +xml_print_opaq(struct xmlpr_ctx *pctx, const struct lyd_node_opaq *node) +{ + LY_ERR ret; + struct lyd_node *child; + + LY_CHECK_RET(xml_print_opaq_open(pctx, node)); + + if (node->value[0]) { + /* print namespaces connected with the value's prefixes */ + if (node->val_prefix_data) { + xml_print_ns_prefix_data(pctx, node->format, node->val_prefix_data, LYXML_PREFIX_REQUIRED); + } + + ly_print_(pctx->out, ">"); + lyxml_dump_text(pctx->out, node->value, 0); + } + + if (node->child) { + /* children */ + if (!node->value[0]) { + ly_print_(pctx->out, ">%s", DO_FORMAT ? "\n" : ""); + } + + LEVEL_INC; + LY_LIST_FOR(node->child, child) { + ret = xml_print_node(pctx, child); + LY_CHECK_ERR_RET(ret, LEVEL_DEC, ret); + } + LEVEL_DEC; + + ly_print_(pctx->out, "%*s%s", INDENT, node->name.name, DO_FORMAT ? "\n" : ""); + } else if (node->value[0]) { + ly_print_(pctx->out, "%s", node->name.name, DO_FORMAT ? "\n" : ""); + } else { + /* no value or children */ + ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : ""); + } + + return LY_SUCCESS; +} + +/** + * @brief Print XML element representing lyd_node. + * + * @param[in] ctx XML printer context. + * @param[in] node Data node to be printed. + * @return LY_ERR value. + */ +static LY_ERR +xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t ns_count; + + if (!lyd_node_should_print(node, pctx->options)) { + /* do not print at all */ + return LY_SUCCESS; + } + + /* remember namespace definition count on this level */ + ns_count = pctx->ns.count; + + if (!node->schema) { + ret = xml_print_opaq(pctx, (const struct lyd_node_opaq *)node); + } else { + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + case LYS_NOTIF: + case LYS_RPC: + case LYS_ACTION: + ret = xml_print_inner(pctx, (const struct lyd_node_inner *)node); + break; + case LYS_LEAF: + case LYS_LEAFLIST: + ret = xml_print_term(pctx, (const struct lyd_node_term *)node); + break; + case LYS_ANYXML: + case LYS_ANYDATA: + ret = xml_print_anydata(pctx, (const struct lyd_node_any *)node); + break; + default: + LOGINT(pctx->ctx); + ret = LY_EINT; + break; + } + } + + /* remove all added namespaces */ + while (ns_count < pctx->ns.count) { + lydict_remove(pctx->ctx, pctx->prefix.objs[pctx->prefix.count - 1]); + ly_set_rm_index(&pctx->prefix, pctx->prefix.count - 1, NULL); + ly_set_rm_index(&pctx->ns, pctx->ns.count - 1, NULL); + } + + return ret; +} + +LY_ERR +xml_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options) +{ + const struct lyd_node *node; + struct xmlpr_ctx pctx = {0}; + + if (!root) { + if ((out->type == LY_OUT_MEMORY) || (out->type == LY_OUT_CALLBACK)) { + ly_print_(out, ""); + } + goto finish; + } + + pctx.out = out; + pctx.level = 0; + pctx.options = options; + pctx.ctx = LYD_CTX(root); + + /* content */ + LY_LIST_FOR(root, node) { + LY_CHECK_RET(xml_print_node(&pctx, node)); + if (!(options & LYD_PRINT_WITHSIBLINGS)) { + break; + } + } + +finish: + assert(!pctx.prefix.count && !pctx.ns.count); + ly_set_erase(&pctx.prefix, NULL); + ly_set_erase(&pctx.ns, NULL); + ly_print_flush(out); + return LY_SUCCESS; +} diff --git a/src/printer_yang.c b/src/printer_yang.c new file mode 100644 index 0000000..ea643ac --- /dev/null +++ b/src/printer_yang.c @@ -0,0 +1,2657 @@ +/** + * @file printer_yang.c + * @author Radek Krejci + * @author Michal Vasko + * @brief YANG printer + * + * Copyright (c) 2015 - 2022 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 + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "log.h" +#include "out.h" +#include "out_internal.h" +#include "plugins_exts.h" +#include "plugins_types.h" +#include "printer_internal.h" +#include "printer_schema.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +/** + * @brief Types of the YANG printers + */ +enum lys_ypr_schema_type { + LYS_YPR_PARSED, /**< YANG printer of the parsed schema */ + LYS_YPR_COMPILED /**< YANG printer of the compiled schema */ +}; + +#define YPR_CTX_FLAG_EXTRA_LINE 0x01 /**< Flag for ::ypr_ctx::flags to print extra line in schema */ + +#define YPR_EXTRA_LINE(COND, PCTX) if (COND) { (PCTX)->flags |= YPR_CTX_FLAG_EXTRA_LINE; } +#define YPR_EXTRA_LINE_PRINT(PCTX) \ + if ((PCTX)->flags & YPR_CTX_FLAG_EXTRA_LINE) { \ + (PCTX)->flags &= ~YPR_CTX_FLAG_EXTRA_LINE; \ + if (DO_FORMAT) { \ + ly_print_((PCTX)->out, "\n"); \ + } \ + } + +/** + * @brief Compiled YANG printer context + * + * Note that the YANG extensions API provides getter to the members for the extension plugins. + */ +struct lys_ypr_ctx { + union { + struct { + struct ly_out *out; /**< output specification */ + uint16_t level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */ + uint16_t flags; /**< internal flags for use by printer */ + uint32_t options; /**< Schema output options (see @ref schemaprinterflags). */ + const struct lys_module *module; /**< schema to print */ + }; + struct lyspr_ctx printer_ctx; + }; + + /* YANG printer specific members */ + enum lys_ypr_schema_type schema; /**< type of the schema to print */ +}; + +/** + * @brief Print the given text as content of a double quoted YANG string, + * including encoding characters that have special meanings. The quotation marks + * are not printed. + * + * Follows RFC 7950, section 6.1.3. + * + * @param[in] out Output specification. + * @param[in] text String to be printed. + * @param[in] len Length of the string from @p text to be printed. In case of -1, + * the @p text is printed completely as a NULL-terminated string. + */ +static void +ypr_encode(struct ly_out *out, const char *text, ssize_t len) +{ + size_t i, start_len; + const char *start; + char special = 0; + + if (!len) { + return; + } + + if (len < 0) { + len = strlen(text); + } + + start = text; + start_len = 0; + for (i = 0; i < (size_t)len; ++i) { + switch (text[i]) { + case '\n': + case '\t': + case '\"': + case '\\': + special = text[i]; + break; + default: + ++start_len; + break; + } + + if (special) { + ly_write_(out, start, start_len); + switch (special) { + case '\n': + ly_write_(out, "\\n", 2); + break; + case '\t': + ly_write_(out, "\\t", 2); + break; + case '\"': + ly_write_(out, "\\\"", 2); + break; + case '\\': + ly_write_(out, "\\\\", 2); + break; + } + + start += start_len + 1; + start_len = 0; + + special = 0; + } + } + + ly_write_(out, start, start_len); +} + +static void +ypr_open(struct ly_out *out, ly_bool *flag) +{ + if (flag && !*flag) { + *flag = 1; + ly_print_(out, " {\n"); + } +} + +static void +ypr_close(struct lys_ypr_ctx *pctx, ly_bool flag) +{ + if (flag) { + ly_print_(pctx->out, "%*s}\n", INDENT); + } else { + ly_print_(pctx->out, ";\n"); + } +} + +static void +ypr_text(struct lys_ypr_ctx *pctx, const char *name, const char *text, ly_bool singleline, ly_bool closed) +{ + const char *s, *t; + + if (singleline) { + ly_print_(pctx->out, "%*s%s \"", INDENT, name); + } else { + ly_print_(pctx->out, "%*s%s\n", INDENT, name); + LEVEL++; + + ly_print_(pctx->out, "%*s\"", INDENT); + } + t = text; + while ((s = strchr(t, '\n'))) { + ypr_encode(pctx->out, t, s - t); + ly_print_(pctx->out, "\n"); + t = s + 1; + if (*t != '\n') { + ly_print_(pctx->out, "%*s ", INDENT); + } + } + + ypr_encode(pctx->out, t, strlen(t)); + if (closed) { + ly_print_(pctx->out, "\";\n"); + } else { + ly_print_(pctx->out, "\""); + } + if (!singleline) { + LEVEL--; + } +} + +static void +yprp_stmt(struct lys_ypr_ctx *pctx, struct lysp_stmt *stmt) +{ + struct lysp_stmt *childstmt; + const char *s, *t; + + if (stmt->arg) { + if (stmt->flags) { + ly_print_(pctx->out, "%*s%s\n", INDENT, stmt->stmt); + LEVEL++; + ly_print_(pctx->out, "%*s%c", INDENT, (stmt->flags & LYS_DOUBLEQUOTED) ? '\"' : '\''); + t = stmt->arg; + while ((s = strchr(t, '\n'))) { + ypr_encode(pctx->out, t, s - t); + ly_print_(pctx->out, "\n"); + t = s + 1; + if (*t != '\n') { + ly_print_(pctx->out, "%*s ", INDENT); + } + } + LEVEL--; + ypr_encode(pctx->out, t, strlen(t)); + ly_print_(pctx->out, "%c%s", (stmt->flags & LYS_DOUBLEQUOTED) ? '\"' : '\'', stmt->child ? " {\n" : ";\n"); + } else { + ly_print_(pctx->out, "%*s%s %s%s", INDENT, stmt->stmt, stmt->arg, stmt->child ? " {\n" : ";\n"); + } + } else { + ly_print_(pctx->out, "%*s%s%s", INDENT, stmt->stmt, stmt->child ? " {\n" : ";\n"); + } + + if (stmt->child) { + LEVEL++; + LY_LIST_FOR(stmt->child, childstmt) { + yprp_stmt(pctx, childstmt); + } + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); + } +} + +static void +yprp_extension_instance(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, + struct lysp_ext_instance *ext, ly_bool *flag) +{ + struct lysp_stmt *stmt; + ly_bool child_presence; + + if ((ext->flags & LYS_INTERNAL) || (ext->parent_stmt != substmt) || (ext->parent_stmt_index != substmt_index)) { + return; + } + + ypr_open(pctx->out, flag); + + if (ext->def->argname) { + ly_print_(pctx->out, "%*s%s \"", INDENT, ext->name); + ypr_encode(pctx->out, ext->argument, -1); + ly_print_(pctx->out, "\""); + } else { + ly_print_(pctx->out, "%*s%s", INDENT, ext->name); + } + + child_presence = 0; + LEVEL++; + LY_LIST_FOR(ext->child, stmt) { + if (stmt->flags & (LYS_YIN_ATTR | LYS_YIN_ARGUMENT)) { + continue; + } + if (!child_presence) { + ly_print_(pctx->out, " {\n"); + child_presence = 1; + } + yprp_stmt(pctx, stmt); + } + LEVEL--; + if (child_presence) { + ly_print_(pctx->out, "%*s}\n", INDENT); + } else { + ly_print_(pctx->out, ";\n"); + } +} + +static void +yprp_extension_instances(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, + struct lysp_ext_instance *exts, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(exts, u) { + yprp_extension_instance(pctx, substmt, substmt_index, &exts[u], flag); + } +} + +static void +yprc_extension_instances(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, + struct lysc_ext_instance *exts, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool inner_flag; + + LY_ARRAY_FOR(exts, u) { + if ((exts[u].parent_stmt != substmt) || (exts[u].parent_stmt_index != substmt_index)) { + return; + } + + ypr_open(pctx->out, flag); + if (exts[u].argument) { + ly_print_(pctx->out, "%*s%s:%s \"", INDENT, exts[u].def->module->name, exts[u].def->name); + ypr_encode(pctx->out, exts[u].argument, -1); + ly_print_(pctx->out, "\""); + } else { + ly_print_(pctx->out, "%*s%s:%s", INDENT, exts[u].def->module->name, exts[u].def->name); + } + + LEVEL++; + inner_flag = 0; + yprc_extension_instances(pctx, LY_STMT_EXTENSION_INSTANCE, 0, exts[u].exts, &inner_flag); + + if (exts[u].def->plugin && exts[u].def->plugin->printer_info) { + exts[u].def->plugin->printer_info(&pctx->printer_ctx, &exts[u], &inner_flag); + } + + LEVEL--; + ypr_close(pctx, inner_flag); + } +} + +static void +ypr_substmt(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, const char *text, void *exts) +{ + ly_bool extflag = 0; + + if (!text) { + /* nothing to print */ + return; + } + + if (lys_stmt_flags(substmt) & LY_STMT_FLAG_ID) { + ly_print_(pctx->out, "%*s%s %s", INDENT, lys_stmt_str(substmt), text); + } else { + ypr_text(pctx, lys_stmt_str(substmt), text, (lys_stmt_flags(substmt) & LY_STMT_FLAG_YIN) ? 0 : 1, 0); + } + + LEVEL++; + if (pctx->schema == LYS_YPR_PARSED) { + yprp_extension_instances(pctx, substmt, substmt_index, exts, &extflag); + } else { + yprc_extension_instances(pctx, substmt, substmt_index, exts, &extflag); + } + LEVEL--; + ypr_close(pctx, extflag); +} + +static void +ypr_unsigned(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, void *exts, + unsigned long attr_value, ly_bool *flag) +{ + char *str; + + if (asprintf(&str, "%lu", attr_value) == -1) { + LOGMEM(pctx->module->ctx); + return; + } + ypr_open(pctx->out, flag); + ypr_substmt(pctx, substmt, substmt_index, str, exts); + free(str); +} + +static void +ypr_signed(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, void *exts, long attr_value, + ly_bool *flag) +{ + char *str; + + if (asprintf(&str, "%ld", attr_value) == -1) { + LOGMEM(pctx->module->ctx); + return; + } + ypr_open(pctx->out, flag); + ypr_substmt(pctx, substmt, substmt_index, str, exts); + free(str); +} + +static void +yprp_revision(struct lys_ypr_ctx *pctx, const struct lysp_revision *rev) +{ + if (rev->dsc || rev->ref || rev->exts) { + ly_print_(pctx->out, "%*srevision %s {\n", INDENT, rev->date); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_REVISION, 0, rev->exts, NULL); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, rev->dsc, rev->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, rev->ref, rev->exts); + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); + } else { + ly_print_(pctx->out, "%*srevision %s;\n", INDENT, rev->date); + } +} + +static void +ypr_mandatory(struct lys_ypr_ctx *pctx, uint16_t flags, void *exts, ly_bool *flag) +{ + if (flags & LYS_MAND_MASK) { + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_MANDATORY, 0, (flags & LYS_MAND_TRUE) ? "true" : "false", exts); + } +} + +static void +ypr_config(struct lys_ypr_ctx *pctx, uint16_t flags, void *exts, ly_bool *flag) +{ + if (flags & LYS_CONFIG_MASK) { + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_CONFIG, 0, (flags & LYS_CONFIG_W) ? "true" : "false", exts); + } +} + +static void +ypr_status(struct lys_ypr_ctx *pctx, uint16_t flags, void *exts, ly_bool *flag) +{ + const char *status = NULL; + + if (flags & LYS_STATUS_CURR) { + ypr_open(pctx->out, flag); + status = "current"; + } else if (flags & LYS_STATUS_DEPRC) { + ypr_open(pctx->out, flag); + status = "deprecated"; + } else if (flags & LYS_STATUS_OBSLT) { + ypr_open(pctx->out, flag); + status = "obsolete"; + } + + ypr_substmt(pctx, LY_STMT_STATUS, 0, status, exts); +} + +static void +ypr_description(struct lys_ypr_ctx *pctx, const char *dsc, void *exts, ly_bool *flag) +{ + if (dsc) { + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, dsc, exts); + } +} + +static void +ypr_reference(struct lys_ypr_ctx *pctx, const char *ref, void *exts, ly_bool *flag) +{ + if (ref) { + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, ref, exts); + } +} + +static void +yprp_iffeatures(struct lys_ypr_ctx *pctx, struct lysp_qname *iffs, struct lysp_ext_instance *exts, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool extflag; + + LY_ARRAY_FOR(iffs, u) { + ypr_open(pctx->out, flag); + extflag = 0; + + ly_print_(pctx->out, "%*sif-feature \"%s\"", INDENT, iffs[u].str); + + /* extensions */ + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_IF_FEATURE, u, exts, &extflag); + LEVEL--; + ypr_close(pctx, extflag); + } +} + +static void +yprp_extension(struct lys_ypr_ctx *pctx, const struct lysp_ext *ext) +{ + ly_bool flag = 0, flag2 = 0; + LY_ARRAY_COUNT_TYPE u; + + ly_print_(pctx->out, "%*sextension %s", INDENT, ext->name); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_EXTENSION, 0, ext->exts, &flag); + + if (ext->argname) { + ypr_open(pctx->out, &flag); + ly_print_(pctx->out, "%*sargument %s", INDENT, ext->argname); + LEVEL++; + if (ext->exts) { + u = -1; + while ((u = lysp_ext_instance_iter(ext->exts, u + 1, LY_STMT_ARGUMENT)) != LY_ARRAY_COUNT(ext->exts)) { + yprp_extension_instance(pctx, LY_STMT_ARGUMENT, 0, &ext->exts[u], &flag2); + } + } + if ((ext->flags & LYS_YINELEM_MASK) || + (ext->exts && (lysp_ext_instance_iter(ext->exts, 0, LY_STMT_YIN_ELEMENT) != LY_ARRAY_COUNT(ext->exts)))) { + ypr_open(pctx->out, &flag2); + ypr_substmt(pctx, LY_STMT_YIN_ELEMENT, 0, (ext->flags & LYS_YINELEM_TRUE) ? "true" : "false", ext->exts); + } + LEVEL--; + ypr_close(pctx, flag2); + } + + ypr_status(pctx, ext->flags, ext->exts, &flag); + ypr_description(pctx, ext->dsc, ext->exts, &flag); + ypr_reference(pctx, ext->ref, ext->exts, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_feature(struct lys_ypr_ctx *pctx, const struct lysp_feature *feat) +{ + ly_bool flag = 0; + + ly_print_(pctx->out, "%*sfeature %s", INDENT, feat->name); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_FEATURE, 0, feat->exts, &flag); + yprp_iffeatures(pctx, feat->iffeatures, feat->exts, &flag); + ypr_status(pctx, feat->flags, feat->exts, &flag); + ypr_description(pctx, feat->dsc, feat->exts, &flag); + ypr_reference(pctx, feat->ref, feat->exts, &flag); + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_identity(struct lys_ypr_ctx *pctx, const struct lysp_ident *ident) +{ + ly_bool flag = 0; + LY_ARRAY_COUNT_TYPE u; + + ly_print_(pctx->out, "%*sidentity %s", INDENT, ident->name); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_IDENTITY, 0, ident->exts, &flag); + yprp_iffeatures(pctx, ident->iffeatures, ident->exts, &flag); + + LY_ARRAY_FOR(ident->bases, u) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_BASE, u, ident->bases[u], ident->exts); + } + + ypr_status(pctx, ident->flags, ident->exts, &flag); + ypr_description(pctx, ident->dsc, ident->exts, &flag); + ypr_reference(pctx, ident->ref, ident->exts, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_identity(struct lys_ypr_ctx *pctx, const struct lysc_ident *ident) +{ + ly_bool flag = 0; + LY_ARRAY_COUNT_TYPE u; + + ly_print_(pctx->out, "%*sidentity %s", INDENT, ident->name); + LEVEL++; + + yprc_extension_instances(pctx, LY_STMT_IDENTITY, 0, ident->exts, &flag); + + ypr_open(pctx->out, &flag); + if (lys_identity_iffeature_value(ident) == LY_ENOT) { + ly_print_(pctx->out, "%*s/* identity \"%s\" is disabled by if-feature(s) */\n", INDENT, ident->name); + } + + LY_ARRAY_FOR(ident->derived, u) { + if (pctx->module != ident->derived[u]->module) { + ly_print_(pctx->out, "%*sderived %s:%s;\n", INDENT, ident->derived[u]->module->prefix, ident->derived[u]->name); + } else { + ly_print_(pctx->out, "%*sderived %s;\n", INDENT, ident->derived[u]->name); + } + } + + ypr_status(pctx, ident->flags, ident->exts, &flag); + ypr_description(pctx, ident->dsc, ident->exts, &flag); + ypr_reference(pctx, ident->ref, ident->exts, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_restr(struct lys_ypr_ctx *pctx, const struct lysp_restr *restr, enum ly_stmt stmt, ly_bool *flag) +{ + ly_bool inner_flag = 0, singleline; + const char *text; + + if (!restr) { + return; + } + + ypr_open(pctx->out, flag); + text = ((restr->arg.str[0] != LYSP_RESTR_PATTERN_NACK) && (restr->arg.str[0] != LYSP_RESTR_PATTERN_ACK)) ? + restr->arg.str : restr->arg.str + 1; + singleline = strchr(text, '\n') ? 0 : 1; + ypr_text(pctx, lyplg_ext_stmt2str(stmt), text, singleline, 0); + + LEVEL++; + yprp_extension_instances(pctx, stmt, 0, restr->exts, &inner_flag); + if (restr->arg.str[0] == LYSP_RESTR_PATTERN_NACK) { + /* special byte value in pattern's expression: 0x15 - invert-match, 0x06 - match */ + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_MODIFIER, 0, "invert-match", restr->exts); + } + if (restr->emsg) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_MESSAGE, 0, restr->emsg, restr->exts); + } + if (restr->eapptag) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_APP_TAG, 0, restr->eapptag, restr->exts); + } + ypr_description(pctx, restr->dsc, restr->exts, &inner_flag); + ypr_reference(pctx, restr->ref, restr->exts, &inner_flag); + + LEVEL--; + ypr_close(pctx, inner_flag); +} + +static void +yprc_must(struct lys_ypr_ctx *pctx, const struct lysc_must *must, ly_bool *flag) +{ + ly_bool inner_flag = 0; + + ypr_open(pctx->out, flag); + ly_print_(pctx->out, "%*smust \"", INDENT); + ypr_encode(pctx->out, must->cond->expr, -1); + ly_print_(pctx->out, "\""); + + LEVEL++; + yprc_extension_instances(pctx, LY_STMT_MUST, 0, must->exts, &inner_flag); + if (must->emsg) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_MESSAGE, 0, must->emsg, must->exts); + } + if (must->eapptag) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_APP_TAG, 0, must->eapptag, must->exts); + } + ypr_description(pctx, must->dsc, must->exts, &inner_flag); + ypr_reference(pctx, must->ref, must->exts, &inner_flag); + + LEVEL--; + ypr_close(pctx, inner_flag); +} + +static void +yprc_range(struct lys_ypr_ctx *pctx, const struct lysc_range *range, LY_DATA_TYPE basetype, ly_bool *flag) +{ + ly_bool inner_flag = 0; + LY_ARRAY_COUNT_TYPE u; + + if (!range) { + return; + } + + ypr_open(pctx->out, flag); + ly_print_(pctx->out, "%*s%s \"", INDENT, (basetype == LY_TYPE_STRING || basetype == LY_TYPE_BINARY) ? "length" : "range"); + LY_ARRAY_FOR(range->parts, u) { + if (u > 0) { + ly_print_(pctx->out, " | "); + } + if (range->parts[u].max_64 == range->parts[u].min_64) { + if (basetype <= LY_TYPE_STRING) { /* unsigned values */ + ly_print_(pctx->out, "%" PRIu64, range->parts[u].max_u64); + } else { /* signed values */ + ly_print_(pctx->out, "%" PRId64, range->parts[u].max_64); + } + } else { + if (basetype <= LY_TYPE_STRING) { /* unsigned values */ + ly_print_(pctx->out, "%" PRIu64 "..%" PRIu64, range->parts[u].min_u64, range->parts[u].max_u64); + } else { /* signed values */ + ly_print_(pctx->out, "%" PRId64 "..%" PRId64, range->parts[u].min_64, range->parts[u].max_64); + } + } + } + ly_print_(pctx->out, "\""); + + LEVEL++; + yprc_extension_instances(pctx, LY_STMT_RANGE, 0, range->exts, &inner_flag); + if (range->emsg) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_MESSAGE, 0, range->emsg, range->exts); + } + if (range->eapptag) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_APP_TAG, 0, range->eapptag, range->exts); + } + ypr_description(pctx, range->dsc, range->exts, &inner_flag); + ypr_reference(pctx, range->ref, range->exts, &inner_flag); + + LEVEL--; + ypr_close(pctx, inner_flag); +} + +static void +yprc_pattern(struct lys_ypr_ctx *pctx, const struct lysc_pattern *pattern, ly_bool *flag) +{ + ly_bool inner_flag = 0; + + ypr_open(pctx->out, flag); + ly_print_(pctx->out, "%*spattern \"", INDENT); + ypr_encode(pctx->out, pattern->expr, -1); + ly_print_(pctx->out, "\""); + + LEVEL++; + yprc_extension_instances(pctx, LY_STMT_PATTERN, 0, pattern->exts, &inner_flag); + if (pattern->inverted) { + /* special byte value in pattern's expression: 0x15 - invert-match, 0x06 - match */ + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_MODIFIER, 0, "invert-match", pattern->exts); + } + if (pattern->emsg) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_MESSAGE, 0, pattern->emsg, pattern->exts); + } + if (pattern->eapptag) { + ypr_open(pctx->out, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_APP_TAG, 0, pattern->eapptag, pattern->exts); + } + ypr_description(pctx, pattern->dsc, pattern->exts, &inner_flag); + ypr_reference(pctx, pattern->ref, pattern->exts, &inner_flag); + + LEVEL--; + ypr_close(pctx, inner_flag); +} + +static void +yprc_bits_enum(struct lys_ypr_ctx *pctx, const struct lysc_type_bitenum_item *items, LY_DATA_TYPE basetype, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + const struct lysc_type_bitenum_item *item; + ly_bool inner_flag; + + assert((basetype == LY_TYPE_BITS) || (basetype == LY_TYPE_ENUM)); + + LY_ARRAY_FOR(items, u) { + item = &items[u]; + inner_flag = 0; + + ypr_open(pctx->out, flag); + ly_print_(pctx->out, "%*s%s \"", INDENT, basetype == LY_TYPE_BITS ? "bit" : "enum"); + ypr_encode(pctx->out, item->name, -1); + ly_print_(pctx->out, "\""); + LEVEL++; + if (basetype == LY_TYPE_BITS) { + yprc_extension_instances(pctx, LY_STMT_BIT, 0, item->exts, &inner_flag); + ypr_unsigned(pctx, LY_STMT_POSITION, 0, item->exts, item->position, &inner_flag); + } else { /* LY_TYPE_ENUM */ + yprc_extension_instances(pctx, LY_STMT_ENUM, 0, item->exts, &inner_flag); + ypr_signed(pctx, LY_STMT_VALUE, 0, item->exts, item->value, &inner_flag); + } + ypr_status(pctx, item->flags, item->exts, &inner_flag); + ypr_description(pctx, item->dsc, item->exts, &inner_flag); + ypr_reference(pctx, item->ref, item->exts, &inner_flag); + LEVEL--; + ypr_close(pctx, inner_flag); + } +} + +static void +yprp_when(struct lys_ypr_ctx *pctx, const struct lysp_when *when, ly_bool *flag) +{ + ly_bool inner_flag = 0; + + if (!when) { + return; + } + ypr_open(pctx->out, flag); + + ly_print_(pctx->out, "%*swhen \"", INDENT); + ypr_encode(pctx->out, when->cond, -1); + ly_print_(pctx->out, "\""); + + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_WHEN, 0, when->exts, &inner_flag); + ypr_description(pctx, when->dsc, when->exts, &inner_flag); + ypr_reference(pctx, when->ref, when->exts, &inner_flag); + LEVEL--; + ypr_close(pctx, inner_flag); +} + +static void +yprc_when(struct lys_ypr_ctx *pctx, const struct lysc_when *when, ly_bool *flag) +{ + ly_bool inner_flag = 0; + + if (!when) { + return; + } + ypr_open(pctx->out, flag); + + ly_print_(pctx->out, "%*swhen \"", INDENT); + ypr_encode(pctx->out, when->cond->expr, -1); + ly_print_(pctx->out, "\""); + + LEVEL++; + yprc_extension_instances(pctx, LY_STMT_WHEN, 0, when->exts, &inner_flag); + ypr_description(pctx, when->dsc, when->exts, &inner_flag); + ypr_reference(pctx, when->ref, when->exts, &inner_flag); + LEVEL--; + ypr_close(pctx, inner_flag); +} + +static void +yprp_bits_enum(struct lys_ypr_ctx *pctx, const struct lysp_type_enum *items, LY_DATA_TYPE type, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool inner_flag; + + LY_ARRAY_FOR(items, u) { + ypr_open(pctx->out, flag); + if (type == LY_TYPE_BITS) { + ly_print_(pctx->out, "%*sbit %s", INDENT, items[u].name); + } else { /* LY_TYPE_ENUM */ + ly_print_(pctx->out, "%*senum \"", INDENT); + ypr_encode(pctx->out, items[u].name, -1); + ly_print_(pctx->out, "\""); + } + inner_flag = 0; + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_ENUM, 0, items[u].exts, &inner_flag); + yprp_iffeatures(pctx, items[u].iffeatures, items[u].exts, &inner_flag); + if (items[u].flags & LYS_SET_VALUE) { + if (type == LY_TYPE_BITS) { + ypr_unsigned(pctx, LY_STMT_POSITION, 0, items[u].exts, items[u].value, &inner_flag); + } else { /* LY_TYPE_ENUM */ + ypr_signed(pctx, LY_STMT_VALUE, 0, items[u].exts, items[u].value, &inner_flag); + } + } + ypr_status(pctx, items[u].flags, items[u].exts, &inner_flag); + ypr_description(pctx, items[u].dsc, items[u].exts, &inner_flag); + ypr_reference(pctx, items[u].ref, items[u].exts, &inner_flag); + LEVEL--; + ypr_close(pctx, inner_flag); + } +} + +static void +yprp_type(struct lys_ypr_ctx *pctx, const struct lysp_type *type) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + + ly_print_(pctx->out, "%*stype %s", INDENT, type->name); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_TYPE, 0, type->exts, &flag); + + yprp_restr(pctx, type->range, LY_STMT_RANGE, &flag); + yprp_restr(pctx, type->length, LY_STMT_LENGTH, &flag); + LY_ARRAY_FOR(type->patterns, u) { + yprp_restr(pctx, &type->patterns[u], LY_STMT_PATTERN, &flag); + } + yprp_bits_enum(pctx, type->bits, LY_TYPE_BITS, &flag); + yprp_bits_enum(pctx, type->enums, LY_TYPE_ENUM, &flag); + + if (type->path) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_PATH, 0, type->path->expr, type->exts); + } + if (type->flags & LYS_SET_REQINST) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_REQUIRE_INSTANCE, 0, type->require_instance ? "true" : "false", type->exts); + } + if (type->flags & LYS_SET_FRDIGITS) { + ypr_unsigned(pctx, LY_STMT_FRACTION_DIGITS, 0, type->exts, type->fraction_digits, &flag); + } + LY_ARRAY_FOR(type->bases, u) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_BASE, u, type->bases[u], type->exts); + } + LY_ARRAY_FOR(type->types, u) { + ypr_open(pctx->out, &flag); + yprp_type(pctx, &type->types[u]); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_dflt_value(struct lys_ypr_ctx *pctx, const struct ly_ctx *ly_pctx, const struct lyd_value *value, + struct lysc_ext_instance *exts) +{ + ly_bool dynamic; + const char *str; + + str = value->realtype->plugin->print(ly_pctx, value, LY_VALUE_JSON, NULL, &dynamic, NULL); + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, str, exts); + if (dynamic) { + free((void *)str); + } +} + +static void +yprc_type(struct lys_ypr_ctx *pctx, const struct lysc_type *type) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + + ly_print_(pctx->out, "%*stype %s", INDENT, lys_datatype2str(type->basetype)); + LEVEL++; + + yprc_extension_instances(pctx, LY_STMT_TYPE, 0, type->exts, &flag); + + switch (type->basetype) { + case LY_TYPE_BINARY: { + struct lysc_type_bin *bin = (struct lysc_type_bin *)type; + + yprc_range(pctx, bin->length, type->basetype, &flag); + break; + } + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: + case LY_TYPE_UINT64: + case LY_TYPE_INT8: + case LY_TYPE_INT16: + case LY_TYPE_INT32: + case LY_TYPE_INT64: { + struct lysc_type_num *num = (struct lysc_type_num *)type; + + yprc_range(pctx, num->range, type->basetype, &flag); + break; + } + case LY_TYPE_STRING: { + struct lysc_type_str *str = (struct lysc_type_str *)type; + + yprc_range(pctx, str->length, type->basetype, &flag); + LY_ARRAY_FOR(str->patterns, u) { + yprc_pattern(pctx, str->patterns[u], &flag); + } + break; + } + case LY_TYPE_BITS: + case LY_TYPE_ENUM: { + /* bits and enums structures are compatible */ + struct lysc_type_bits *bits = (struct lysc_type_bits *)type; + + yprc_bits_enum(pctx, bits->bits, type->basetype, &flag); + break; + } + case LY_TYPE_BOOL: + case LY_TYPE_EMPTY: + /* nothing to do */ + break; + case LY_TYPE_DEC64: { + struct lysc_type_dec *dec = (struct lysc_type_dec *)type; + + ypr_open(pctx->out, &flag); + ypr_unsigned(pctx, LY_STMT_FRACTION_DIGITS, 0, type->exts, dec->fraction_digits, &flag); + yprc_range(pctx, dec->range, dec->basetype, &flag); + break; + } + case LY_TYPE_IDENT: { + struct lysc_type_identityref *ident = (struct lysc_type_identityref *)type; + + LY_ARRAY_FOR(ident->bases, u) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_BASE, u, ident->bases[u]->name, type->exts); + } + break; + } + case LY_TYPE_INST: { + struct lysc_type_instanceid *inst = (struct lysc_type_instanceid *)type; + + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_REQUIRE_INSTANCE, 0, inst->require_instance ? "true" : "false", inst->exts); + break; + } + case LY_TYPE_LEAFREF: { + struct lysc_type_leafref *lr = (struct lysc_type_leafref *)type; + + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_PATH, 0, lr->path->expr, lr->exts); + ypr_substmt(pctx, LY_STMT_REQUIRE_INSTANCE, 0, lr->require_instance ? "true" : "false", lr->exts); + yprc_type(pctx, lr->realtype); + break; + } + case LY_TYPE_UNION: { + struct lysc_type_union *un = (struct lysc_type_union *)type; + + LY_ARRAY_FOR(un->types, u) { + ypr_open(pctx->out, &flag); + yprc_type(pctx, un->types[u]); + } + break; + } + default: + LOGINT(pctx->module->ctx); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_typedef(struct lys_ypr_ctx *pctx, const struct lysp_tpdf *tpdf) +{ + ly_print_(pctx->out, "%*stypedef %s {\n", INDENT, tpdf->name); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_TYPEDEF, 0, tpdf->exts, NULL); + + yprp_type(pctx, &tpdf->type); + + if (tpdf->units) { + ypr_substmt(pctx, LY_STMT_UNITS, 0, tpdf->units, tpdf->exts); + } + if (tpdf->dflt.str) { + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, tpdf->dflt.str, tpdf->exts); + } + + ypr_status(pctx, tpdf->flags, tpdf->exts, NULL); + ypr_description(pctx, tpdf->dsc, tpdf->exts, NULL); + ypr_reference(pctx, tpdf->ref, tpdf->exts, NULL); + + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); +} + +static void yprp_node(struct lys_ypr_ctx *pctx, const struct lysp_node *node); +static void yprc_node(struct lys_ypr_ctx *pctx, const struct lysc_node *node); +static void yprp_action(struct lys_ypr_ctx *pctx, const struct lysp_node_action *action); +static void yprp_notification(struct lys_ypr_ctx *pctx, const struct lysp_node_notif *notif); + +static void +yprp_grouping(struct lys_ypr_ctx *pctx, const struct lysp_node_grp *grp) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node *data; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *subgrp; + + ly_print_(pctx->out, "%*sgrouping %s", INDENT, grp->name); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_GROUPING, 0, grp->exts, &flag); + ypr_status(pctx, grp->flags, grp->exts, &flag); + ypr_description(pctx, grp->dsc, grp->exts, &flag); + ypr_reference(pctx, grp->ref, grp->exts, &flag); + + LY_ARRAY_FOR(grp->typedefs, u) { + ypr_open(pctx->out, &flag); + yprp_typedef(pctx, &grp->typedefs[u]); + } + + LY_LIST_FOR(grp->groupings, subgrp) { + ypr_open(pctx->out, &flag); + yprp_grouping(pctx, subgrp); + } + + LY_LIST_FOR(grp->child, data) { + ypr_open(pctx->out, &flag); + yprp_node(pctx, data); + } + + LY_LIST_FOR(grp->actions, action) { + ypr_open(pctx->out, &flag); + yprp_action(pctx, action); + } + + LY_LIST_FOR(grp->notifs, notif) { + ypr_open(pctx->out, &flag); + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_inout(struct lys_ypr_ctx *pctx, const struct lysp_node_action_inout *inout, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node *data; + struct lysp_node_grp *grp; + + if (!inout->child) { + /* no children */ + return; + } + ypr_open(pctx->out, flag); + YPR_EXTRA_LINE_PRINT(pctx); + + ly_print_(pctx->out, "%*s%s {\n", INDENT, inout->name); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_MUST, 0, inout->exts, NULL); + LY_ARRAY_FOR(inout->musts, u) { + yprp_restr(pctx, &inout->musts[u], LY_STMT_MUST, NULL); + } + LY_ARRAY_FOR(inout->typedefs, u) { + yprp_typedef(pctx, &inout->typedefs[u]); + } + LY_LIST_FOR(inout->groupings, grp) { + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(inout->child, data) { + yprp_node(pctx, data); + } + + LEVEL--; + ypr_close(pctx, 1); +} + +static void +yprc_inout(struct lys_ypr_ctx *pctx, const struct lysc_node_action_inout *inout, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_node *data; + + if (!inout->child) { + /* input/output is empty */ + return; + } + ypr_open(pctx->out, flag); + + ly_print_(pctx->out, "\n%*s%s {\n", INDENT, inout->name); + LEVEL++; + + yprc_extension_instances(pctx, lyplg_ext_nodetype2stmt(inout->nodetype), 0, inout->exts, NULL); + LY_ARRAY_FOR(inout->musts, u) { + yprc_must(pctx, &inout->musts[u], NULL); + } + + if (!(pctx->options & LYS_PRINT_NO_SUBSTMT)) { + LY_LIST_FOR(inout->child, data) { + yprc_node(pctx, data); + } + } + + LEVEL--; + ypr_close(pctx, 1); +} + +static void +yprp_notification(struct lys_ypr_ctx *pctx, const struct lysp_node_notif *notif) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node *data; + struct lysp_node_grp *grp; + + ly_print_(pctx->out, "%*snotification %s", INDENT, notif->name); + + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_NOTIFICATION, 0, notif->exts, &flag); + yprp_iffeatures(pctx, notif->iffeatures, notif->exts, &flag); + + LY_ARRAY_FOR(notif->musts, u) { + yprp_restr(pctx, ¬if->musts[u], LY_STMT_MUST, &flag); + } + ypr_status(pctx, notif->flags, notif->exts, &flag); + ypr_description(pctx, notif->dsc, notif->exts, &flag); + ypr_reference(pctx, notif->ref, notif->exts, &flag); + + LY_ARRAY_FOR(notif->typedefs, u) { + ypr_open(pctx->out, &flag); + yprp_typedef(pctx, ¬if->typedefs[u]); + } + + LY_LIST_FOR(notif->groupings, grp) { + ypr_open(pctx->out, &flag); + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(notif->child, data) { + ypr_open(pctx->out, &flag); + yprp_node(pctx, data); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_notification(struct lys_ypr_ctx *pctx, const struct lysc_node_notif *notif) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysc_node *data; + + ly_print_(pctx->out, "%*snotification %s", INDENT, notif->name); + + LEVEL++; + yprc_extension_instances(pctx, LY_STMT_NOTIFICATION, 0, notif->exts, &flag); + + LY_ARRAY_FOR(notif->musts, u) { + yprc_must(pctx, ¬if->musts[u], &flag); + } + ypr_status(pctx, notif->flags, notif->exts, &flag); + ypr_description(pctx, notif->dsc, notif->exts, &flag); + ypr_reference(pctx, notif->ref, notif->exts, &flag); + + if (!(pctx->options & LYS_PRINT_NO_SUBSTMT)) { + LY_LIST_FOR(notif->child, data) { + ypr_open(pctx->out, &flag); + yprc_node(pctx, data); + } + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_action(struct lys_ypr_ctx *pctx, const struct lysp_node_action *action) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node_grp *grp; + + ly_print_(pctx->out, "%*s%s %s", INDENT, action->parent ? "action" : "rpc", action->name); + + LEVEL++; + yprp_extension_instances(pctx, lyplg_ext_nodetype2stmt(action->nodetype), 0, action->exts, &flag); + yprp_iffeatures(pctx, action->iffeatures, action->exts, &flag); + ypr_status(pctx, action->flags, action->exts, &flag); + ypr_description(pctx, action->dsc, action->exts, &flag); + ypr_reference(pctx, action->ref, action->exts, &flag); + + YPR_EXTRA_LINE(flag, pctx); + + LY_ARRAY_FOR(action->typedefs, u) { + ypr_open(pctx->out, &flag); + YPR_EXTRA_LINE_PRINT(pctx); + yprp_typedef(pctx, &action->typedefs[u]); + } + + YPR_EXTRA_LINE(action->typedefs, pctx); + + LY_LIST_FOR(action->groupings, grp) { + ypr_open(pctx->out, &flag); + YPR_EXTRA_LINE_PRINT(pctx); + yprp_grouping(pctx, grp); + } + + YPR_EXTRA_LINE(action->groupings, pctx); + + yprp_inout(pctx, &action->input, &flag); + yprp_inout(pctx, &action->output, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_action(struct lys_ypr_ctx *pctx, const struct lysc_node_action *action) +{ + ly_bool flag = 0; + + ly_print_(pctx->out, "%*s%s %s", INDENT, action->parent ? "action" : "rpc", action->name); + + LEVEL++; + yprc_extension_instances(pctx, lyplg_ext_nodetype2stmt(action->nodetype), 0, action->exts, &flag); + ypr_status(pctx, action->flags, action->exts, &flag); + ypr_description(pctx, action->dsc, action->exts, &flag); + ypr_reference(pctx, action->ref, action->exts, &flag); + + yprc_inout(pctx, &action->input, &flag); + yprc_inout(pctx, &action->output, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_node_common1(struct lys_ypr_ctx *pctx, const struct lysp_node *node, ly_bool *flag) +{ + ly_print_(pctx->out, "%*s%s %s%s", INDENT, lys_nodetype2str(node->nodetype), node->name, flag ? "" : " {\n"); + LEVEL++; + + yprp_extension_instances(pctx, lyplg_ext_nodetype2stmt(node->nodetype), 0, node->exts, flag); + yprp_when(pctx, lysp_node_when(node), flag); + yprp_iffeatures(pctx, node->iffeatures, node->exts, flag); +} + +static void +yprc_node_common1(struct lys_ypr_ctx *pctx, const struct lysc_node *node, ly_bool *flag) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_when **when; + + ly_print_(pctx->out, "%*s%s %s%s", INDENT, lys_nodetype2str(node->nodetype), node->name, flag ? "" : " {\n"); + LEVEL++; + + yprc_extension_instances(pctx, lyplg_ext_nodetype2stmt(node->nodetype), 0, node->exts, flag); + + when = lysc_node_when(node); + LY_ARRAY_FOR(when, u) { + yprc_when(pctx, when[u], flag); + } +} + +/* macro to unify the code */ +#define YPR_NODE_COMMON2 \ + ypr_config(pctx, node->flags, node->exts, flag); \ + if (node->nodetype & (LYS_CHOICE | LYS_LEAF | LYS_ANYDATA)) { \ + ypr_mandatory(pctx, node->flags, node->exts, flag); \ + } \ + ypr_status(pctx, node->flags, node->exts, flag); \ + ypr_description(pctx, node->dsc, node->exts, flag); \ + ypr_reference(pctx, node->ref, node->exts, flag) + +static void +yprp_node_common2(struct lys_ypr_ctx *pctx, const struct lysp_node *node, ly_bool *flag) +{ + YPR_NODE_COMMON2; +} + +static void +yprc_node_common2(struct lys_ypr_ctx *pctx, const struct lysc_node *node, ly_bool *flag) +{ + YPR_NODE_COMMON2; +} + +#undef YPR_NODE_COMMON2 + +static void +yprp_container(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node *child; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *grp; + struct lysp_node_container *cont = (struct lysp_node_container *)node; + + yprp_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(cont->musts, u) { + yprp_restr(pctx, &cont->musts[u], LY_STMT_MUST, &flag); + } + if (cont->presence) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_PRESENCE, 0, cont->presence, cont->exts); + } + + yprp_node_common2(pctx, node, &flag); + + LY_ARRAY_FOR(cont->typedefs, u) { + ypr_open(pctx->out, &flag); + yprp_typedef(pctx, &cont->typedefs[u]); + } + + LY_LIST_FOR(cont->groupings, grp) { + ypr_open(pctx->out, &flag); + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(cont->child, child) { + ypr_open(pctx->out, &flag); + yprp_node(pctx, child); + } + + LY_LIST_FOR(cont->actions, action) { + ypr_open(pctx->out, &flag); + yprp_action(pctx, action); + } + + LY_LIST_FOR(cont->notifs, notif) { + ypr_open(pctx->out, &flag); + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_container(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysc_node *child; + struct lysc_node_action *action; + struct lysc_node_notif *notif; + struct lysc_node_container *cont = (struct lysc_node_container *)node; + + yprc_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(cont->musts, u) { + yprc_must(pctx, &cont->musts[u], &flag); + } + if (cont->flags & LYS_PRESENCE) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_PRESENCE, 0, "true", cont->exts); + } + + yprc_node_common2(pctx, node, &flag); + + if (!(pctx->options & LYS_PRINT_NO_SUBSTMT)) { + LY_LIST_FOR(cont->child, child) { + ypr_open(pctx->out, &flag); + yprc_node(pctx, child); + } + + LY_LIST_FOR(cont->actions, action) { + ypr_open(pctx->out, &flag); + yprc_action(pctx, action); + } + + LY_LIST_FOR(cont->notifs, notif) { + ypr_open(pctx->out, &flag); + yprc_notification(pctx, notif); + } + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_case(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + ly_bool flag = 0; + struct lysp_node *child; + struct lysp_node_case *cas = (struct lysp_node_case *)node; + + yprp_node_common1(pctx, node, &flag); + yprp_node_common2(pctx, node, &flag); + + LY_LIST_FOR(cas->child, child) { + ypr_open(pctx->out, &flag); + yprp_node(pctx, child); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_case(struct lys_ypr_ctx *pctx, const struct lysc_node_case *cs) +{ + ly_bool flag = 0; + struct lysc_node *child; + + yprc_node_common1(pctx, &cs->node, &flag); + yprc_node_common2(pctx, &cs->node, &flag); + + if (!(pctx->options & LYS_PRINT_NO_SUBSTMT)) { + for (child = cs->child; child && child->parent == (struct lysc_node *)cs; child = child->next) { + ypr_open(pctx->out, &flag); + yprc_node(pctx, child); + } + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_choice(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + ly_bool flag = 0; + struct lysp_node *child; + struct lysp_node_choice *choice = (struct lysp_node_choice *)node; + + yprp_node_common1(pctx, node, &flag); + + if (choice->dflt.str) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, choice->dflt.str, choice->exts); + } + + yprp_node_common2(pctx, node, &flag); + + LY_LIST_FOR(choice->child, child) { + ypr_open(pctx->out, &flag); + yprp_node(pctx, child); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_choice(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + ly_bool flag = 0; + struct lysc_node_case *cs; + struct lysc_node_choice *choice = (struct lysc_node_choice *)node; + + yprc_node_common1(pctx, node, &flag); + + if (choice->dflt) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, choice->dflt->name, choice->exts); + } + + yprc_node_common2(pctx, node, &flag); + + for (cs = choice->cases; cs; cs = (struct lysc_node_case *)cs->next) { + ypr_open(pctx->out, &flag); + yprc_case(pctx, cs); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_leaf(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node_leaf *leaf = (struct lysp_node_leaf *)node; + + yprp_node_common1(pctx, node, NULL); + + yprp_type(pctx, &leaf->type); + ypr_substmt(pctx, LY_STMT_UNITS, 0, leaf->units, leaf->exts); + LY_ARRAY_FOR(leaf->musts, u) { + yprp_restr(pctx, &leaf->musts[u], LY_STMT_MUST, NULL); + } + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, leaf->dflt.str, leaf->exts); + + yprp_node_common2(pctx, node, NULL); + + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); +} + +static void +yprc_leaf(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_node_leaf *leaf = (struct lysc_node_leaf *)node; + + yprc_node_common1(pctx, node, NULL); + + yprc_type(pctx, leaf->type); + ypr_substmt(pctx, LY_STMT_UNITS, 0, leaf->units, leaf->exts); + LY_ARRAY_FOR(leaf->musts, u) { + yprc_must(pctx, &leaf->musts[u], NULL); + } + + if (leaf->dflt) { + yprc_dflt_value(pctx, node->module->ctx, leaf->dflt, leaf->exts); + } + + yprc_node_common2(pctx, node, NULL); + + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); +} + +static void +yprp_leaflist(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node_leaflist *llist = (struct lysp_node_leaflist *)node; + + yprp_node_common1(pctx, node, NULL); + + yprp_type(pctx, &llist->type); + ypr_substmt(pctx, LY_STMT_UNITS, 0, llist->units, llist->exts); + LY_ARRAY_FOR(llist->musts, u) { + yprp_restr(pctx, &llist->musts[u], LY_STMT_MUST, NULL); + } + LY_ARRAY_FOR(llist->dflts, u) { + ypr_substmt(pctx, LY_STMT_DEFAULT, u, llist->dflts[u].str, llist->exts); + } + + ypr_config(pctx, node->flags, node->exts, NULL); + + if (llist->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, llist->exts, llist->min, NULL); + } + if (llist->flags & LYS_SET_MAX) { + if (llist->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, llist->exts, llist->max, NULL); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", llist->exts); + } + } + + if (llist->flags & LYS_ORDBY_MASK) { + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, (llist->flags & LYS_ORDBY_USER) ? "user" : "system", llist->exts); + } + + ypr_status(pctx, node->flags, node->exts, NULL); + ypr_description(pctx, node->dsc, node->exts, NULL); + ypr_reference(pctx, node->ref, node->exts, NULL); + + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); +} + +static void +yprc_leaflist(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_node_leaflist *llist = (struct lysc_node_leaflist *)node; + + yprc_node_common1(pctx, node, NULL); + + yprc_type(pctx, llist->type); + ypr_substmt(pctx, LY_STMT_UNITS, 0, llist->units, llist->exts); + LY_ARRAY_FOR(llist->musts, u) { + yprc_must(pctx, &llist->musts[u], NULL); + } + LY_ARRAY_FOR(llist->dflts, u) { + yprc_dflt_value(pctx, node->module->ctx, llist->dflts[u], llist->exts); + } + + ypr_config(pctx, node->flags, node->exts, NULL); + + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, llist->exts, llist->min, NULL); + if (llist->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, llist->exts, llist->max, NULL); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", llist->exts); + } + + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, (llist->flags & LYS_ORDBY_USER) ? "user" : "system", llist->exts); + + ypr_status(pctx, node->flags, node->exts, NULL); + ypr_description(pctx, node->dsc, node->exts, NULL); + ypr_reference(pctx, node->ref, node->exts, NULL); + + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); +} + +static void +yprp_list(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node *child; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *grp; + struct lysp_node_list *list = (struct lysp_node_list *)node; + + yprp_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(list->musts, u) { + yprp_restr(pctx, &list->musts[u], LY_STMT_MUST, &flag); + } + if (list->key) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_KEY, 0, list->key, list->exts); + } + LY_ARRAY_FOR(list->uniques, u) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_UNIQUE, u, list->uniques[u].str, list->exts); + } + + ypr_config(pctx, node->flags, node->exts, &flag); + + if (list->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, list->exts, list->min, &flag); + } + if (list->flags & LYS_SET_MAX) { + if (list->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, list->exts, list->max, &flag); + } else { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", list->exts); + } + } + + if (list->flags & LYS_ORDBY_MASK) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, (list->flags & LYS_ORDBY_USER) ? "user" : "system", list->exts); + } + + ypr_status(pctx, node->flags, node->exts, &flag); + ypr_description(pctx, node->dsc, node->exts, &flag); + ypr_reference(pctx, node->ref, node->exts, &flag); + + LY_ARRAY_FOR(list->typedefs, u) { + ypr_open(pctx->out, &flag); + yprp_typedef(pctx, &list->typedefs[u]); + } + + LY_LIST_FOR(list->groupings, grp) { + ypr_open(pctx->out, &flag); + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(list->child, child) { + ypr_open(pctx->out, &flag); + yprp_node(pctx, child); + } + + LY_LIST_FOR(list->actions, action) { + ypr_open(pctx->out, &flag); + yprp_action(pctx, action); + } + + LY_LIST_FOR(list->notifs, notif) { + ypr_open(pctx->out, &flag); + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_list(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + LY_ARRAY_COUNT_TYPE u, v; + struct lysc_node_list *list = (struct lysc_node_list *)node; + + yprc_node_common1(pctx, node, NULL); + + LY_ARRAY_FOR(list->musts, u) { + yprc_must(pctx, &list->musts[u], NULL); + } + if (!(list->flags & LYS_KEYLESS)) { + ly_print_(pctx->out, "%*skey \"", INDENT); + for (struct lysc_node *key = list->child; key && key->nodetype == LYS_LEAF && (key->flags & LYS_KEY); key = key->next) { + ly_print_(pctx->out, "%s%s", u > 0 ? ", " : "", key->name); + } + ly_print_(pctx->out, "\";\n"); + } + LY_ARRAY_FOR(list->uniques, u) { + ly_print_(pctx->out, "%*sunique \"", INDENT); + LY_ARRAY_FOR(list->uniques[u], v) { + ly_print_(pctx->out, "%s%s", v > 0 ? ", " : "", list->uniques[u][v]->name); + } + ypr_close(pctx, 0); + } + + ypr_config(pctx, node->flags, node->exts, NULL); + + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, list->exts, list->min, NULL); + if (list->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, list->exts, list->max, NULL); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", list->exts); + } + + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, (list->flags & LYS_ORDBY_USER) ? "user" : "system", list->exts); + + ypr_status(pctx, node->flags, node->exts, NULL); + ypr_description(pctx, node->dsc, node->exts, NULL); + ypr_reference(pctx, node->ref, node->exts, NULL); + + if (!(pctx->options & LYS_PRINT_NO_SUBSTMT)) { + struct lysc_node *child; + struct lysc_node_action *action; + struct lysc_node_notif *notif; + + LY_LIST_FOR(list->child, child) { + yprc_node(pctx, child); + } + + LY_LIST_FOR(list->actions, action) { + yprc_action(pctx, action); + } + + LY_LIST_FOR(list->notifs, notif) { + yprc_notification(pctx, notif); + } + } + + LEVEL--; + ypr_close(pctx, 1); +} + +static void +yprp_refine(struct lys_ypr_ctx *pctx, struct lysp_refine *refine) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + + ly_print_(pctx->out, "%*srefine \"%s\"", INDENT, refine->nodeid); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_REFINE, 0, refine->exts, &flag); + yprp_iffeatures(pctx, refine->iffeatures, refine->exts, &flag); + + LY_ARRAY_FOR(refine->musts, u) { + ypr_open(pctx->out, &flag); + yprp_restr(pctx, &refine->musts[u], LY_STMT_MUST, NULL); + } + + if (refine->presence) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_PRESENCE, 0, refine->presence, refine->exts); + } + + LY_ARRAY_FOR(refine->dflts, u) { + ypr_open(pctx->out, &flag); + ypr_substmt(pctx, LY_STMT_DEFAULT, u, refine->dflts[u].str, refine->exts); + } + + ypr_config(pctx, refine->flags, refine->exts, &flag); + ypr_mandatory(pctx, refine->flags, refine->exts, &flag); + + if (refine->flags & LYS_SET_MIN) { + ypr_open(pctx->out, &flag); + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, refine->exts, refine->min, NULL); + } + if (refine->flags & LYS_SET_MAX) { + ypr_open(pctx->out, &flag); + if (refine->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, refine->exts, refine->max, NULL); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", refine->exts); + } + } + + ypr_description(pctx, refine->dsc, refine->exts, &flag); + ypr_reference(pctx, refine->ref, refine->exts, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_augment(struct lys_ypr_ctx *pctx, const struct lysp_node_augment *aug) +{ + struct lysp_node *child; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + + ly_print_(pctx->out, "%*saugment \"%s\" {\n", INDENT, aug->nodeid); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_AUGMENT, 0, aug->exts, NULL); + yprp_when(pctx, aug->when, NULL); + yprp_iffeatures(pctx, aug->iffeatures, aug->exts, NULL); + ypr_status(pctx, aug->flags, aug->exts, NULL); + ypr_description(pctx, aug->dsc, aug->exts, NULL); + ypr_reference(pctx, aug->ref, aug->exts, NULL); + + LY_LIST_FOR(aug->child, child) { + yprp_node(pctx, child); + } + + LY_LIST_FOR(aug->actions, action) { + yprp_action(pctx, action); + } + + LY_LIST_FOR(aug->notifs, notif) { + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, 1); +} + +static void +yprp_uses(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node_uses *uses = (struct lysp_node_uses *)node; + struct lysp_node_augment *aug; + + yprp_node_common1(pctx, node, &flag); + yprp_node_common2(pctx, node, &flag); + + LY_ARRAY_FOR(uses->refines, u) { + ypr_open(pctx->out, &flag); + yprp_refine(pctx, &uses->refines[u]); + } + + LY_LIST_FOR(uses->augments, aug) { + ypr_open(pctx->out, &flag); + yprp_augment(pctx, aug); + } + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_anydata(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysp_node_anydata *any = (struct lysp_node_anydata *)node; + + yprp_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(any->musts, u) { + ypr_open(pctx->out, &flag); + yprp_restr(pctx, &any->musts[u], LY_STMT_MUST, NULL); + } + + yprp_node_common2(pctx, node, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprc_anydata(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + ly_bool flag = 0; + struct lysc_node_anydata *any = (struct lysc_node_anydata *)node; + + yprc_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(any->musts, u) { + ypr_open(pctx->out, &flag); + yprc_must(pctx, &any->musts[u], NULL); + } + + yprc_node_common2(pctx, node, &flag); + + LEVEL--; + ypr_close(pctx, flag); +} + +static void +yprp_node(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + switch (node->nodetype) { + case LYS_CONTAINER: + yprp_container(pctx, node); + break; + case LYS_CHOICE: + yprp_choice(pctx, node); + break; + case LYS_LEAF: + yprp_leaf(pctx, node); + break; + case LYS_LEAFLIST: + yprp_leaflist(pctx, node); + break; + case LYS_LIST: + yprp_list(pctx, node); + break; + case LYS_USES: + yprp_uses(pctx, node); + break; + case LYS_ANYXML: + case LYS_ANYDATA: + yprp_anydata(pctx, node); + break; + case LYS_CASE: + yprp_case(pctx, node); + break; + default: + break; + } +} + +static void +yprc_node(struct lys_ypr_ctx *pctx, const struct lysc_node *node) +{ + switch (node->nodetype) { + case LYS_CONTAINER: + yprc_container(pctx, node); + break; + case LYS_CHOICE: + yprc_choice(pctx, node); + break; + case LYS_LEAF: + yprc_leaf(pctx, node); + break; + case LYS_LEAFLIST: + yprc_leaflist(pctx, node); + break; + case LYS_LIST: + yprc_list(pctx, node); + break; + case LYS_ANYXML: + case LYS_ANYDATA: + yprc_anydata(pctx, node); + break; + default: + break; + } +} + +static void +yprp_deviation(struct lys_ypr_ctx *pctx, const struct lysp_deviation *deviation) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_deviate_add *add; + struct lysp_deviate_rpl *rpl; + struct lysp_deviate_del *del; + struct lysp_deviate *elem; + + ly_print_(pctx->out, "%*sdeviation \"%s\" {\n", INDENT, deviation->nodeid); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATION, 0, deviation->exts, NULL); + ypr_description(pctx, deviation->dsc, deviation->exts, NULL); + ypr_reference(pctx, deviation->ref, deviation->exts, NULL); + + LY_LIST_FOR(deviation->deviates, elem) { + ly_print_(pctx->out, "%*sdeviate ", INDENT); + if (elem->mod == LYS_DEV_NOT_SUPPORTED) { + if (elem->exts) { + ly_print_(pctx->out, "not-supported {\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, elem->exts, NULL); + } else { + ly_print_(pctx->out, "not-supported;\n"); + continue; + } + } else if (elem->mod == LYS_DEV_ADD) { + add = (struct lysp_deviate_add *)elem; + ly_print_(pctx->out, "add {\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, add->exts, NULL); + ypr_substmt(pctx, LY_STMT_UNITS, 0, add->units, add->exts); + LY_ARRAY_FOR(add->musts, u) { + yprp_restr(pctx, &add->musts[u], LY_STMT_MUST, NULL); + } + LY_ARRAY_FOR(add->uniques, u) { + ypr_substmt(pctx, LY_STMT_UNIQUE, u, add->uniques[u].str, add->exts); + } + LY_ARRAY_FOR(add->dflts, u) { + ypr_substmt(pctx, LY_STMT_DEFAULT, u, add->dflts[u].str, add->exts); + } + ypr_config(pctx, add->flags, add->exts, NULL); + ypr_mandatory(pctx, add->flags, add->exts, NULL); + if (add->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, add->exts, add->min, NULL); + } + if (add->flags & LYS_SET_MAX) { + if (add->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, add->exts, add->max, NULL); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", add->exts); + } + } + } else if (elem->mod == LYS_DEV_REPLACE) { + rpl = (struct lysp_deviate_rpl *)elem; + ly_print_(pctx->out, "replace {\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, rpl->exts, NULL); + if (rpl->type) { + yprp_type(pctx, rpl->type); + } + ypr_substmt(pctx, LY_STMT_UNITS, 0, rpl->units, rpl->exts); + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, rpl->dflt.str, rpl->exts); + ypr_config(pctx, rpl->flags, rpl->exts, NULL); + ypr_mandatory(pctx, rpl->flags, rpl->exts, NULL); + if (rpl->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, rpl->exts, rpl->min, NULL); + } + if (rpl->flags & LYS_SET_MAX) { + if (rpl->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, rpl->exts, rpl->max, NULL); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", rpl->exts); + } + } + } else if (elem->mod == LYS_DEV_DELETE) { + del = (struct lysp_deviate_del *)elem; + ly_print_(pctx->out, "delete {\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, del->exts, NULL); + ypr_substmt(pctx, LY_STMT_UNITS, 0, del->units, del->exts); + LY_ARRAY_FOR(del->musts, u) { + yprp_restr(pctx, &del->musts[u], LY_STMT_MUST, NULL); + } + LY_ARRAY_FOR(del->uniques, u) { + ypr_substmt(pctx, LY_STMT_UNIQUE, u, del->uniques[u].str, del->exts); + } + LY_ARRAY_FOR(del->dflts, u) { + ypr_substmt(pctx, LY_STMT_DEFAULT, u, del->dflts[u].str, del->exts); + } + } + + LEVEL--; + ypr_close(pctx, 1); + } + + LEVEL--; + ypr_close(pctx, 1); +} + +static void +yang_print_parsed_linkage(struct lys_ypr_ctx *pctx, const struct lysp_module *modp) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(modp->imports, u) { + if (modp->imports[u].flags & LYS_INTERNAL) { + continue; + } + + YPR_EXTRA_LINE_PRINT(pctx); + ly_print_(pctx->out, "%*simport %s {\n", INDENT, modp->imports[u].name); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_IMPORT, 0, modp->imports[u].exts, NULL); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, modp->imports[u].prefix, modp->imports[u].exts); + if (modp->imports[u].rev[0]) { + ypr_substmt(pctx, LY_STMT_REVISION_DATE, 0, modp->imports[u].rev, modp->imports[u].exts); + } + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, modp->imports[u].dsc, modp->imports[u].exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, modp->imports[u].ref, modp->imports[u].exts); + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); + } + YPR_EXTRA_LINE(modp->imports, pctx); + + LY_ARRAY_FOR(modp->includes, u) { + if (modp->includes[u].injected) { + /* do not print the includes injected from submodules */ + continue; + } + YPR_EXTRA_LINE_PRINT(pctx); + if (modp->includes[u].rev[0] || modp->includes[u].dsc || modp->includes[u].ref || modp->includes[u].exts) { + ly_print_(pctx->out, "%*sinclude %s {\n", INDENT, modp->includes[u].name); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_INCLUDE, 0, modp->includes[u].exts, NULL); + if (modp->includes[u].rev[0]) { + ypr_substmt(pctx, LY_STMT_REVISION_DATE, 0, modp->includes[u].rev, modp->includes[u].exts); + } + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, modp->includes[u].dsc, modp->includes[u].exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, modp->includes[u].ref, modp->includes[u].exts); + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); + } else { + ly_print_(pctx->out, "\n%*sinclude \"%s\";\n", INDENT, modp->includes[u].name); + } + } + YPR_EXTRA_LINE(modp->includes, pctx); +} + +static void +yang_print_parsed_body(struct lys_ypr_ctx *pctx, const struct lysp_module *modp) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node *data; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *grp; + struct lysp_node_augment *aug; + + LY_ARRAY_FOR(modp->extensions, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_extension(pctx, &modp->extensions[u]); + } + + YPR_EXTRA_LINE(modp->extensions, pctx); + + if (modp->exts) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_extension_instances(pctx, LY_STMT_MODULE, 0, modp->exts, NULL); + } + + YPR_EXTRA_LINE(modp->exts, pctx); + + LY_ARRAY_FOR(modp->features, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_feature(pctx, &modp->features[u]); + YPR_EXTRA_LINE(1, pctx); + } + + YPR_EXTRA_LINE(modp->features, pctx); + + LY_ARRAY_FOR(modp->identities, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_identity(pctx, &modp->identities[u]); + YPR_EXTRA_LINE(1, pctx); + } + + YPR_EXTRA_LINE(modp->identities, pctx); + + LY_ARRAY_FOR(modp->typedefs, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_typedef(pctx, &modp->typedefs[u]); + YPR_EXTRA_LINE(1, pctx); + } + + YPR_EXTRA_LINE(modp->typedefs, pctx); + + LY_LIST_FOR(modp->groupings, grp) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_grouping(pctx, grp); + YPR_EXTRA_LINE(1, pctx); + } + + YPR_EXTRA_LINE(modp->groupings, pctx); + + LY_LIST_FOR(modp->data, data) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_node(pctx, data); + } + + YPR_EXTRA_LINE(modp->data, pctx); + + LY_LIST_FOR(modp->augments, aug) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_augment(pctx, aug); + } + + YPR_EXTRA_LINE(modp->augments, pctx); + + LY_LIST_FOR(modp->rpcs, action) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_action(pctx, action); + } + + YPR_EXTRA_LINE(modp->rpcs, pctx); + + LY_LIST_FOR(modp->notifs, notif) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_notification(pctx, notif); + } + + YPR_EXTRA_LINE(modp->notifs, pctx); + + LY_ARRAY_FOR(modp->deviations, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_deviation(pctx, &modp->deviations[u]); + } + + YPR_EXTRA_LINE(modp->deviations, pctx); +} + +LY_ERR +yang_print_parsed_module(struct ly_out *out, const struct lysp_module *modp, uint32_t options) +{ + LY_ARRAY_COUNT_TYPE u; + const struct lys_module *module = modp->mod; + struct lys_ypr_ctx pctx_ = { + .out = out, + .level = 0, + .options = options, + .module = module, + .schema = LYS_YPR_PARSED + }, *pctx = &pctx_; + + ly_print_(pctx->out, "%*smodule %s {\n", INDENT, module->name); + LEVEL++; + + /* module-header-stmts */ + if (modp->version) { + ypr_substmt(pctx, LY_STMT_YANG_VERSION, 0, modp->version == LYS_VERSION_1_1 ? "1.1" : "1", modp->exts); + } + ypr_substmt(pctx, LY_STMT_NAMESPACE, 0, module->ns, modp->exts); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, module->prefix, modp->exts); + + YPR_EXTRA_LINE(1, pctx); + + /* linkage-stmts (import/include) */ + yang_print_parsed_linkage(pctx, modp); + + /* meta-stmts */ + if (module->org || module->contact || module->dsc || module->ref) { + YPR_EXTRA_LINE_PRINT(pctx); + } + ypr_substmt(pctx, LY_STMT_ORGANIZATION, 0, module->org, modp->exts); + ypr_substmt(pctx, LY_STMT_CONTACT, 0, module->contact, modp->exts); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, module->dsc, modp->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, module->ref, modp->exts); + + YPR_EXTRA_LINE(module->org || module->contact || module->dsc || module->ref, pctx); + + /* revision-stmts */ + LY_ARRAY_FOR(modp->revs, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_revision(pctx, &modp->revs[u]); + } + + YPR_EXTRA_LINE(modp->revs, pctx); + + /* body-stmts */ + yang_print_parsed_body(pctx, modp); + + LEVEL--; + ly_print_(out, "%*s}\n", INDENT); + ly_print_flush(out); + + return LY_SUCCESS; +} + +static void +yprp_belongsto(struct lys_ypr_ctx *pctx, const struct lysp_submodule *submodp) +{ + ly_print_(pctx->out, "%*sbelongs-to %s {\n", INDENT, submodp->mod->name); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_BELONGS_TO, 0, submodp->exts, NULL); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, submodp->prefix, submodp->exts); + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); +} + +LY_ERR +yang_print_parsed_submodule(struct ly_out *out, const struct lysp_submodule *submodp, uint32_t options) +{ + LY_ARRAY_COUNT_TYPE u; + struct lys_ypr_ctx pctx_ = { + .out = out, + .level = 0, + .options = options, + .module = submodp->mod, + .schema = LYS_YPR_PARSED + }, *pctx = &pctx_; + + ly_print_(pctx->out, "%*ssubmodule %s {\n", INDENT, submodp->name); + LEVEL++; + + /* submodule-header-stmts */ + if (submodp->version) { + ypr_substmt(pctx, LY_STMT_YANG_VERSION, 0, submodp->version == LYS_VERSION_1_1 ? "1.1" : "1", submodp->exts); + } + + yprp_belongsto(pctx, submodp); + + YPR_EXTRA_LINE(1, pctx); + + /* linkage-stmts (import/include) */ + yang_print_parsed_linkage(pctx, (struct lysp_module *)submodp); + + /* meta-stmts */ + if (submodp->org || submodp->contact || submodp->dsc || submodp->ref) { + YPR_EXTRA_LINE_PRINT(pctx); + } + ypr_substmt(pctx, LY_STMT_ORGANIZATION, 0, submodp->org, submodp->exts); + ypr_substmt(pctx, LY_STMT_CONTACT, 0, submodp->contact, submodp->exts); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, submodp->dsc, submodp->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, submodp->ref, submodp->exts); + + YPR_EXTRA_LINE(submodp->org || submodp->contact || submodp->dsc || submodp->ref, pctx); + + /* revision-stmts */ + LY_ARRAY_FOR(submodp->revs, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprp_revision(pctx, &submodp->revs[u]); + } + + YPR_EXTRA_LINE(submodp->revs, pctx); + + /* body-stmts */ + yang_print_parsed_body(pctx, (struct lysp_module *)submodp); + + LEVEL--; + ly_print_(out, "%*s}\n", INDENT); + ly_print_flush(out); + + return LY_SUCCESS; +} + +LY_ERR +yang_print_compiled_node(struct ly_out *out, const struct lysc_node *node, uint32_t options) +{ + struct lys_ypr_ctx pctx_ = { + .out = out, + .level = 0, + .options = options, + .module = node->module, + .schema = LYS_YPR_COMPILED + }, *pctx = &pctx_; + + yprc_node(pctx, node); + + ly_print_flush(out); + return LY_SUCCESS; +} + +LY_ERR +yang_print_compiled(struct ly_out *out, const struct lys_module *module, uint32_t options) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_module *modc = module->compiled; + struct lys_ypr_ctx pctx_ = { + .out = out, + .level = 0, + .options = options, + .module = module, + .schema = LYS_YPR_COMPILED + }, *pctx = &pctx_; + + ly_print_(pctx->out, "%*smodule %s {\n", INDENT, module->name); + LEVEL++; + + /* module-header-stmts */ + ypr_substmt(pctx, LY_STMT_NAMESPACE, 0, module->ns, modc->exts); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, module->prefix, modc->exts); + + YPR_EXTRA_LINE(1, pctx); + + /* no linkage-stmts */ + + /* meta-stmts */ + if (module->org || module->contact || module->dsc || module->ref) { + YPR_EXTRA_LINE_PRINT(pctx); + } + ypr_substmt(pctx, LY_STMT_ORGANIZATION, 0, module->org, modc->exts); + ypr_substmt(pctx, LY_STMT_CONTACT, 0, module->contact, modc->exts); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, module->dsc, modc->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, module->ref, modc->exts); + + YPR_EXTRA_LINE(module->org || module->contact || module->dsc || module->ref, pctx); + + /* revision-stmts */ + if (module->revision) { + YPR_EXTRA_LINE_PRINT(pctx); + ly_print_(pctx->out, "%*srevision %s;\n", INDENT, module->revision); + YPR_EXTRA_LINE(1, pctx); + } + + /* body-stmts */ + if (modc->exts) { + YPR_EXTRA_LINE_PRINT(pctx); + yprc_extension_instances(pctx, LY_STMT_MODULE, 0, module->compiled->exts, NULL); + YPR_EXTRA_LINE(1, pctx); + } + + LY_ARRAY_FOR(module->identities, u) { + YPR_EXTRA_LINE_PRINT(pctx); + yprc_identity(pctx, &module->identities[u]); + YPR_EXTRA_LINE(1, pctx); + } + + if (!(pctx->options & LYS_PRINT_NO_SUBSTMT)) { + struct lysc_node *data; + struct lysc_node_action *rpc; + struct lysc_node_notif *notif; + + LY_LIST_FOR(modc->data, data) { + YPR_EXTRA_LINE_PRINT(pctx); + yprc_node(pctx, data); + } + + YPR_EXTRA_LINE(modc->data, pctx); + + LY_LIST_FOR(modc->rpcs, rpc) { + YPR_EXTRA_LINE_PRINT(pctx); + yprc_action(pctx, rpc); + } + + YPR_EXTRA_LINE(modc->rpcs, pctx); + + LY_LIST_FOR(modc->notifs, notif) { + YPR_EXTRA_LINE_PRINT(pctx); + yprc_notification(pctx, notif); + } + + YPR_EXTRA_LINE(modc->notifs, pctx); + } + + LEVEL--; + ly_print_(out, "%*s}\n", INDENT); + ly_print_flush(out); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF void +lyplg_ext_print_info_extension_instance(struct lyspr_ctx *ctx, const struct lysc_ext_instance *ext, ly_bool *flag) +{ + struct lys_ypr_ctx *pctx = (struct lys_ypr_ctx *)ctx; + LY_ARRAY_COUNT_TYPE u, v; + ly_bool data_printed = 0; + + LY_ARRAY_FOR(ext->substmts, u) { + switch (ext->substmts[u].stmt) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_USES: { + const struct lysc_node *node; + + if (data_printed) { + break; + } + + LY_LIST_FOR(*(const struct lysc_node **)ext->substmts[u].storage, node) { + ypr_open(pctx->out, flag); + if (ext->substmts[u].stmt == LY_STMT_NOTIFICATION) { + yprc_notification(pctx, (struct lysc_node_notif *)node); + } else if (ext->substmts[u].stmt & (LY_STMT_INPUT | LY_STMT_OUTPUT)) { + yprc_inout(pctx, (struct lysc_node_action_inout *)node, flag); + } else if (ext->substmts[u].stmt & (LY_STMT_ACTION | LY_STMT_RPC)) { + yprc_action(pctx, (struct lysc_node_action *)node); + } else { + yprc_node(pctx, node); + } + } + + /* all data nodes are stored in a linked list so all were printed */ + data_printed = 1; + break; + } + case LY_STMT_ARGUMENT: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_KEY: + case LY_STMT_MODIFIER: + case LY_STMT_NAMESPACE: + case LY_STMT_ORGANIZATION: + case LY_STMT_PRESENCE: + case LY_STMT_REFERENCE: + case LY_STMT_UNITS: + if (*(const char **)ext->substmts[u].storage) { + ypr_open(pctx->out, flag); + ypr_substmt(pctx, ext->substmts[u].stmt, 0, *(const char **)ext->substmts[u].storage, ext->exts); + } + break; + case LY_STMT_BIT: + case LY_STMT_ENUM: { + const struct lysc_type_bitenum_item *items = *(struct lysc_type_bitenum_item **)ext->substmts[u].storage; + + yprc_bits_enum(pctx, items, ext->substmts[u].stmt == LY_STMT_BIT ? LY_TYPE_BITS : LY_TYPE_ENUM, flag); + break; + } + case LY_STMT_CONFIG: + ypr_config(pctx, *(uint16_t *)ext->substmts[u].storage, ext->exts, flag); + break; + case LY_STMT_EXTENSION_INSTANCE: + yprc_extension_instances(pctx, LY_STMT_EXTENSION_INSTANCE, 0, + *(struct lysc_ext_instance **)ext->substmts[u].storage, flag); + break; + case LY_STMT_FRACTION_DIGITS: + if (*(uint8_t *)ext->substmts[u].storage) { + ypr_unsigned(pctx, LY_STMT_FRACTION_DIGITS, 0, ext->exts, *(uint8_t *)ext->substmts[u].storage, flag); + } + break; + case LY_STMT_IDENTITY: { + const struct lysc_ident *idents = *(struct lysc_ident **)ext->substmts[u].storage; + + LY_ARRAY_FOR(idents, v) { + yprc_identity(pctx, &idents[v]); + } + break; + } + case LY_STMT_LENGTH: + if (*(struct lysc_range **)ext->substmts[u].storage) { + yprc_range(pctx, *(struct lysc_range **)ext->substmts[u].storage, LY_TYPE_STRING, flag); + } + break; + case LY_STMT_MANDATORY: + ypr_mandatory(pctx, *(uint16_t *)ext->substmts[u].storage, ext->exts, flag); + break; + case LY_STMT_MAX_ELEMENTS: { + uint32_t max = *(uint32_t *)ext->substmts[u].storage; + + if (max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, ext->exts, max, flag); + } else { + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", ext->exts); + } + break; + } + case LY_STMT_MIN_ELEMENTS: + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, ext->exts, *(uint32_t *)ext->substmts[u].storage, flag); + break; + case LY_STMT_ORDERED_BY: + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, + (*(uint16_t *)ext->substmts[u].storage & LYS_ORDBY_USER) ? "user" : "system", ext->exts); + break; + case LY_STMT_MUST: { + const struct lysc_must *musts = *(struct lysc_must **)ext->substmts[u].storage; + + LY_ARRAY_FOR(musts, v) { + yprc_must(pctx, &musts[v], flag); + } + break; + } + case LY_STMT_PATTERN: { + const struct lysc_pattern *patterns = *(struct lysc_pattern **)ext->substmts[u].storage; + + LY_ARRAY_FOR(patterns, v) { + yprc_pattern(pctx, &patterns[v], flag); + } + break; + } + case LY_STMT_POSITION: + if (*(int64_t *)ext->substmts[u].storage) { + ypr_unsigned(pctx, ext->substmts[u].stmt, 0, ext->exts, *(int64_t *)ext->substmts[u].storage, flag); + } + break; + case LY_STMT_VALUE: + if (*(int64_t *)ext->substmts[u].storage) { + ypr_signed(pctx, ext->substmts[u].stmt, 0, ext->exts, *(int64_t *)ext->substmts[u].storage, flag); + } + break; + case LY_STMT_RANGE: + if (*(struct lysc_range **)ext->substmts[u].storage) { + yprc_range(pctx, *(struct lysc_range **)ext->substmts[u].storage, LY_TYPE_UINT64, flag); + } + break; + case LY_STMT_REQUIRE_INSTANCE: + ypr_open(pctx->out, flag); + ypr_substmt(pctx, LY_STMT_REQUIRE_INSTANCE, 0, *(uint8_t *)ext->substmts[u].storage ? "true" : "false", + ext->exts); + break; + case LY_STMT_STATUS: + ypr_status(pctx, *(uint16_t *)ext->substmts[u].storage, ext->exts, flag); + break; + case LY_STMT_TYPE: + if (*(const struct lysc_type **)ext->substmts[u].storage) { + ypr_open(pctx->out, flag); + yprc_type(pctx, *(const struct lysc_type **)ext->substmts[u].storage); + } + break; + case LY_STMT_WHEN: + yprc_when(pctx, *(struct lysc_when **)ext->substmts[u].storage, flag); + break; + case LY_STMT_AUGMENT: + case LY_STMT_BASE: + case LY_STMT_BELONGS_TO: + case LY_STMT_DEFAULT: + case LY_STMT_DEVIATE: + case LY_STMT_DEVIATION: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IF_FEATURE: + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + case LY_STMT_MODULE: + case LY_STMT_PATH: + case LY_STMT_PREFIX: + case LY_STMT_REFINE: + case LY_STMT_REVISION: + case LY_STMT_REVISION_DATE: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPEDEF: + case LY_STMT_UNIQUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + /* nothing to do */ + break; + default: + LOGINT(pctx->module->ctx); + break; + } + } +} diff --git a/src/printer_yin.c b/src/printer_yin.c new file mode 100644 index 0000000..74e0f0e --- /dev/null +++ b/src/printer_yin.c @@ -0,0 +1,1501 @@ +/** + * @file printer_yin.c + * @author Fred Gan + * @author Michal Vasko + * @brief YIN printer + * + * Copyright (c) 2015 - 2022 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 + +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "log.h" +#include "out.h" +#include "out_internal.h" +#include "printer_internal.h" +#include "tree.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xml.h" +#include "xpath.h" + +/** + * @brief YIN printer context. + */ +struct lys_ypr_ctx { + union { + struct { + struct ly_out *out; /**< output specification */ + uint16_t level; /**< current indentation level: 0 - no formatting, >= 1 indentation levels */ + uint32_t options; /**< Schema output options (see @ref schemaprinterflags). */ + const struct lys_module *module; /**< schema to print */ + }; + struct lyspr_ctx printer_ctx; + }; + + /* YIN printer specific members */ +}; + +static void +ypr_open(struct lys_ypr_ctx *pctx, const char *elem_name, const char *attr_name, const char *attr_value, int8_t flag) +{ + ly_print_(pctx->out, "%*s<%s", INDENT, elem_name); + + if (attr_name) { + ly_print_(pctx->out, " %s=\"", attr_name); + lyxml_dump_text(pctx->out, attr_value, 1); + ly_print_(pctx->out, "\"%s", flag == -1 ? "/>\n" : flag == 1 ? ">\n" : ""); + } else if (flag) { + ly_print_(pctx->out, flag == -1 ? "/>\n" : ">\n"); + } +} + +static void +ypr_close(struct lys_ypr_ctx *pctx, const char *elem_name, int8_t flag) +{ + if (flag) { + ly_print_(pctx->out, "%*s\n", INDENT, elem_name); + } else { + ly_print_(pctx->out, "/>\n"); + } +} + +/* + * par_close_flag + * 0 - parent not yet closed, printing >\n, setting flag to 1 + * 1 or NULL - parent already closed, do nothing + */ +static void +ypr_close_parent(struct lys_ypr_ctx *pctx, int8_t *par_close_flag) +{ + if (par_close_flag && !(*par_close_flag)) { + (*par_close_flag) = 1; + ly_print_(pctx->out, ">\n"); + } +} + +static void +ypr_yin_arg(struct lys_ypr_ctx *pctx, const char *arg, const char *text) +{ + ly_print_(pctx->out, "%*s<%s>", INDENT, arg); + lyxml_dump_text(pctx->out, text, 0); + ly_print_(pctx->out, "\n", arg); +} + +static void +yprp_stmt(struct lys_ypr_ctx *pctx, struct lysp_stmt *stmt) +{ + struct lysp_stmt *childstmt; + int8_t flag = stmt->child ? 1 : -1; + + /* TODO: + the extension instance substatements in extension instances (LY_STMT_EXTENSION_INSTANCE) + cannot find the compiled information, so it is needed to be done, + currently it is ignored */ + if (lys_stmt_str(stmt->kw)) { + if (lys_stmt_flags(stmt->kw) & LY_STMT_FLAG_YIN) { + ypr_open(pctx, stmt->stmt, NULL, NULL, flag); + ypr_yin_arg(pctx, lys_stmt_arg(stmt->kw), stmt->arg); + } else { + ypr_open(pctx, stmt->stmt, lys_stmt_arg(stmt->kw), stmt->arg, flag); + } + } + + if (stmt->child) { + LEVEL++; + LY_LIST_FOR(stmt->child, childstmt) { + yprp_stmt(pctx, childstmt); + } + LEVEL--; + ypr_close(pctx, stmt->stmt, flag); + } +} + +static void +yprp_extension_instance(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, + struct lysp_ext_instance *ext, int8_t *flag) +{ + struct lysp_stmt *stmt; + int8_t inner_flag; + + if ((ext->flags & LYS_INTERNAL) || (ext->parent_stmt != substmt) || (ext->parent_stmt_index != substmt_index)) { + return; + } + + ypr_close_parent(pctx, flag); + inner_flag = 0; + + ypr_open(pctx, ext->name, (ext->def->flags & LYS_YINELEM_TRUE) ? NULL : ext->def->argname, ext->argument, inner_flag); + LEVEL++; + if (ext->def->flags & LYS_YINELEM_TRUE) { + const char *prefix, *name, *id; + size_t prefix_len, name_len; + + ypr_close_parent(pctx, &inner_flag); + + /* we need to use the same namespace as for the extension instance element */ + id = ext->name; + ly_parse_nodeid(&id, &prefix, &prefix_len, &name, &name_len); + ly_print_(pctx->out, "%*s<%.*s:%s>", INDENT, (int)prefix_len, prefix, ext->def->argname); + lyxml_dump_text(pctx->out, ext->argument, 0); + ly_print_(pctx->out, "\n", (int)prefix_len, prefix, ext->def->argname); + } + LY_LIST_FOR(ext->child, stmt) { + if (stmt->flags & (LYS_YIN_ATTR | LYS_YIN_ARGUMENT)) { + continue; + } + + ypr_close_parent(pctx, &inner_flag); + yprp_stmt(pctx, stmt); + } + LEVEL--; + ypr_close(pctx, ext->name, inner_flag); +} + +static void +yprp_extension_instances(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, + struct lysp_ext_instance *exts, int8_t *flag) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(exts, u) { + yprp_extension_instance(pctx, substmt, substmt_index, &exts[u], flag); + } +} + +static void +ypr_substmt(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, const char *text, void *exts) +{ + int8_t extflag = 0; + + if (!text) { + /* nothing to print */ + return; + } + + if (lys_stmt_flags(substmt) & LY_STMT_FLAG_YIN) { + extflag = 1; + ypr_open(pctx, lys_stmt_str(substmt), NULL, NULL, extflag); + } else { + ypr_open(pctx, lys_stmt_str(substmt), lys_stmt_arg(substmt), text, extflag); + } + + LEVEL++; + yprp_extension_instances(pctx, substmt, substmt_index, exts, &extflag); + + /* argument as yin-element */ + if (lys_stmt_flags(substmt) & LY_STMT_FLAG_YIN) { + ypr_yin_arg(pctx, lys_stmt_arg(substmt), text); + } + + LEVEL--; + ypr_close(pctx, lys_stmt_str(substmt), extflag); +} + +static void +ypr_unsigned(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, void *exts, unsigned long attr_value) +{ + char *str; + + if (asprintf(&str, "%lu", attr_value) == -1) { + LOGMEM(pctx->module->ctx); + return; + } + ypr_substmt(pctx, substmt, substmt_index, str, exts); + free(str); +} + +static void +ypr_signed(struct lys_ypr_ctx *pctx, enum ly_stmt substmt, uint8_t substmt_index, void *exts, long attr_value) +{ + char *str; + + if (asprintf(&str, "%ld", attr_value) == -1) { + LOGMEM(pctx->module->ctx); + return; + } + ypr_substmt(pctx, substmt, substmt_index, str, exts); + free(str); +} + +static void +yprp_revision(struct lys_ypr_ctx *pctx, const struct lysp_revision *rev) +{ + if (rev->dsc || rev->ref || rev->exts) { + ypr_open(pctx, "revision", "date", rev->date, 1); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_REVISION, 0, rev->exts, NULL); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, rev->dsc, rev->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, rev->ref, rev->exts); + LEVEL--; + ypr_close(pctx, "revision", 1); + } else { + ypr_open(pctx, "revision", "date", rev->date, -1); + } +} + +static void +ypr_mandatory(struct lys_ypr_ctx *pctx, uint16_t flags, void *exts, int8_t *flag) +{ + if (flags & LYS_MAND_MASK) { + ypr_close_parent(pctx, flag); + ypr_substmt(pctx, LY_STMT_MANDATORY, 0, (flags & LYS_MAND_TRUE) ? "true" : "false", exts); + } +} + +static void +ypr_config(struct lys_ypr_ctx *pctx, uint16_t flags, void *exts, int8_t *flag) +{ + if (flags & LYS_CONFIG_MASK) { + ypr_close_parent(pctx, flag); + ypr_substmt(pctx, LY_STMT_CONFIG, 0, (flags & LYS_CONFIG_W) ? "true" : "false", exts); + } +} + +static void +ypr_status(struct lys_ypr_ctx *pctx, uint16_t flags, void *exts, int8_t *flag) +{ + const char *status = NULL; + + if (flags & LYS_STATUS_CURR) { + ypr_close_parent(pctx, flag); + status = "current"; + } else if (flags & LYS_STATUS_DEPRC) { + ypr_close_parent(pctx, flag); + status = "deprecated"; + } else if (flags & LYS_STATUS_OBSLT) { + ypr_close_parent(pctx, flag); + status = "obsolete"; + } + + ypr_substmt(pctx, LY_STMT_STATUS, 0, status, exts); +} + +static void +ypr_description(struct lys_ypr_ctx *pctx, const char *dsc, void *exts, int8_t *flag) +{ + if (dsc) { + ypr_close_parent(pctx, flag); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, dsc, exts); + } +} + +static void +ypr_reference(struct lys_ypr_ctx *pctx, const char *ref, void *exts, int8_t *flag) +{ + if (ref) { + ypr_close_parent(pctx, flag); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, ref, exts); + } +} + +static void +yprp_iffeatures(struct lys_ypr_ctx *pctx, struct lysp_qname *iffs, struct lysp_ext_instance *exts, int8_t *flag) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t extflag; + + LY_ARRAY_FOR(iffs, u) { + ypr_close_parent(pctx, flag); + extflag = 0; + + ly_print_(pctx->out, "%*sout, "\"/>\n"); + } +} + +static void +yprp_extension(struct lys_ypr_ctx *pctx, const struct lysp_ext *ext) +{ + int8_t flag = 0, flag2 = 0; + LY_ARRAY_COUNT_TYPE u; + + ypr_open(pctx, "extension", "name", ext->name, flag); + LEVEL++; + + if (ext->exts) { + ypr_close_parent(pctx, &flag); + yprp_extension_instances(pctx, LY_STMT_EXTENSION, 0, ext->exts, &flag); + } + + if (ext->argname) { + ypr_close_parent(pctx, &flag); + ypr_open(pctx, "argument", "name", ext->argname, flag2); + + LEVEL++; + if (ext->exts) { + u = -1; + while ((u = lysp_ext_instance_iter(ext->exts, u + 1, LY_STMT_ARGUMENT)) != LY_ARRAY_COUNT(ext->exts)) { + ypr_close_parent(pctx, &flag2); + yprp_extension_instance(pctx, LY_STMT_ARGUMENT, 0, &ext->exts[u], &flag2); + } + } + if ((ext->flags & LYS_YINELEM_MASK) || + (ext->exts && (lysp_ext_instance_iter(ext->exts, 0, LY_STMT_YIN_ELEMENT) != LY_ARRAY_COUNT(ext->exts)))) { + ypr_close_parent(pctx, &flag2); + ypr_substmt(pctx, LY_STMT_YIN_ELEMENT, 0, (ext->flags & LYS_YINELEM_TRUE) ? "true" : "false", ext->exts); + } + LEVEL--; + ypr_close(pctx, "argument", flag2); + } + + ypr_status(pctx, ext->flags, ext->exts, &flag); + ypr_description(pctx, ext->dsc, ext->exts, &flag); + ypr_reference(pctx, ext->ref, ext->exts, &flag); + + LEVEL--; + ypr_close(pctx, "extension", flag); +} + +static void +yprp_feature(struct lys_ypr_ctx *pctx, const struct lysp_feature *feat) +{ + int8_t flag = 0; + + ypr_open(pctx, "feature", "name", feat->name, flag); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_FEATURE, 0, feat->exts, &flag); + yprp_iffeatures(pctx, feat->iffeatures, feat->exts, &flag); + ypr_status(pctx, feat->flags, feat->exts, &flag); + ypr_description(pctx, feat->dsc, feat->exts, &flag); + ypr_reference(pctx, feat->ref, feat->exts, &flag); + LEVEL--; + ypr_close(pctx, "feature", flag); +} + +static void +yprp_identity(struct lys_ypr_ctx *pctx, const struct lysp_ident *ident) +{ + int8_t flag = 0; + LY_ARRAY_COUNT_TYPE u; + + ypr_open(pctx, "identity", "name", ident->name, flag); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_IDENTITY, 0, ident->exts, &flag); + yprp_iffeatures(pctx, ident->iffeatures, ident->exts, &flag); + + LY_ARRAY_FOR(ident->bases, u) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_BASE, u, ident->bases[u], ident->exts); + } + + ypr_status(pctx, ident->flags, ident->exts, &flag); + ypr_description(pctx, ident->dsc, ident->exts, &flag); + ypr_reference(pctx, ident->ref, ident->exts, &flag); + + LEVEL--; + ypr_close(pctx, "identity", flag); +} + +static void +yprp_restr(struct lys_ypr_ctx *pctx, const struct lysp_restr *restr, enum ly_stmt stmt, const char *attr, int8_t *flag) +{ + (void)flag; + int8_t inner_flag = 0; + + if (!restr) { + return; + } + + ly_print_(pctx->out, "%*s<%s %s=\"", INDENT, lyplg_ext_stmt2str(stmt), attr); + lyxml_dump_text(pctx->out, + (restr->arg.str[0] != LYSP_RESTR_PATTERN_NACK && restr->arg.str[0] != LYSP_RESTR_PATTERN_ACK) ? + restr->arg.str : &restr->arg.str[1], 1); + ly_print_(pctx->out, "\""); + + LEVEL++; + yprp_extension_instances(pctx, stmt, 0, restr->exts, &inner_flag); + if (restr->arg.str[0] == LYSP_RESTR_PATTERN_NACK) { + ypr_close_parent(pctx, &inner_flag); + /* special byte value in pattern's expression: 0x15 - invert-match, 0x06 - match */ + ypr_substmt(pctx, LY_STMT_MODIFIER, 0, "invert-match", restr->exts); + } + if (restr->emsg) { + ypr_close_parent(pctx, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_MESSAGE, 0, restr->emsg, restr->exts); + } + if (restr->eapptag) { + ypr_close_parent(pctx, &inner_flag); + ypr_substmt(pctx, LY_STMT_ERROR_APP_TAG, 0, restr->eapptag, restr->exts); + } + ypr_description(pctx, restr->dsc, restr->exts, &inner_flag); + ypr_reference(pctx, restr->ref, restr->exts, &inner_flag); + + LEVEL--; + ypr_close(pctx, lyplg_ext_stmt2str(stmt), inner_flag); +} + +static void +yprp_when(struct lys_ypr_ctx *pctx, struct lysp_when *when, int8_t *flag) +{ + int8_t inner_flag = 0; + + if (!when) { + return; + } + + ypr_close_parent(pctx, flag); + ly_print_(pctx->out, "%*sout, when->cond, 1); + ly_print_(pctx->out, "\""); + + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_WHEN, 0, when->exts, &inner_flag); + ypr_description(pctx, when->dsc, when->exts, &inner_flag); + ypr_reference(pctx, when->ref, when->exts, &inner_flag); + LEVEL--; + ypr_close(pctx, "when", inner_flag); +} + +static void +yprp_enum(struct lys_ypr_ctx *pctx, const struct lysp_type_enum *items, LY_DATA_TYPE type, int8_t *flag) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t inner_flag; + + (void)flag; + + LY_ARRAY_FOR(items, u) { + if (type == LY_TYPE_BITS) { + ly_print_(pctx->out, "%*sout, items[u].name, 1); + ly_print_(pctx->out, "\""); + } else { /* LY_TYPE_ENUM */ + ly_print_(pctx->out, "%*sout, items[u].name, 1); + ly_print_(pctx->out, "\""); + } + inner_flag = 0; + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_ENUM, 0, items[u].exts, &inner_flag); + yprp_iffeatures(pctx, items[u].iffeatures, items[u].exts, &inner_flag); + if (items[u].flags & LYS_SET_VALUE) { + if (type == LY_TYPE_BITS) { + ypr_close_parent(pctx, &inner_flag); + ypr_unsigned(pctx, LY_STMT_POSITION, 0, items[u].exts, items[u].value); + } else { /* LY_TYPE_ENUM */ + ypr_close_parent(pctx, &inner_flag); + ypr_signed(pctx, LY_STMT_VALUE, 0, items[u].exts, items[u].value); + } + } + ypr_status(pctx, items[u].flags, items[u].exts, &inner_flag); + ypr_description(pctx, items[u].dsc, items[u].exts, &inner_flag); + ypr_reference(pctx, items[u].ref, items[u].exts, &inner_flag); + LEVEL--; + ypr_close(pctx, type == LY_TYPE_BITS ? "bit" : "enum", inner_flag); + } +} + +static void +yprp_type(struct lys_ypr_ctx *pctx, const struct lysp_type *type) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + + if (!pctx || !type) { + return; + } + + ypr_open(pctx, "type", "name", type->name, flag); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_TYPE, 0, type->exts, &flag); + + if (type->range || type->length || type->patterns || type->bits || type->enums) { + ypr_close_parent(pctx, &flag); + } + yprp_restr(pctx, type->range, LY_STMT_RANGE, "value", &flag); + yprp_restr(pctx, type->length, LY_STMT_LENGTH, "value", &flag); + LY_ARRAY_FOR(type->patterns, u) { + yprp_restr(pctx, &type->patterns[u], LY_STMT_PATTERN, "value", &flag); + } + yprp_enum(pctx, type->bits, LY_TYPE_BITS, &flag); + yprp_enum(pctx, type->enums, LY_TYPE_ENUM, &flag); + + if (type->path) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_PATH, 0, type->path->expr, type->exts); + } + if (type->flags & LYS_SET_REQINST) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_REQUIRE_INSTANCE, 0, type->require_instance ? "true" : "false", type->exts); + } + if (type->flags & LYS_SET_FRDIGITS) { + ypr_close_parent(pctx, &flag); + ypr_unsigned(pctx, LY_STMT_FRACTION_DIGITS, 0, type->exts, type->fraction_digits); + } + LY_ARRAY_FOR(type->bases, u) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_BASE, u, type->bases[u], type->exts); + } + LY_ARRAY_FOR(type->types, u) { + ypr_close_parent(pctx, &flag); + yprp_type(pctx, &type->types[u]); + } + + LEVEL--; + ypr_close(pctx, "type", flag); +} + +static void +yprp_typedef(struct lys_ypr_ctx *pctx, const struct lysp_tpdf *tpdf) +{ + ypr_open(pctx, "typedef", "name", tpdf->name, 1); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_TYPEDEF, 0, tpdf->exts, NULL); + + yprp_type(pctx, &tpdf->type); + + if (tpdf->units) { + ypr_substmt(pctx, LY_STMT_UNITS, 0, tpdf->units, tpdf->exts); + } + if (tpdf->dflt.str) { + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, tpdf->dflt.str, tpdf->exts); + } + + ypr_status(pctx, tpdf->flags, tpdf->exts, NULL); + ypr_description(pctx, tpdf->dsc, tpdf->exts, NULL); + ypr_reference(pctx, tpdf->ref, tpdf->exts, NULL); + + LEVEL--; + ypr_close(pctx, "typedef", 1); +} + +static void yprp_node(struct lys_ypr_ctx *pctx, const struct lysp_node *node); +static void yprp_action(struct lys_ypr_ctx *pctx, const struct lysp_node_action *action); +static void yprp_notification(struct lys_ypr_ctx *pctx, const struct lysp_node_notif *notif); + +static void +yprp_grouping(struct lys_ypr_ctx *pctx, const struct lysp_node_grp *grp) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node *data; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *subgrp; + + ypr_open(pctx, "grouping", "name", grp->name, flag); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_GROUPING, 0, grp->exts, &flag); + ypr_status(pctx, grp->flags, grp->exts, &flag); + ypr_description(pctx, grp->dsc, grp->exts, &flag); + ypr_reference(pctx, grp->ref, grp->exts, &flag); + + LY_ARRAY_FOR(grp->typedefs, u) { + ypr_close_parent(pctx, &flag); + yprp_typedef(pctx, &grp->typedefs[u]); + } + + LY_LIST_FOR(grp->groupings, subgrp) { + ypr_close_parent(pctx, &flag); + yprp_grouping(pctx, subgrp); + } + + LY_LIST_FOR(grp->child, data) { + ypr_close_parent(pctx, &flag); + yprp_node(pctx, data); + } + + LY_LIST_FOR(grp->actions, action) { + ypr_close_parent(pctx, &flag); + yprp_action(pctx, action); + } + + LY_LIST_FOR(grp->notifs, notif) { + ypr_close_parent(pctx, &flag); + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, "grouping", flag); +} + +static void +yprp_inout(struct lys_ypr_ctx *pctx, const struct lysp_node_action_inout *inout, int8_t *flag) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node *data; + struct lysp_node_grp *grp; + + if (!inout->child) { + /* input/output is empty */ + return; + } + ypr_close_parent(pctx, flag); + + ypr_open(pctx, inout->name, NULL, NULL, *flag); + LEVEL++; + + yprp_extension_instances(pctx, lyplg_ext_nodetype2stmt(inout->nodetype), 0, inout->exts, NULL); + LY_ARRAY_FOR(inout->musts, u) { + yprp_restr(pctx, &inout->musts[u], LY_STMT_MUST, "condition", NULL); + } + LY_ARRAY_FOR(inout->typedefs, u) { + yprp_typedef(pctx, &inout->typedefs[u]); + } + LY_LIST_FOR(inout->groupings, grp) { + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(inout->child, data) { + yprp_node(pctx, data); + } + + LEVEL--; + ypr_close(pctx, inout->name, 1); +} + +static void +yprp_notification(struct lys_ypr_ctx *pctx, const struct lysp_node_notif *notif) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node *data; + struct lysp_node_grp *grp; + + ypr_open(pctx, "notification", "name", notif->name, flag); + + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_NOTIFICATION, 0, notif->exts, &flag); + yprp_iffeatures(pctx, notif->iffeatures, notif->exts, &flag); + + LY_ARRAY_FOR(notif->musts, u) { + ypr_close_parent(pctx, &flag); + yprp_restr(pctx, ¬if->musts[u], LY_STMT_MUST, "condition", &flag); + } + ypr_status(pctx, notif->flags, notif->exts, &flag); + ypr_description(pctx, notif->dsc, notif->exts, &flag); + ypr_reference(pctx, notif->ref, notif->exts, &flag); + + LY_ARRAY_FOR(notif->typedefs, u) { + ypr_close_parent(pctx, &flag); + yprp_typedef(pctx, ¬if->typedefs[u]); + } + + LY_LIST_FOR(notif->groupings, grp) { + ypr_close_parent(pctx, &flag); + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(notif->child, data) { + ypr_close_parent(pctx, &flag); + yprp_node(pctx, data); + } + + LEVEL--; + ypr_close(pctx, "notification", flag); +} + +static void +yprp_action(struct lys_ypr_ctx *pctx, const struct lysp_node_action *action) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node_grp *grp; + + ypr_open(pctx, action->parent ? "action" : "rpc", "name", action->name, flag); + + LEVEL++; + yprp_extension_instances(pctx, lyplg_ext_nodetype2stmt(action->nodetype), 0, action->exts, &flag); + yprp_iffeatures(pctx, action->iffeatures, action->exts, &flag); + ypr_status(pctx, action->flags, action->exts, &flag); + ypr_description(pctx, action->dsc, action->exts, &flag); + ypr_reference(pctx, action->ref, action->exts, &flag); + + LY_ARRAY_FOR(action->typedefs, u) { + ypr_close_parent(pctx, &flag); + yprp_typedef(pctx, &action->typedefs[u]); + } + + LY_LIST_FOR(action->groupings, grp) { + ypr_close_parent(pctx, &flag); + yprp_grouping(pctx, grp); + } + + yprp_inout(pctx, &action->input, &flag); + yprp_inout(pctx, &action->output, &flag); + + LEVEL--; + ypr_close(pctx, action->parent ? "action" : "rpc", flag); +} + +static void +yprp_node_common1(struct lys_ypr_ctx *pctx, const struct lysp_node *node, int8_t *flag) +{ + ypr_open(pctx, lys_nodetype2str(node->nodetype), "name", node->name, *flag); + LEVEL++; + + yprp_extension_instances(pctx, lyplg_ext_nodetype2stmt(node->nodetype), 0, node->exts, flag); + yprp_when(pctx, lysp_node_when(node), flag); + yprp_iffeatures(pctx, node->iffeatures, node->exts, flag); +} + +static void +yprp_node_common2(struct lys_ypr_ctx *pctx, const struct lysp_node *node, int8_t *flag) +{ + ypr_config(pctx, node->flags, node->exts, flag); + if (node->nodetype & (LYS_CHOICE | LYS_LEAF | LYS_ANYDATA)) { + ypr_mandatory(pctx, node->flags, node->exts, flag); + } + ypr_status(pctx, node->flags, node->exts, flag); + ypr_description(pctx, node->dsc, node->exts, flag); + ypr_reference(pctx, node->ref, node->exts, flag); +} + +static void +yprp_container(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node *child; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *grp; + struct lysp_node_container *cont = (struct lysp_node_container *)node; + + yprp_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(cont->musts, u) { + ypr_close_parent(pctx, &flag); + yprp_restr(pctx, &cont->musts[u], LY_STMT_MUST, "condition", &flag); + } + if (cont->presence) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_PRESENCE, 0, cont->presence, cont->exts); + } + + yprp_node_common2(pctx, node, &flag); + + LY_ARRAY_FOR(cont->typedefs, u) { + ypr_close_parent(pctx, &flag); + yprp_typedef(pctx, &cont->typedefs[u]); + } + + LY_LIST_FOR(cont->groupings, grp) { + ypr_close_parent(pctx, &flag); + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(cont->child, child) { + ypr_close_parent(pctx, &flag); + yprp_node(pctx, child); + } + + LY_LIST_FOR(cont->actions, action) { + ypr_close_parent(pctx, &flag); + yprp_action(pctx, action); + } + + LY_LIST_FOR(cont->notifs, notif) { + ypr_close_parent(pctx, &flag); + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, "container", flag); +} + +static void +yprp_case(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + int8_t flag = 0; + struct lysp_node *child; + struct lysp_node_case *cas = (struct lysp_node_case *)node; + + yprp_node_common1(pctx, node, &flag); + yprp_node_common2(pctx, node, &flag); + + LY_LIST_FOR(cas->child, child) { + ypr_close_parent(pctx, &flag); + yprp_node(pctx, child); + } + + LEVEL--; + ypr_close(pctx, "case", flag); +} + +static void +yprp_choice(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + int8_t flag = 0; + struct lysp_node *child; + struct lysp_node_choice *choice = (struct lysp_node_choice *)node; + + yprp_node_common1(pctx, node, &flag); + + if (choice->dflt.str) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, choice->dflt.str, choice->exts); + } + + yprp_node_common2(pctx, node, &flag); + + LY_LIST_FOR(choice->child, child) { + ypr_close_parent(pctx, &flag); + yprp_node(pctx, child); + } + + LEVEL--; + ypr_close(pctx, "choice", flag); +} + +static void +yprp_leaf(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node_leaf *leaf = (struct lysp_node_leaf *)node; + + int8_t flag = 1; + + yprp_node_common1(pctx, node, &flag); + + yprp_type(pctx, &leaf->type); + ypr_substmt(pctx, LY_STMT_UNITS, 0, leaf->units, leaf->exts); + LY_ARRAY_FOR(leaf->musts, u) { + yprp_restr(pctx, &leaf->musts[u], LY_STMT_MUST, "condition", &flag); + } + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, leaf->dflt.str, leaf->exts); + + yprp_node_common2(pctx, node, &flag); + + LEVEL--; + ypr_close(pctx, "leaf", flag); +} + +static void +yprp_leaflist(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node_leaflist *llist = (struct lysp_node_leaflist *)node; + int8_t flag = 1; + + yprp_node_common1(pctx, node, &flag); + + yprp_type(pctx, &llist->type); + ypr_substmt(pctx, LY_STMT_UNITS, 0, llist->units, llist->exts); + LY_ARRAY_FOR(llist->musts, u) { + yprp_restr(pctx, &llist->musts[u], LY_STMT_MUST, "condition", NULL); + } + LY_ARRAY_FOR(llist->dflts, u) { + ypr_substmt(pctx, LY_STMT_DEFAULT, u, llist->dflts[u].str, llist->exts); + } + + ypr_config(pctx, node->flags, node->exts, NULL); + + if (llist->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, llist->exts, llist->min); + } + if (llist->flags & LYS_SET_MAX) { + if (llist->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, llist->exts, llist->max); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", llist->exts); + } + } + + if (llist->flags & LYS_ORDBY_MASK) { + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, (llist->flags & LYS_ORDBY_USER) ? "user" : "system", llist->exts); + } + + ypr_status(pctx, node->flags, node->exts, &flag); + ypr_description(pctx, node->dsc, node->exts, &flag); + ypr_reference(pctx, node->ref, node->exts, &flag); + + LEVEL--; + ypr_close(pctx, "leaf-list", flag); +} + +static void +yprp_list(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node *child; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *grp; + struct lysp_node_list *list = (struct lysp_node_list *)node; + + yprp_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(list->musts, u) { + ypr_close_parent(pctx, &flag); + yprp_restr(pctx, &list->musts[u], LY_STMT_MUST, "condition", &flag); + } + if (list->key) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_KEY, 0, list->key, list->exts); + } + LY_ARRAY_FOR(list->uniques, u) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_UNIQUE, u, list->uniques[u].str, list->exts); + } + + ypr_config(pctx, node->flags, node->exts, &flag); + + if (list->flags & LYS_SET_MIN) { + ypr_close_parent(pctx, &flag); + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, list->exts, list->min); + } + if (list->flags & LYS_SET_MAX) { + ypr_close_parent(pctx, &flag); + if (list->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, list->exts, list->max); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", list->exts); + } + } + + if (list->flags & LYS_ORDBY_MASK) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_ORDERED_BY, 0, (list->flags & LYS_ORDBY_USER) ? "user" : "system", list->exts); + } + + ypr_status(pctx, node->flags, node->exts, &flag); + ypr_description(pctx, node->dsc, node->exts, &flag); + ypr_reference(pctx, node->ref, node->exts, &flag); + + LY_ARRAY_FOR(list->typedefs, u) { + ypr_close_parent(pctx, &flag); + yprp_typedef(pctx, &list->typedefs[u]); + } + + LY_LIST_FOR(list->groupings, grp) { + ypr_close_parent(pctx, &flag); + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(list->child, child) { + ypr_close_parent(pctx, &flag); + yprp_node(pctx, child); + } + + LY_LIST_FOR(list->actions, action) { + ypr_close_parent(pctx, &flag); + yprp_action(pctx, action); + } + + LY_LIST_FOR(list->notifs, notif) { + ypr_close_parent(pctx, &flag); + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, "list", flag); +} + +static void +yprp_refine(struct lys_ypr_ctx *pctx, struct lysp_refine *refine) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + + ypr_open(pctx, "refine", "target-node", refine->nodeid, flag); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_REFINE, 0, refine->exts, &flag); + yprp_iffeatures(pctx, refine->iffeatures, refine->exts, &flag); + + LY_ARRAY_FOR(refine->musts, u) { + ypr_close_parent(pctx, &flag); + yprp_restr(pctx, &refine->musts[u], LY_STMT_MUST, "condition", &flag); + } + + if (refine->presence) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_PRESENCE, 0, refine->presence, refine->exts); + } + + LY_ARRAY_FOR(refine->dflts, u) { + ypr_close_parent(pctx, &flag); + ypr_substmt(pctx, LY_STMT_DEFAULT, u, refine->dflts[u].str, refine->exts); + } + + ypr_config(pctx, refine->flags, refine->exts, &flag); + ypr_mandatory(pctx, refine->flags, refine->exts, &flag); + + if (refine->flags & LYS_SET_MIN) { + ypr_close_parent(pctx, &flag); + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, refine->exts, refine->min); + } + if (refine->flags & LYS_SET_MAX) { + ypr_close_parent(pctx, &flag); + if (refine->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, refine->exts, refine->max); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", refine->exts); + } + } + + ypr_description(pctx, refine->dsc, refine->exts, &flag); + ypr_reference(pctx, refine->ref, refine->exts, &flag); + + LEVEL--; + ypr_close(pctx, "refine", flag); +} + +static void +yprp_augment(struct lys_ypr_ctx *pctx, const struct lysp_node_augment *aug) +{ + struct lysp_node *child; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + + ypr_open(pctx, "augment", "target-node", aug->nodeid, 1); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_AUGMENT, 0, aug->exts, NULL); + yprp_when(pctx, aug->when, NULL); + yprp_iffeatures(pctx, aug->iffeatures, aug->exts, NULL); + ypr_status(pctx, aug->flags, aug->exts, NULL); + ypr_description(pctx, aug->dsc, aug->exts, NULL); + ypr_reference(pctx, aug->ref, aug->exts, NULL); + + LY_LIST_FOR(aug->child, child) { + yprp_node(pctx, child); + } + + LY_LIST_FOR(aug->actions, action) { + yprp_action(pctx, action); + } + + LY_LIST_FOR(aug->notifs, notif) { + yprp_notification(pctx, notif); + } + + LEVEL--; + ypr_close(pctx, "augment", 1); +} + +static void +yprp_uses(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node_uses *uses = (struct lysp_node_uses *)node; + struct lysp_node_augment *aug; + + yprp_node_common1(pctx, node, &flag); + yprp_node_common2(pctx, node, &flag); + + LY_ARRAY_FOR(uses->refines, u) { + ypr_close_parent(pctx, &flag); + yprp_refine(pctx, &uses->refines[u]); + } + + LY_LIST_FOR(uses->augments, aug) { + ypr_close_parent(pctx, &flag); + yprp_augment(pctx, aug); + } + + LEVEL--; + ypr_close(pctx, "uses", flag); +} + +static void +yprp_anydata(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + LY_ARRAY_COUNT_TYPE u; + int8_t flag = 0; + struct lysp_node_anydata *any = (struct lysp_node_anydata *)node; + + yprp_node_common1(pctx, node, &flag); + + LY_ARRAY_FOR(any->musts, u) { + ypr_close_parent(pctx, &flag); + yprp_restr(pctx, &any->musts[u], LY_STMT_MUST, "condition", &flag); + } + + yprp_node_common2(pctx, node, &flag); + + LEVEL--; + ypr_close(pctx, lys_nodetype2str(node->nodetype), flag); +} + +static void +yprp_node(struct lys_ypr_ctx *pctx, const struct lysp_node *node) +{ + switch (node->nodetype) { + case LYS_CONTAINER: + yprp_container(pctx, node); + break; + case LYS_CHOICE: + yprp_choice(pctx, node); + break; + case LYS_LEAF: + yprp_leaf(pctx, node); + break; + case LYS_LEAFLIST: + yprp_leaflist(pctx, node); + break; + case LYS_LIST: + yprp_list(pctx, node); + break; + case LYS_USES: + yprp_uses(pctx, node); + break; + case LYS_ANYXML: + case LYS_ANYDATA: + yprp_anydata(pctx, node); + break; + case LYS_CASE: + yprp_case(pctx, node); + break; + default: + break; + } +} + +static void +yprp_deviation(struct lys_ypr_ctx *pctx, const struct lysp_deviation *deviation) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_deviate_add *add; + struct lysp_deviate_rpl *rpl; + struct lysp_deviate_del *del; + struct lysp_deviate *elem; + + ypr_open(pctx, "deviation", "target-node", deviation->nodeid, 1); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATION, 0, deviation->exts, NULL); + ypr_description(pctx, deviation->dsc, deviation->exts, NULL); + ypr_reference(pctx, deviation->ref, deviation->exts, NULL); + + LY_LIST_FOR(deviation->deviates, elem) { + ly_print_(pctx->out, "%*smod == LYS_DEV_NOT_SUPPORTED) { + if (elem->exts) { + ly_print_(pctx->out, "not-supported\">\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, elem->exts, NULL); + } else { + ly_print_(pctx->out, "not-supported\"/>\n"); + continue; + } + } else if (elem->mod == LYS_DEV_ADD) { + add = (struct lysp_deviate_add *)elem; + ly_print_(pctx->out, "add\">\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, add->exts, NULL); + ypr_substmt(pctx, LY_STMT_UNITS, 0, add->units, add->exts); + LY_ARRAY_FOR(add->musts, u) { + yprp_restr(pctx, &add->musts[u], LY_STMT_MUST, "condition", NULL); + } + LY_ARRAY_FOR(add->uniques, u) { + ypr_substmt(pctx, LY_STMT_UNIQUE, u, add->uniques[u].str, add->exts); + } + LY_ARRAY_FOR(add->dflts, u) { + ypr_substmt(pctx, LY_STMT_DEFAULT, u, add->dflts[u].str, add->exts); + } + ypr_config(pctx, add->flags, add->exts, NULL); + ypr_mandatory(pctx, add->flags, add->exts, NULL); + if (add->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, add->exts, add->min); + } + if (add->flags & LYS_SET_MAX) { + if (add->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, add->exts, add->max); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", add->exts); + } + } + } else if (elem->mod == LYS_DEV_REPLACE) { + rpl = (struct lysp_deviate_rpl *)elem; + ly_print_(pctx->out, "replace\">\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, rpl->exts, NULL); + if (rpl->type) { + yprp_type(pctx, rpl->type); + } + ypr_substmt(pctx, LY_STMT_UNITS, 0, rpl->units, rpl->exts); + ypr_substmt(pctx, LY_STMT_DEFAULT, 0, rpl->dflt.str, rpl->exts); + ypr_config(pctx, rpl->flags, rpl->exts, NULL); + ypr_mandatory(pctx, rpl->flags, rpl->exts, NULL); + if (rpl->flags & LYS_SET_MIN) { + ypr_unsigned(pctx, LY_STMT_MIN_ELEMENTS, 0, rpl->exts, rpl->min); + } + if (rpl->flags & LYS_SET_MAX) { + if (rpl->max) { + ypr_unsigned(pctx, LY_STMT_MAX_ELEMENTS, 0, rpl->exts, rpl->max); + } else { + ypr_substmt(pctx, LY_STMT_MAX_ELEMENTS, 0, "unbounded", rpl->exts); + } + } + } else if (elem->mod == LYS_DEV_DELETE) { + del = (struct lysp_deviate_del *)elem; + ly_print_(pctx->out, "delete\">\n"); + LEVEL++; + + yprp_extension_instances(pctx, LY_STMT_DEVIATE, 0, del->exts, NULL); + ypr_substmt(pctx, LY_STMT_UNITS, 0, del->units, del->exts); + LY_ARRAY_FOR(del->musts, u) { + yprp_restr(pctx, &del->musts[u], LY_STMT_MUST, "condition", NULL); + } + LY_ARRAY_FOR(del->uniques, u) { + ypr_substmt(pctx, LY_STMT_UNIQUE, u, del->uniques[u].str, del->exts); + } + LY_ARRAY_FOR(del->dflts, u) { + ypr_substmt(pctx, LY_STMT_DEFAULT, u, del->dflts[u].str, del->exts); + } + } + + LEVEL--; + ypr_close(pctx, "deviate", 1); + } + + LEVEL--; + ypr_close(pctx, "deviation", 1); +} + +static void +ypr_xmlns(struct lys_ypr_ctx *pctx, const struct lys_module *module, uint16_t indent) +{ + ly_print_(pctx->out, "%*sxmlns=\"%s\"", indent + INDENT, YIN_NS_URI); + ly_print_(pctx->out, "\n%*sxmlns:%s=\"%s\"", indent + INDENT, module->prefix, module->ns); +} + +static void +ypr_import_xmlns(struct lys_ypr_ctx *pctx, const struct lysp_module *modp, uint16_t indent) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(modp->imports, u){ + ly_print_(pctx->out, "\n%*sxmlns:%s=\"%s\"", indent + INDENT, modp->imports[u].prefix, modp->imports[u].module->ns); + } +} + +static void +yin_print_parsed_linkage(struct lys_ypr_ctx *pctx, const struct lysp_module *modp) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(modp->imports, u) { + if (modp->imports[u].flags & LYS_INTERNAL) { + continue; + } + + ypr_open(pctx, "import", "module", modp->imports[u].name, 1); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_IMPORT, 0, modp->imports[u].exts, NULL); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, modp->imports[u].prefix, modp->imports[u].exts); + if (modp->imports[u].rev[0]) { + ypr_substmt(pctx, LY_STMT_REVISION_DATE, 0, modp->imports[u].rev, modp->imports[u].exts); + } + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, modp->imports[u].dsc, modp->imports[u].exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, modp->imports[u].ref, modp->imports[u].exts); + LEVEL--; + ypr_close(pctx, "import", 1); + } + LY_ARRAY_FOR(modp->includes, u) { + if (modp->includes[u].injected) { + /* do not print the includes injected from submodules */ + continue; + } + if (modp->includes[u].rev[0] || modp->includes[u].dsc || modp->includes[u].ref || modp->includes[u].exts) { + ypr_open(pctx, "include", "module", modp->includes[u].name, 1); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_INCLUDE, 0, modp->includes[u].exts, NULL); + if (modp->includes[u].rev[0]) { + ypr_substmt(pctx, LY_STMT_REVISION_DATE, 0, modp->includes[u].rev, modp->includes[u].exts); + } + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, modp->includes[u].dsc, modp->includes[u].exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, modp->includes[u].ref, modp->includes[u].exts); + LEVEL--; + ly_print_(pctx->out, "%*s}\n", INDENT); + } else { + ypr_open(pctx, "include", "module", modp->includes[u].name, -1); + } + } +} + +static void +yin_print_parsed_body(struct lys_ypr_ctx *pctx, const struct lysp_module *modp) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysp_node *data; + struct lysp_node_action *action; + struct lysp_node_notif *notif; + struct lysp_node_grp *grp; + struct lysp_node_augment *aug; + + LY_ARRAY_FOR(modp->extensions, u) { + yprp_extension(pctx, &modp->extensions[u]); + } + if (modp->exts) { + yprp_extension_instances(pctx, LY_STMT_MODULE, 0, modp->exts, NULL); + } + + LY_ARRAY_FOR(modp->features, u) { + yprp_feature(pctx, &modp->features[u]); + } + + LY_ARRAY_FOR(modp->identities, u) { + yprp_identity(pctx, &modp->identities[u]); + } + + LY_ARRAY_FOR(modp->typedefs, u) { + yprp_typedef(pctx, &modp->typedefs[u]); + } + + LY_LIST_FOR(modp->groupings, grp) { + yprp_grouping(pctx, grp); + } + + LY_LIST_FOR(modp->data, data) { + yprp_node(pctx, data); + } + + LY_LIST_FOR(modp->augments, aug) { + yprp_augment(pctx, aug); + } + + LY_LIST_FOR(modp->rpcs, action) { + yprp_action(pctx, action); + } + + LY_LIST_FOR(modp->notifs, notif) { + yprp_notification(pctx, notif); + } + + LY_ARRAY_FOR(modp->deviations, u) { + yprp_deviation(pctx, &modp->deviations[u]); + } +} + +LY_ERR +yin_print_parsed_module(struct ly_out *out, const struct lysp_module *modp, uint32_t options) +{ + LY_ARRAY_COUNT_TYPE u; + const struct lys_module *module = modp->mod; + struct lys_ypr_ctx pctx_ = {.out = out, .level = 0, .module = module, .options = options}, *pctx = &pctx_; + + ly_print_(pctx->out, "\n"); + ly_print_(pctx->out, "%*sname); + ypr_xmlns(pctx, module, XML_NS_INDENT); + ypr_import_xmlns(pctx, modp, XML_NS_INDENT); + ly_print_(pctx->out, ">\n"); + + LEVEL++; + + /* module-header-stmts */ + if (modp->version) { + ypr_substmt(pctx, LY_STMT_YANG_VERSION, 0, modp->version == LYS_VERSION_1_1 ? "1.1" : "1", modp->exts); + } + ypr_substmt(pctx, LY_STMT_NAMESPACE, 0, module->ns, modp->exts); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, module->prefix, modp->exts); + + /* linkage-stmts (import/include) */ + yin_print_parsed_linkage(pctx, modp); + + /* meta-stmts */ + if (module->org || module->contact || module->dsc || module->ref) { + ly_print_(out, "\n"); + } + ypr_substmt(pctx, LY_STMT_ORGANIZATION, 0, module->org, modp->exts); + ypr_substmt(pctx, LY_STMT_CONTACT, 0, module->contact, modp->exts); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, module->dsc, modp->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, module->ref, modp->exts); + + /* revision-stmts */ + if (modp->revs) { + ly_print_(out, "\n"); + } + LY_ARRAY_FOR(modp->revs, u) { + yprp_revision(pctx, &modp->revs[u]); + } + + /* body-stmts */ + yin_print_parsed_body(pctx, modp); + + LEVEL--; + ly_print_(out, "%*s\n", INDENT); + ly_print_flush(out); + + return LY_SUCCESS; +} + +static void +yprp_belongsto(struct lys_ypr_ctx *pctx, const struct lysp_submodule *submodp) +{ + ypr_open(pctx, "belongs-to", "module", submodp->mod->name, 1); + LEVEL++; + yprp_extension_instances(pctx, LY_STMT_BELONGS_TO, 0, submodp->exts, NULL); + ypr_substmt(pctx, LY_STMT_PREFIX, 0, submodp->prefix, submodp->exts); + LEVEL--; + ypr_close(pctx, "belongs-to", 1); +} + +LY_ERR +yin_print_parsed_submodule(struct ly_out *out, const struct lysp_submodule *submodp, uint32_t options) +{ + LY_ARRAY_COUNT_TYPE u; + struct lys_ypr_ctx pctx_ = {.out = out, .level = 0, .module = submodp->mod, .options = options}, *pctx = &pctx_; + + ly_print_(pctx->out, "\n"); + ly_print_(pctx->out, "%*sname); + ypr_xmlns(pctx, submodp->mod, XML_NS_INDENT); + ypr_import_xmlns(pctx, (struct lysp_module *)submodp, XML_NS_INDENT); + ly_print_(pctx->out, ">\n"); + + LEVEL++; + + /* submodule-header-stmts */ + if (submodp->version) { + ypr_substmt(pctx, LY_STMT_YANG_VERSION, 0, submodp->version == LYS_VERSION_1_1 ? "1.1" : "1", submodp->exts); + } + yprp_belongsto(pctx, submodp); + + /* linkage-stmts (import/include) */ + yin_print_parsed_linkage(pctx, (struct lysp_module *)submodp); + + /* meta-stmts */ + if (submodp->org || submodp->contact || submodp->dsc || submodp->ref) { + ly_print_(out, "\n"); + } + ypr_substmt(pctx, LY_STMT_ORGANIZATION, 0, submodp->org, submodp->exts); + ypr_substmt(pctx, LY_STMT_CONTACT, 0, submodp->contact, submodp->exts); + ypr_substmt(pctx, LY_STMT_DESCRIPTION, 0, submodp->dsc, submodp->exts); + ypr_substmt(pctx, LY_STMT_REFERENCE, 0, submodp->ref, submodp->exts); + + /* revision-stmts */ + if (submodp->revs) { + ly_print_(out, "\n"); + } + LY_ARRAY_FOR(submodp->revs, u) { + yprp_revision(pctx, &submodp->revs[u]); + } + + /* body-stmts */ + yin_print_parsed_body(pctx, (struct lysp_module *)submodp); + + LEVEL--; + ly_print_(out, "%*s\n", INDENT); + ly_print_flush(out); + + return LY_SUCCESS; +} diff --git a/src/schema_compile.c b/src/schema_compile.c new file mode 100644 index 0000000..ed768ba --- /dev/null +++ b/src/schema_compile.c @@ -0,0 +1,1798 @@ +/** + * @file schema_compile.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Schema compilation. + * + * Copyright (c) 2015 - 2022 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 + +#include "schema_compile.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "in.h" +#include "log.h" +#include "parser_schema.h" +#include "path.h" +#include "plugins.h" +#include "plugins_exts.h" +#include "plugins_internal.h" +#include "plugins_types.h" +#include "schema_compile_amend.h" +#include "schema_compile_node.h" +#include "schema_features.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_schema.h" +#include "tree_schema_free.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +void +lysc_update_path(struct lysc_ctx *ctx, const struct lys_module *parent_module, const char *name) +{ + int len; + uint8_t nextlevel = 0; /* 0 - no starttag, 1 - '/' starttag, 2 - '=' starttag + '}' endtag */ + + if (!name) { + /* removing last path segment */ + if (ctx->path[ctx->path_len - 1] == '}') { + for ( ; ctx->path[ctx->path_len] != '=' && ctx->path[ctx->path_len] != '{'; --ctx->path_len) {} + if (ctx->path[ctx->path_len] == '=') { + ctx->path[ctx->path_len++] = '}'; + } else { + /* not a top-level special tag, remove also preceiding '/' */ + goto remove_nodelevel; + } + } else { +remove_nodelevel: + for ( ; ctx->path[ctx->path_len] != '/'; --ctx->path_len) {} + if (ctx->path_len == 0) { + /* top-level (last segment) */ + ctx->path_len = 1; + } + } + /* set new terminating NULL-byte */ + ctx->path[ctx->path_len] = '\0'; + } else { + if (ctx->path_len > 1) { + if (!parent_module && (ctx->path[ctx->path_len - 1] == '}') && (ctx->path[ctx->path_len - 2] != '\'')) { + /* extension of the special tag */ + nextlevel = 2; + --ctx->path_len; + } else { + /* there is already some path, so add next level */ + nextlevel = 1; + } + } /* else the path is just initiated with '/', so do not add additional slash in case of top-level nodes */ + + if (nextlevel != 2) { + if ((parent_module && (parent_module == ctx->cur_mod)) || (!parent_module && (ctx->path_len > 1) && (name[0] == '{'))) { + /* module not changed, print the name unprefixed */ + len = snprintf(&ctx->path[ctx->path_len], LYSC_CTX_BUFSIZE - ctx->path_len, "%s%s", nextlevel ? "/" : "", name); + } else { + len = snprintf(&ctx->path[ctx->path_len], LYSC_CTX_BUFSIZE - ctx->path_len, "%s%s:%s", nextlevel ? "/" : "", ctx->cur_mod->name, name); + } + } else { + len = snprintf(&ctx->path[ctx->path_len], LYSC_CTX_BUFSIZE - ctx->path_len, "='%s'}", name); + } + if (len >= LYSC_CTX_BUFSIZE - (int)ctx->path_len) { + /* output truncated */ + ctx->path_len = LYSC_CTX_BUFSIZE - 1; + } else { + ctx->path_len += len; + } + } + + LOG_LOCBACK(0, 0, 1, 0); + LOG_LOCSET(NULL, NULL, ctx->path, NULL); +} + +/** + * @brief Fill in the prepared compiled extensions definition structure according to the parsed extension definition. + * + * @param[in] ctx Compile context. + * @param[in] extp Parsed extension instance. + * @param[out] ext Compiled extension definition. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_extension(struct lysc_ctx *ctx, struct lysp_ext_instance *extp, struct lysc_ext **ext) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_ext *ep = extp->def; + + if (!ep->compiled) { + lysc_update_path(ctx, NULL, "{extension}"); + lysc_update_path(ctx, NULL, ep->name); + + /* compile the extension definition */ + *ext = ep->compiled = calloc(1, sizeof **ext); + (*ext)->refcount = 1; + DUP_STRING_GOTO(ctx->ctx, ep->name, (*ext)->name, ret, cleanup); + DUP_STRING_GOTO(ctx->ctx, ep->argname, (*ext)->argname, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_ext_find_definition(ctx->ctx, extp, (const struct lys_module **)&(*ext)->module, NULL), + cleanup); + + /* compile nested extensions */ + COMPILE_EXTS_GOTO(ctx, ep->exts, (*ext)->exts, *ext, ret, cleanup); + + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + + /* find extension definition plugin */ + (*ext)->plugin = extp->record ? (struct lyplg_ext *)&extp->record->plugin : NULL; + } + + *ext = ep->compiled; + +cleanup: + if (ret) { + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + } + return ret; +} + +LY_ERR +lys_compile_ext(struct lysc_ctx *ctx, struct lysp_ext_instance *extp, struct lysc_ext_instance *ext, void *parent) +{ + LY_ERR ret = LY_SUCCESS; + + DUP_STRING_GOTO(ctx->ctx, extp->argument, ext->argument, ret, cleanup); + ext->module = ctx->cur_mod; + ext->parent = parent; + ext->parent_stmt = extp->parent_stmt; + ext->parent_stmt_index = extp->parent_stmt_index; + + lysc_update_path(ctx, (ext->parent_stmt & LY_STMT_NODE_MASK) ? ((struct lysc_node *)ext->parent)->module : NULL, + "{extension}"); + lysc_update_path(ctx, NULL, extp->name); + + /* compile extension if not already */ + LY_CHECK_GOTO(ret = lys_compile_extension(ctx, extp, &ext->def), cleanup); + + /* compile */ + if (ext->def->plugin && ext->def->plugin->compile) { + if (ext->argument) { + lysc_update_path(ctx, ext->module, ext->argument); + } + ret = ext->def->plugin->compile(ctx, extp, ext); + if (ret == LY_ENOT) { + lysc_ext_instance_free(&ctx->free_ctx, ext); + } + if (ext->argument) { + lysc_update_path(ctx, NULL, NULL); + } + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + return ret; +} + +static void +lysc_unres_must_free(struct lysc_unres_must *m) +{ + LY_ARRAY_FREE(m->local_mods); + free(m); +} + +static void +lysc_unres_dflt_free(const struct ly_ctx *ctx, struct lysc_unres_dflt *r) +{ + assert(!r->dflt || !r->dflts); + if (r->dflt) { + lysp_qname_free((struct ly_ctx *)ctx, r->dflt); + free(r->dflt); + } else { + FREE_ARRAY((struct ly_ctx *)ctx, r->dflts, lysp_qname_free); + } + free(r); +} + +LY_ERR +lys_identity_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lysp_module *parsed_mod, + const struct lysp_ident *identities_p, struct lysc_ident **identities) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_ctx cctx; + struct lysc_ident *ident; + LY_ERR ret = LY_SUCCESS; + + assert(ctx_sc || ctx); + + if (!ctx_sc) { + if (parsed_mod) { + LYSC_CTX_INIT_PMOD(cctx, parsed_mod, NULL); + } else { + LYSC_CTX_INIT_CTX(cctx, ctx); + } + ctx_sc = &cctx; + } + + if (!identities_p) { + return LY_SUCCESS; + } + + lysc_update_path(ctx_sc, NULL, "{identity}"); + LY_ARRAY_FOR(identities_p, u) { + lysc_update_path(ctx_sc, NULL, identities_p[u].name); + + /* add new compiled identity */ + LY_ARRAY_NEW_GOTO(ctx_sc->ctx, *identities, ident, ret, done); + + DUP_STRING_GOTO(ctx_sc->ctx, identities_p[u].name, ident->name, ret, done); + DUP_STRING_GOTO(ctx_sc->ctx, identities_p[u].dsc, ident->dsc, ret, done); + DUP_STRING_GOTO(ctx_sc->ctx, identities_p[u].ref, ident->ref, ret, done); + ident->module = ctx_sc->cur_mod; + /* backlinks (derived) can be added no sooner than when all the identities in the current module are present */ + COMPILE_EXTS_GOTO(ctx_sc, identities_p[u].exts, ident->exts, ident, ret, done); + ident->flags = identities_p[u].flags; + + lysc_update_path(ctx_sc, NULL, NULL); + } + lysc_update_path(ctx_sc, NULL, NULL); + +done: + if (ret) { + lysc_update_path(ctx_sc, NULL, NULL); + lysc_update_path(ctx_sc, NULL, NULL); + } + return ret; +} + +/** + * @brief Check circular dependency of identities - identity MUST NOT reference itself (via their base statement). + * + * The function works in the same way as lys_compile_feature_circular_check() with different structures and error messages. + * + * @param[in] ctx Compile context for logging. + * @param[in] ident The base identity (its derived list is being extended by the identity being currently processed). + * @param[in] derived The list of derived identities of the identity being currently processed (not the one provided as @p ident) + * @return LY_SUCCESS if everything is ok. + * @return LY_EVALID if the identity is derived from itself. + */ +static LY_ERR +lys_compile_identity_circular_check(struct lysc_ctx *ctx, struct lysc_ident *ident, struct lysc_ident **derived) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + struct ly_set recursion = {0}; + struct lysc_ident *drv; + + if (!derived) { + return LY_SUCCESS; + } + + for (u = 0; u < LY_ARRAY_COUNT(derived); ++u) { + if (ident == derived[u]) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Identity \"%s\" is indirectly derived from itself.", ident->name); + ret = LY_EVALID; + goto cleanup; + } + ret = ly_set_add(&recursion, derived[u], 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + for (v = 0; v < recursion.count; ++v) { + drv = recursion.objs[v]; + for (u = 0; u < LY_ARRAY_COUNT(drv->derived); ++u) { + if (ident == drv->derived[u]) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Identity \"%s\" is indirectly derived from itself.", ident->name); + ret = LY_EVALID; + goto cleanup; + } + ret = ly_set_add(&recursion, drv->derived[u], 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + ly_set_erase(&recursion, NULL); + return ret; +} + +LY_ERR +lys_compile_identity_bases(struct lysc_ctx *ctx, const struct lysp_module *base_pmod, const char **bases_p, + struct lysc_ident *ident, struct lysc_ident ***bases) +{ + LY_ARRAY_COUNT_TYPE u, v; + const char *s, *name; + const struct lys_module *mod; + struct lysc_ident **idref; + + assert(ident || bases); + + if ((LY_ARRAY_COUNT(bases_p) > 1) && (ctx->pmod->version < LYS_VERSION_1_1)) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Multiple bases in %s are allowed only in YANG 1.1 modules.", ident ? "identity" : "identityref type"); + return LY_EVALID; + } + + LY_ARRAY_FOR(bases_p, u) { + s = strchr(bases_p[u], ':'); + if (s) { + /* prefixed identity */ + name = &s[1]; + mod = ly_resolve_prefix(ctx->ctx, bases_p[u], s - bases_p[u], LY_VALUE_SCHEMA, (void *)base_pmod); + } else { + name = bases_p[u]; + mod = base_pmod->mod; + } + if (!mod) { + if (ident) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid prefix used for base (%s) of identity \"%s\".", bases_p[u], ident->name); + } else { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid prefix used for base (%s) of identityref.", bases_p[u]); + } + return LY_EVALID; + } + + idref = NULL; + LY_ARRAY_FOR(mod->identities, v) { + if (!strcmp(name, mod->identities[v].name)) { + if (ident) { + if (ident == &mod->identities[v]) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Identity \"%s\" is derived from itself.", ident->name); + return LY_EVALID; + } + LY_CHECK_RET(lys_compile_identity_circular_check(ctx, &mod->identities[v], ident->derived)); + /* we have match! store the backlink */ + LY_ARRAY_NEW_RET(ctx->ctx, mod->identities[v].derived, idref, LY_EMEM); + *idref = ident; + } else { + /* we have match! store the found identity */ + LY_ARRAY_NEW_RET(ctx->ctx, *bases, idref, LY_EMEM); + *idref = &mod->identities[v]; + } + break; + } + } + if (!idref) { + if (ident) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Unable to find base (%s) of identity \"%s\".", bases_p[u], ident->name); + } else { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Unable to find base (%s) of identityref.", bases_p[u]); + } + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief For the given array of identities, set the backlinks from all their base identities. + * + * @param[in] ctx Compile context, not only for logging but also to get the current module to resolve prefixes. + * @param[in] idents_p Array of identities definitions from the parsed schema structure. + * @param[in,out] idents Array of referencing identities to which the backlinks are supposed to be set. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_identities_derived(struct lysc_ctx *ctx, struct lysp_ident *idents_p, struct lysc_ident **idents) +{ + LY_ARRAY_COUNT_TYPE u, v; + + lysc_update_path(ctx, NULL, "{identity}"); + + for (u = 0; u < LY_ARRAY_COUNT(*idents); ++u) { + /* find matching parsed identity */ + for (v = 0; v < LY_ARRAY_COUNT(idents_p); ++v) { + if (idents_p[v].name == (*idents)[u].name) { + break; + } + } + + if ((v == LY_ARRAY_COUNT(idents_p)) || !idents_p[v].bases) { + /* identity not found (it may be from a submodule) or identity without bases */ + continue; + } + + lysc_update_path(ctx, NULL, (*idents)[u].name); + LY_CHECK_RET(lys_compile_identity_bases(ctx, ctx->pmod, idents_p[v].bases, &(*idents)[u], NULL)); + lysc_update_path(ctx, NULL, NULL); + } + + lysc_update_path(ctx, NULL, NULL); + return LY_SUCCESS; +} + +LY_ERR +lys_compile_expr_implement(const struct ly_ctx *ctx, const struct lyxp_expr *expr, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool implement, struct lys_glob_unres *unres, const struct lys_module **mod_p) +{ + uint32_t i; + const char *ptr, *start, **imp_f, *all_f[] = {"*", NULL}; + const struct lys_module *mod; + + assert(implement || mod_p); + + for (i = 0; i < expr->used; ++i) { + if ((expr->tokens[i] != LYXP_TOKEN_NAMETEST) && (expr->tokens[i] != LYXP_TOKEN_LITERAL)) { + /* token cannot have a prefix */ + continue; + } + + start = expr->expr + expr->tok_pos[i]; + if (!(ptr = ly_strnchr(start, ':', expr->tok_len[i]))) { + /* token without a prefix */ + continue; + } + + if (!(mod = ly_resolve_prefix(ctx, start, ptr - start, format, prefix_data))) { + /* unknown prefix, do not care right now */ + continue; + } + + /* unimplemented module found */ + if (!mod->implemented && !implement) { + /* should not be implemented now */ + *mod_p = mod; + break; + } + + if (!mod->implemented) { + /* implement if not implemented */ + imp_f = (ctx->flags & LY_CTX_ENABLE_IMP_FEATURES) ? all_f : NULL; + LY_CHECK_RET(lys_implement((struct lys_module *)mod, imp_f, unres)); + } + if (!mod->compiled) { + /* compile if not implemented before or only marked for compilation */ + LY_CHECK_RET(lys_compile((struct lys_module *)mod, &unres->ds_unres)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Check and optionally implement modules referenced by a when expression. + * + * @param[in] ctx Compile context. + * @param[in] when When to check. + * @param[in,out] unres Global unres structure. + * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for these whens to evaluate. + * @return LY_ENOT if full check of this when should be skipped. + * @return LY_ERR value on error. + */ +static LY_ERR +lys_compile_unres_when_implement(struct lysc_ctx *ctx, const struct lysc_when *when, struct lys_glob_unres *unres) +{ + LY_ERR rc = LY_SUCCESS; + const struct lys_module *mod = NULL; + + /* check whether all the referenced modules are implemented */ + rc = lys_compile_expr_implement(ctx->ctx, when->cond, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, + ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod); + if (rc) { + goto cleanup; + } else if (mod) { + LOGWRN(ctx->ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.", + when->cond->expr, mod->name); + rc = LY_ENOT; + goto cleanup; + } + +cleanup: + return rc; +} + +/** + * @brief Check when for cyclic dependencies. + * + * @param[in] set Set with all the referenced nodes. + * @param[in] node Node whose "when" referenced nodes are in @p set. + * @return LY_ERR value + */ +static LY_ERR +lys_compile_unres_when_cyclic(struct lyxp_set *set, const struct lysc_node *node) +{ + struct lyxp_set tmp_set; + struct lyxp_set_scnode *xp_scnode; + uint32_t i, j; + LY_ARRAY_COUNT_TYPE u; + LY_ERR ret = LY_SUCCESS; + + memset(&tmp_set, 0, sizeof tmp_set); + + /* prepare in_ctx of the set */ + for (i = 0; i < set->used; ++i) { + xp_scnode = &set->val.scnodes[i]; + + if (xp_scnode->in_ctx != LYXP_SET_SCNODE_START_USED) { + /* check node when, skip the context node (it was just checked) */ + xp_scnode->in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + } + } + + for (i = 0; i < set->used; ++i) { + xp_scnode = &set->val.scnodes[i]; + if (xp_scnode->in_ctx != LYXP_SET_SCNODE_ATOM_CTX) { + /* already checked */ + continue; + } + + if ((xp_scnode->type != LYXP_NODE_ELEM) || !lysc_node_when(xp_scnode->scnode)) { + /* no when to check */ + xp_scnode->in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + continue; + } + + node = xp_scnode->scnode; + do { + struct lysc_when **when_list, *when; + + LOG_LOCSET(node, NULL, NULL, NULL); + when_list = lysc_node_when(node); + LY_ARRAY_FOR(when_list, u) { + when = when_list[u]; + ret = lyxp_atomize(set->ctx, when->cond, node->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, + when->context, when->context, &tmp_set, LYXP_SCNODE_SCHEMA); + if (ret != LY_SUCCESS) { + LOGVAL(set->ctx, LYVE_SEMANTICS, "Invalid when condition \"%s\".", when->cond->expr); + LOG_LOCBACK(1, 0, 0, 0); + goto cleanup; + } + + for (j = 0; j < tmp_set.used; ++j) { + /* skip roots'n'stuff */ + if (tmp_set.val.scnodes[j].type == LYXP_NODE_ELEM) { + /* try to find this node in our set */ + uint32_t idx; + + if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) && + (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) { + LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".", + tmp_set.val.scnodes[j].scnode->name); + ret = LY_EVALID; + LOG_LOCBACK(1, 0, 0, 0); + goto cleanup; + } + + /* needs to be checked, if in both sets, will be ignored */ + tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + } else { + /* no when, nothing to check */ + tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + } + } + + /* merge this set into the global when set */ + lyxp_set_scnode_merge(set, &tmp_set); + } + + /* check when of non-data parents as well */ + node = node->parent; + + LOG_LOCBACK(1, 0, 0, 0); + } while (node && (node->nodetype & (LYS_CASE | LYS_CHOICE))); + + /* this node when was checked (xp_scnode could have been reallocd) */ + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + } + +cleanup: + lyxp_set_free_content(&tmp_set); + return ret; +} + +LY_ERR +lysc_check_status(struct lysc_ctx *ctx, uint16_t flags1, void *mod1, const char *name1, uint16_t flags2, void *mod2, + const char *name2) +{ + uint16_t flg1, flg2; + + flg1 = (flags1 & LYS_STATUS_MASK) ? (flags1 & LYS_STATUS_MASK) : LYS_STATUS_CURR; + flg2 = (flags2 & LYS_STATUS_MASK) ? (flags2 & LYS_STATUS_MASK) : LYS_STATUS_CURR; + + if ((flg1 < flg2) && (mod1 == mod2)) { + if (ctx) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "A %s definition \"%s\" is not allowed to reference %s definition \"%s\".", + flg1 == LYS_STATUS_CURR ? "current" : "deprecated", name1, + flg2 == LYS_STATUS_OBSLT ? "obsolete" : "deprecated", name2); + } + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Check when expressions of a node on a complete compiled schema tree. + * + * @param[in] ctx Compile context. + * @param[in] when When to check. + * @param[in] node Node with @p when. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_unres_when(struct lysc_ctx *ctx, const struct lysc_when *when, const struct lysc_node *node) +{ + struct lyxp_set tmp_set = {0}; + uint32_t i, opts; + LY_ERR ret = LY_SUCCESS; + + opts = LYXP_SCNODE_SCHEMA | ((node->flags & LYS_IS_OUTPUT) ? LYXP_SCNODE_OUTPUT : 0); + + /* check "when" */ + ret = lyxp_atomize(ctx->ctx, when->cond, node->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, when->context, + when->context, &tmp_set, opts); + if (ret) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid when condition \"%s\".", when->cond->expr); + goto cleanup; + } + + ctx->path[0] = '\0'; + lysc_path(node, LYSC_PATH_LOG, ctx->path, LYSC_CTX_BUFSIZE); + for (i = 0; i < tmp_set.used; ++i) { + /* skip roots'n'stuff */ + if ((tmp_set.val.scnodes[i].type == LYXP_NODE_ELEM) && + (tmp_set.val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START_USED)) { + struct lysc_node *schema = tmp_set.val.scnodes[i].scnode; + + /* XPath expression cannot reference "lower" status than the node that has the definition */ + if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module, + schema->name)) { + LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr, + (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name); + } + + /* check dummy node children/value accessing */ + if (lysc_data_parent(schema) == node) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children."); + ret = LY_EVALID; + goto cleanup; + } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value."); + ret = LY_EVALID; + goto cleanup; + } + } + } + + /* check cyclic dependencies */ + ret = lys_compile_unres_when_cyclic(&tmp_set, node); + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + lyxp_set_free_content(&tmp_set); + return ret; +} + +/** + * @brief Check must expressions of a node on a complete compiled schema tree. + * + * @param[in] ctx Compile context. + * @param[in] node Node to check. + * @param[in] local_mods Sized array of local modules for musts of @p node at the same index. + * @param[in,out] unres Global unres structure. + * @return LY_ERECOMPILE + * @return LY_ERR value + */ +static LY_ERR +lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods, + struct lys_glob_unres *unres) +{ + struct lyxp_set tmp_set; + uint32_t i, opts; + LY_ARRAY_COUNT_TYPE u; + struct lysc_must *musts = NULL; + LY_ERR ret = LY_SUCCESS; + const struct lys_module *mod; + uint16_t flg; + + LOG_LOCSET(node, NULL, NULL, NULL); + + memset(&tmp_set, 0, sizeof tmp_set); + opts = LYXP_SCNODE_SCHEMA | ((node->flags & LYS_IS_OUTPUT) ? LYXP_SCNODE_OUTPUT : 0); + + musts = lysc_node_musts(node); + LY_ARRAY_FOR(musts, u) { + /* first check whether all the referenced modules are implemented */ + mod = NULL; + ret = lys_compile_expr_implement(ctx->ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, + ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod); + if (ret) { + goto cleanup; + } else if (mod) { + LOGWRN(ctx->ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.", + musts[u].cond->expr, mod->name); + continue; + } + + /* check "must" */ + ret = lyxp_atomize(ctx->ctx, musts[u].cond, node->module, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, node, + node, &tmp_set, opts); + if (ret) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid must condition \"%s\".", musts[u].cond->expr); + goto cleanup; + } + + ctx->path[0] = '\0'; + lysc_path(node, LYSC_PATH_LOG, ctx->path, LYSC_CTX_BUFSIZE); + for (i = 0; i < tmp_set.used; ++i) { + /* skip roots'n'stuff */ + if (tmp_set.val.scnodes[i].type == LYXP_NODE_ELEM) { + struct lysc_node *schema = tmp_set.val.scnodes[i].scnode; + + /* XPath expression cannot reference "lower" status than the node that has the definition */ + if (local_mods[u]->mod == node->module) { + /* use flags of the context node since the definition is local */ + flg = node->flags; + } else { + /* definition is foreign (deviation, refine), always current */ + flg = LYS_STATUS_CURR; + } + if (lysc_check_status(NULL, flg, local_mods[u]->mod, node->name, schema->flags, schema->module, + schema->name)) { + LOGWRN(ctx->ctx, "Must condition \"%s\" may be referencing %s node \"%s\".", musts[u].cond->expr, + (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name); + break; + } + } + } + + lyxp_set_free_content(&tmp_set); + } + +cleanup: + lyxp_set_free_content(&tmp_set); + LOG_LOCBACK(1, 0, 0, 0); + return ret; +} + +/** + * @brief Remove all disabled bits/enums from a sized array. + * + * @param[in] ctx Context with the dictionary. + * @param[in] items Sized array of bits/enums. + */ +static void +lys_compile_unres_disabled_bitenum_remove(struct lysf_ctx *ctx, struct lysc_type_bitenum_item *items) +{ + LY_ARRAY_COUNT_TYPE u = 0, last_u; + + while (u < LY_ARRAY_COUNT(items)) { + if (items[u].flags & LYS_DISABLED) { + /* free the disabled item */ + lysc_enum_item_free(ctx, &items[u]); + + /* replace it with the following items */ + last_u = LY_ARRAY_COUNT(items) - 1; + if (u < last_u) { + memmove(items + u, items + u + 1, (last_u - u) * sizeof *items); + } + + /* one item less */ + LY_ARRAY_DECREMENT(items); + continue; + } + + ++u; + } +} + +/** + * @brief Find and remove all disabled bits/enums in a leaf/leaf-list type. + * + * @param[in] ctx Compile context. + * @param[in] leaf Leaf/leaf-list to check. + * @return LY_ERR value + */ +static LY_ERR +lys_compile_unres_disabled_bitenum(struct lysc_ctx *ctx, struct lysc_node_leaf *leaf) +{ + struct lysc_type **t; + LY_ARRAY_COUNT_TYPE u, count; + struct lysc_type_enum *ent; + + if (leaf->type->basetype == LY_TYPE_UNION) { + t = ((struct lysc_type_union *)leaf->type)->types; + count = LY_ARRAY_COUNT(t); + } else { + t = &leaf->type; + count = 1; + } + for (u = 0; u < count; ++u) { + if ((t[u]->basetype == LY_TYPE_BITS) || (t[u]->basetype == LY_TYPE_ENUM)) { + /* remove all disabled items */ + ent = (struct lysc_type_enum *)(t[u]); + lys_compile_unres_disabled_bitenum_remove(&ctx->free_ctx, ent->enums); + + if (!LY_ARRAY_COUNT(ent->enums)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "%s type of node \"%s\" without any (or all disabled) valid values.", + (ent->basetype == LY_TYPE_BITS) ? "Bits" : "Enumeration", leaf->name); + return LY_EVALID; + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Check leafref for its target existence on a complete compiled schema tree. + * + * @param[in] ctx Compile context. + * @param[in] node Context node for the leafref. + * @param[in] lref Leafref to check/resolve. + * @param[in] local_mod Local module for the leafref type. + * @param[in,out] unres Global unres structure. + * @return LY_ERECOMPILE if context recompilation is needed, + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_unres_leafref(struct lysc_ctx *ctx, const struct lysc_node *node, struct lysc_type_leafref *lref, + const struct lysp_module *local_mod, struct lys_glob_unres *unres) +{ + const struct lysc_node *target = NULL; + struct ly_path *p; + struct lysc_type *type; + uint16_t flg; + + assert(node->nodetype & (LYS_LEAF | LYS_LEAFLIST)); + + /* first implement all the modules in the path */ + LY_CHECK_RET(lys_compile_expr_implement(ctx->ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL)); + + /* try to find the target, current module is that of the context node (RFC 7950 6.4.1 second bullet) */ + LY_CHECK_RET(ly_path_compile_leafref(ctx->ctx, node, ctx->ext, lref->path, + (node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, + LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, &p)); + + /* get the target node */ + target = p[LY_ARRAY_COUNT(p) - 1].node; + ly_path_free(node->module->ctx, p); + + if (!(target->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid leafref path \"%s\" - target node is %s instead of leaf or leaf-list.", + lref->path->expr, lys_nodetype2str(target->nodetype)); + return LY_EVALID; + } + + /* check status */ + ctx->path[0] = '\0'; + lysc_path(node, LYSC_PATH_LOG, ctx->path, LYSC_CTX_BUFSIZE); + ctx->path_len = strlen(ctx->path); + if (node->module == local_mod->mod) { + /* use flags of the context node since the definition is local */ + flg = node->flags; + } else { + /* definition is foreign (deviation), always current */ + flg = LYS_STATUS_CURR; + } + if (lysc_check_status(ctx, flg, local_mod->mod, node->name, target->flags, target->module, target->name)) { + return LY_EVALID; + } + ctx->path_len = 1; + ctx->path[1] = '\0'; + + /* check config */ + if (lref->require_instance) { + if ((node->flags & LYS_CONFIG_W) && (target->flags & LYS_CONFIG_R)) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid leafref path \"%s\" - target is supposed" + " to represent configuration data (as the leafref does), but it does not.", lref->path->expr); + return LY_EVALID; + } + } + + /* check for circular chain of leafrefs */ + for (type = ((struct lysc_node_leaf *)target)->type; + type && (type->basetype == LY_TYPE_LEAFREF); + type = ((struct lysc_type_leafref *)type)->realtype) { + if (type == (struct lysc_type *)lref) { + /* circular chain detected */ + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid leafref path \"%s\" - circular chain of leafrefs detected.", + lref->path->expr); + return LY_EVALID; + } + } + + /* store the type */ + lref->realtype = ((struct lysc_node_leaf *)target)->type; + ++lref->realtype->refcount; + return LY_SUCCESS; +} + +/** + * @brief Compile default value(s) for leaf or leaf-list expecting a complete compiled schema tree. + * + * @param[in] ctx Compile context. + * @param[in] node Leaf or leaf-list to compile the default value(s) for. + * @param[in] type Type of the default value. + * @param[in] dflt Default value. + * @param[in] dflt_pmod Parsed module of the @p dflt to resolve possible prefixes. + * @param[in,out] storage Storage for the compiled default value. + * @param[in,out] unres Global unres structure for newly implemented modules. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_unres_dflt(struct lysc_ctx *ctx, struct lysc_node *node, struct lysc_type *type, const char *dflt, + const struct lysp_module *dflt_pmod, struct lyd_value *storage, struct lys_glob_unres *unres) +{ + LY_ERR ret; + uint32_t options; + struct ly_err_item *err = NULL; + + options = (ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED) ? LYPLG_TYPE_STORE_IMPLEMENT : 0; + ret = type->plugin->store(ctx->ctx, type, dflt, strlen(dflt), options, LY_VALUE_SCHEMA, (void *)dflt_pmod, + LYD_HINT_SCHEMA, node, storage, unres, &err); + if (ret == LY_ERECOMPILE) { + /* fine, but we need to recompile */ + return LY_ERECOMPILE; + } else if (ret == LY_EINCOMPLETE) { + /* we have no data so we will not be resolving it */ + ret = LY_SUCCESS; + } + + if (ret) { + LOG_LOCSET(node, NULL, NULL, NULL); + if (err) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid default - value does not fit the type (%s).", err->msg); + ly_err_free(err); + } else { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid default - value does not fit the type."); + } + LOG_LOCBACK(1, 0, 0, 0); + return ret; + } + + LY_ATOMIC_INC_BARRIER(((struct lysc_type *)storage->realtype)->refcount); + return LY_SUCCESS; +} + +/** + * @brief Compile default value of a leaf expecting a complete compiled schema tree. + * + * @param[in] ctx Compile context. + * @param[in] leaf Leaf that the default value is for. + * @param[in] dflt Default value to compile. + * @param[in,out] unres Global unres structure for newly implemented modules. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_unres_leaf_dlft(struct lysc_ctx *ctx, struct lysc_node_leaf *leaf, struct lysp_qname *dflt, + struct lys_glob_unres *unres) +{ + LY_ERR ret; + + assert(!leaf->dflt); + + if (leaf->flags & (LYS_MAND_TRUE | LYS_KEY)) { + /* ignore default values for keys and mandatory leaves */ + return LY_SUCCESS; + } + + /* allocate the default value */ + leaf->dflt = calloc(1, sizeof *leaf->dflt); + LY_CHECK_ERR_RET(!leaf->dflt, LOGMEM(ctx->ctx), LY_EMEM); + + /* store the default value */ + ret = lys_compile_unres_dflt(ctx, &leaf->node, leaf->type, dflt->str, dflt->mod, leaf->dflt, unres); + if (ret) { + free(leaf->dflt); + leaf->dflt = NULL; + } + + return ret; +} + +/** + * @brief Compile default values of a leaf-list expecting a complete compiled schema tree. + * + * @param[in] ctx Compile context. + * @param[in] llist Leaf-list that the default value(s) are for. + * @param[in] dflt Default value to compile, in case of a single value. + * @param[in] dflts Sized array of default values, in case of more values. + * @param[in,out] unres Global unres structure for newly implemented modules. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_unres_llist_dflts(struct lysc_ctx *ctx, struct lysc_node_leaflist *llist, struct lysp_qname *dflt, + struct lysp_qname *dflts, struct lys_glob_unres *unres) +{ + LY_ERR ret; + LY_ARRAY_COUNT_TYPE orig_count, u, v; + + assert(dflt || dflts); + + /* in case there were already some defaults and we are adding new by deviations */ + orig_count = LY_ARRAY_COUNT(llist->dflts); + + /* allocate new items */ + LY_ARRAY_CREATE_RET(ctx->ctx, llist->dflts, orig_count + (dflts ? LY_ARRAY_COUNT(dflts) : 1), LY_EMEM); + + /* fill each new default value */ + if (dflts) { + LY_ARRAY_FOR(dflts, u) { + llist->dflts[orig_count + u] = calloc(1, sizeof **llist->dflts); + ret = lys_compile_unres_dflt(ctx, &llist->node, llist->type, dflts[u].str, dflts[u].mod, + llist->dflts[orig_count + u], unres); + LY_CHECK_ERR_RET(ret, free(llist->dflts[orig_count + u]), ret); + LY_ARRAY_INCREMENT(llist->dflts); + } + } else { + llist->dflts[orig_count] = calloc(1, sizeof **llist->dflts); + ret = lys_compile_unres_dflt(ctx, &llist->node, llist->type, dflt->str, dflt->mod, + llist->dflts[orig_count], unres); + LY_CHECK_ERR_RET(ret, free(llist->dflts[orig_count]), ret); + LY_ARRAY_INCREMENT(llist->dflts); + } + + /* check default value uniqueness */ + if (llist->flags & LYS_CONFIG_W) { + /* configuration data values must be unique - so check the default values */ + for (u = orig_count; u < LY_ARRAY_COUNT(llist->dflts); ++u) { + for (v = 0; v < u; ++v) { + if (!llist->dflts[u]->realtype->plugin->compare(llist->dflts[u], llist->dflts[v])) { + lysc_update_path(ctx, llist->parent ? llist->parent->module : NULL, llist->name); + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Configuration leaf-list has multiple defaults of the same value \"%s\".", + llist->dflts[u]->realtype->plugin->print(ctx->ctx, llist->dflts[u], LY_VALUE_CANON, NULL, NULL, NULL)); + lysc_update_path(ctx, NULL, NULL); + return LY_EVALID; + } + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Iteratively get all leafrefs from @p node + * if the node is of type union, otherwise just return the leafref. + * + * @param[in] node Node that may contain the leafref. + * @param[in,out] index Value that is passed between function calls. + * For each new node, initialize value of the @p index to 0, otherwise + * do not modify the value between calls. + * @return Pointer to the leafref or next leafref, otherwise NULL. + */ +static struct lysc_type_leafref * +lys_type_leafref_next(const struct lysc_node *node, uint64_t *index) +{ + struct lysc_type_leafref *ret = NULL; + struct lysc_type_union *uni; + struct lysc_type *leaf_type; + + assert(node->nodetype & LYD_NODE_TERM); + + leaf_type = ((struct lysc_node_leaf *)node)->type; + if (leaf_type->basetype == LY_TYPE_UNION) { + uni = (struct lysc_type_union *)leaf_type; + + /* find next union leafref */ + while (*index < LY_ARRAY_COUNT(uni->types)) { + if (uni->types[*index]->basetype == LY_TYPE_LEAFREF) { + ret = (struct lysc_type_leafref *)uni->types[*index]; + ++(*index); + break; + } + + ++(*index); + } + } else { + /* return just the single leafref */ + if (*index == 0) { + ++(*index); + assert(leaf_type->basetype == LY_TYPE_LEAFREF); + ret = (struct lysc_type_leafref *)leaf_type; + } + } + + return ret; +} + +/** + * @brief Finish dependency set compilation by resolving all the unres sets. + * + * @param[in] ctx libyang context. + * @param[in] unres Global unres structure with the sets to resolve. + * @return LY_SUCCESS on success. + * @return LY_ERECOMPILE if the dep set needs to be recompiled. + * @return LY_ERR value on error. + */ +static LY_ERR +lys_compile_unres_depset(struct ly_ctx *ctx, struct lys_glob_unres *unres) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lysc_node *node; + struct lysc_type *typeiter; + struct lysc_type_leafref *lref; + struct lysc_ctx cctx = {0}; + struct lys_depset_unres *ds_unres = &unres->ds_unres; + struct ly_path *path; + LY_ARRAY_COUNT_TYPE v; + struct lysc_unres_leafref *l; + struct lysc_unres_when *w; + struct lysc_unres_must *m; + struct lysc_unres_dflt *d; + uint32_t i, processed_leafrefs = 0; + +resolve_all: + /* check disabled leafrefs first */ + while (ds_unres->disabled_leafrefs.count) { + /* remember index, it can change before we get to free this item */ + i = ds_unres->disabled_leafrefs.count - 1; + l = ds_unres->disabled_leafrefs.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, l->node->module->parsed, l->ext); + + LOG_LOCSET(l->node, NULL, NULL, NULL); + v = 0; + while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) { + ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres); + } + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + ly_set_rm_index(&ds_unres->disabled_leafrefs, i, free); + } + + /* for leafref, we need 2 rounds - first detects circular chain by storing the first referred type (which + * can be also leafref, in case it is already resolved, go through the chain and check that it does not + * point to the starting leafref type). The second round stores the first non-leafref type for later data validation. + * Also do the same check for set of the disabled leafrefs, but without the second round. */ + for (i = processed_leafrefs; i < ds_unres->leafrefs.count; ++i) { + l = ds_unres->leafrefs.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, l->node->module->parsed, l->ext); + + LOG_LOCSET(l->node, NULL, NULL, NULL); + v = 0; + while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) { + ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres); + } + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + } + for (i = processed_leafrefs; i < ds_unres->leafrefs.count; ++i) { + l = ds_unres->leafrefs.objs[i]; + + /* store pointer to the real type */ + v = 0; + while ((lref = lys_type_leafref_next(l->node, &v))) { + for (typeiter = lref->realtype; + typeiter->basetype == LY_TYPE_LEAFREF; + typeiter = ((struct lysc_type_leafref *)typeiter)->realtype) {} + + lysc_type_free(&cctx.free_ctx, lref->realtype); + lref->realtype = typeiter; + ++lref->realtype->refcount; + } + + /* if 'goto' will be used on the 'resolve_all' label, then the current leafref will not be processed again */ + processed_leafrefs++; + } + + /* check when, first implement all the referenced modules (for the cyclic check in the next loop to work) */ + i = 0; + while (i < ds_unres->whens.count) { + w = ds_unres->whens.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, w->node->module->parsed, NULL); + + LOG_LOCSET(w->node, NULL, NULL, NULL); + r = lys_compile_unres_when_implement(&cctx, w->when, unres); + LOG_LOCBACK(w->node ? 1 : 0, 0, 0, 0); + + if (r == LY_ENOT) { + /* skip full when check, remove from the set */ + free(w); + ly_set_rm_index(&ds_unres->whens, i, NULL); + continue; + } else if (r) { + /* error */ + ret = r; + goto cleanup; + } + + ++i; + } + while (ds_unres->whens.count) { + i = ds_unres->whens.count - 1; + w = ds_unres->whens.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, w->node->module->parsed, NULL); + + LOG_LOCSET(w->node, NULL, NULL, NULL); + ret = lys_compile_unres_when(&cctx, w->when, w->node); + LOG_LOCBACK(w->node ? 1 : 0, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + free(w); + ly_set_rm_index(&ds_unres->whens, i, NULL); + } + + /* check must */ + while (ds_unres->musts.count) { + i = ds_unres->musts.count - 1; + m = ds_unres->musts.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, m->node->module->parsed, m->ext); + + LOG_LOCSET(m->node, NULL, NULL, NULL); + ret = lys_compile_unres_must(&cctx, m->node, m->local_mods, unres); + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + lysc_unres_must_free(m); + ly_set_rm_index(&ds_unres->musts, i, NULL); + } + + /* remove disabled enums/bits */ + while (ds_unres->disabled_bitenums.count) { + i = ds_unres->disabled_bitenums.count - 1; + node = ds_unres->disabled_bitenums.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, node->module->parsed, NULL); + + LOG_LOCSET(node, NULL, NULL, NULL); + ret = lys_compile_unres_disabled_bitenum(&cctx, (struct lysc_node_leaf *)node); + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + ly_set_rm_index(&ds_unres->disabled_bitenums, i, NULL); + } + + /* finish incomplete default values compilation */ + while (ds_unres->dflts.count) { + i = ds_unres->dflts.count - 1; + d = ds_unres->dflts.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, d->leaf->module->parsed, NULL); + + LOG_LOCSET(&d->leaf->node, NULL, NULL, NULL); + if (d->leaf->nodetype == LYS_LEAF) { + ret = lys_compile_unres_leaf_dlft(&cctx, d->leaf, d->dflt, unres); + } else { + ret = lys_compile_unres_llist_dflts(&cctx, d->llist, d->dflt, d->dflts, unres); + } + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + lysc_unres_dflt_free(ctx, d); + ly_set_rm_index(&ds_unres->dflts, i, NULL); + } + + /* some unres items may have been added */ + if ((processed_leafrefs != ds_unres->leafrefs.count) || ds_unres->disabled_leafrefs.count || + ds_unres->whens.count || ds_unres->musts.count || ds_unres->dflts.count) { + goto resolve_all; + } + + /* finally, remove all disabled nodes */ + for (i = 0; i < ds_unres->disabled.count; ++i) { + node = ds_unres->disabled.snodes[i]; + if (node->flags & LYS_KEY) { + LOG_LOCSET(node, NULL, NULL, NULL); + LOGVAL(ctx, LYVE_REFERENCE, "Key \"%s\" is disabled.", node->name); + LOG_LOCBACK(1, 0, 0, 0); + ret = LY_EVALID; + goto cleanup; + } + LYSC_CTX_INIT_PMOD(cctx, node->module->parsed, NULL); + + lysc_node_free(&cctx.free_ctx, node, 1); + } + + /* also check if the leafref target has not been disabled */ + for (i = 0; i < ds_unres->leafrefs.count; ++i) { + l = ds_unres->leafrefs.objs[i]; + LYSC_CTX_INIT_PMOD(cctx, l->node->module->parsed, l->ext); + + v = 0; + while ((lref = lys_type_leafref_next(l->node, &v))) { + ret = ly_path_compile_leafref(cctx.ctx, l->node, cctx.ext, lref->path, + (l->node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, + LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, &path); + ly_path_free(l->node->module->ctx, path); + + assert(ret != LY_ERECOMPILE); + if (ret) { + LOG_LOCSET(l->node, NULL, NULL, NULL); + LOGVAL(ctx, LYVE_REFERENCE, "Target of leafref \"%s\" cannot be referenced because it is disabled.", + l->node->name); + LOG_LOCBACK(1, 0, 0, 0); + ret = LY_EVALID; + goto cleanup; + } + } + } + +cleanup: + lysf_ctx_erase(&cctx.free_ctx); + return ret; +} + +/** + * @brief Erase dep set unres. + * + * @param[in] ctx libyang context. + * @param[in] unres Global unres structure with the sets to resolve. + */ +static void +lys_compile_unres_depset_erase(const struct ly_ctx *ctx, struct lys_glob_unres *unres) +{ + uint32_t i; + + ly_set_erase(&unres->ds_unres.whens, free); + for (i = 0; i < unres->ds_unres.musts.count; ++i) { + lysc_unres_must_free(unres->ds_unres.musts.objs[i]); + } + ly_set_erase(&unres->ds_unres.musts, NULL); + ly_set_erase(&unres->ds_unres.leafrefs, free); + for (i = 0; i < unres->ds_unres.dflts.count; ++i) { + lysc_unres_dflt_free(ctx, unres->ds_unres.dflts.objs[i]); + } + ly_set_erase(&unres->ds_unres.dflts, NULL); + ly_set_erase(&unres->ds_unres.disabled, NULL); + ly_set_erase(&unres->ds_unres.disabled_leafrefs, free); + ly_set_erase(&unres->ds_unres.disabled_bitenums, NULL); +} + +/** + * @brief Compile all flagged modules in a dependency set, recursively if recompilation is needed. + * + * @param[in] ctx libyang context. + * @param[in] dep_set Dependency set to compile. + * @param[in,out] unres Global unres to use. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_depset_r(struct ly_ctx *ctx, struct ly_set *dep_set, struct lys_glob_unres *unres) +{ + LY_ERR ret = LY_SUCCESS; + struct lysf_ctx fctx = {.ctx = ctx}; + struct lys_module *mod; + uint32_t i; + + for (i = 0; i < dep_set->count; ++i) { + mod = dep_set->objs[i]; + if (!mod->to_compile) { + /* skip */ + continue; + } + assert(mod->implemented); + + /* free the compiled module, if any */ + lysc_module_free(&fctx, mod->compiled); + mod->compiled = NULL; + + /* (re)compile the module */ + LY_CHECK_GOTO(ret = lys_compile(mod, &unres->ds_unres), cleanup); + } + + /* resolve dep set unres */ + ret = lys_compile_unres_depset(ctx, unres); + if (ret == LY_ERECOMPILE) { + /* new module is implemented, discard current dep set unres and recompile the whole dep set */ + lys_compile_unres_depset_erase(ctx, unres); + return lys_compile_depset_r(ctx, dep_set, unres); + } else if (ret) { + /* error */ + goto cleanup; + } + + /* success, unset the flags of all the modules in the dep set */ + for (i = 0; i < dep_set->count; ++i) { + mod = dep_set->objs[i]; + mod->to_compile = 0; + } + +cleanup: + lys_compile_unres_depset_erase(ctx, unres); + lysf_ctx_erase(&fctx); + return ret; +} + +/** + * @brief Check if-feature of all features of all modules in a dep set. + * + * @param[in] dep_set Dep set to check. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_depset_check_features(struct ly_set *dep_set) +{ + struct lys_module *mod; + uint32_t i; + + for (i = 0; i < dep_set->count; ++i) { + mod = dep_set->objs[i]; + if (!mod->to_compile) { + /* skip */ + continue; + } + + /* check features of this module */ + LY_CHECK_RET(lys_check_features(mod->parsed)); + } + + return LY_SUCCESS; +} + +LY_ERR +lys_compile_depset_all(struct ly_ctx *ctx, struct lys_glob_unres *unres) +{ + uint32_t i; + + for (i = 0; i < unres->dep_sets.count; ++i) { + LY_CHECK_RET(lys_compile_depset_check_features(unres->dep_sets.objs[i])); + LY_CHECK_RET(lys_compile_depset_r(ctx, unres->dep_sets.objs[i], unres)); + } + + return LY_SUCCESS; +} + +/** + * @brief Finish compilation of all the module unres sets in a compile context. + * + * @param[in] ctx Compile context with unres sets. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_unres_mod(struct lysc_ctx *ctx) +{ + struct lysc_augment *aug; + struct lysc_deviation *dev; + struct lys_module *orig_mod = ctx->cur_mod; + uint32_t i; + + /* check that all augments were applied */ + for (i = 0; i < ctx->augs.count; ++i) { + aug = ctx->augs.objs[i]; + ctx->cur_mod = aug->aug_pmod->mod; + if (aug->ext) { + lysc_update_path(ctx, NULL, "{extension}"); + lysc_update_path(ctx, NULL, aug->ext->name); + } + lysc_update_path(ctx, NULL, "{augment}"); + lysc_update_path(ctx, NULL, aug->nodeid->expr); + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Augment%s target node \"%s\" from module \"%s\" was not found.", + aug->ext ? " extension" : "", aug->nodeid->expr, LYSP_MODULE_NAME(aug->aug_pmod)); + ctx->cur_mod = orig_mod; + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + if (aug->ext) { + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + } + } + if (ctx->augs.count) { + return LY_ENOTFOUND; + } + + /* check that all deviations were applied */ + for (i = 0; i < ctx->devs.count; ++i) { + dev = ctx->devs.objs[i]; + lysc_update_path(ctx, NULL, "{deviation}"); + lysc_update_path(ctx, NULL, dev->nodeid->expr); + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Deviation(s) target node \"%s\" from module \"%s\" was not found.", + dev->nodeid->expr, LYSP_MODULE_NAME(dev->dev_pmods[0])); + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + } + if (ctx->devs.count) { + return LY_ENOTFOUND; + } + + return LY_SUCCESS; +} + +/** + * @brief Erase all the module unres sets in a compile context. + * + * @param[in] ctx Compile context with unres sets. + * @param[in] error Whether the compilation finished with an error or not. + */ +static void +lys_compile_unres_mod_erase(struct lysc_ctx *ctx, ly_bool error) +{ + uint32_t i; + + ly_set_erase(&ctx->groupings, NULL); + ly_set_erase(&ctx->tpdf_chain, NULL); + + if (!error) { + /* there can be no leftover deviations or augments */ + LY_CHECK_ERR_RET(ctx->augs.count, LOGINT(ctx->ctx), ); + LY_CHECK_ERR_RET(ctx->devs.count, LOGINT(ctx->ctx), ); + + ly_set_erase(&ctx->augs, NULL); + ly_set_erase(&ctx->devs, NULL); + ly_set_erase(&ctx->uses_augs, NULL); + ly_set_erase(&ctx->uses_rfns, NULL); + } else { + for (i = 0; i < ctx->augs.count; ++i) { + lysc_augment_free(ctx->ctx, ctx->augs.objs[i]); + } + ly_set_erase(&ctx->augs, NULL); + for (i = 0; i < ctx->devs.count; ++i) { + lysc_deviation_free(ctx->ctx, ctx->devs.objs[i]); + } + ly_set_erase(&ctx->devs, NULL); + for (i = 0; i < ctx->uses_augs.count; ++i) { + lysc_augment_free(ctx->ctx, ctx->uses_augs.objs[i]); + } + ly_set_erase(&ctx->uses_augs, NULL); + for (i = 0; i < ctx->uses_rfns.count; ++i) { + lysc_refine_free(ctx->ctx, ctx->uses_rfns.objs[i]); + } + ly_set_erase(&ctx->uses_rfns, NULL); + } +} + +LY_ERR +lys_compile(struct lys_module *mod, struct lys_depset_unres *unres) +{ + struct lysc_ctx ctx; + struct lysc_module *mod_c = NULL; + struct lysp_module *sp; + struct lysp_submodule *submod; + struct lysp_node *pnode; + struct lysp_node_grp *grp; + LY_ARRAY_COUNT_TYPE u; + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(NULL, mod, mod->parsed, !mod->compiled, mod->ctx, LY_EINVAL); + + assert(mod->implemented && mod->to_compile); + + sp = mod->parsed; + LYSC_CTX_INIT_PMOD(ctx, sp, NULL); + ctx.unres = unres; + + ++mod->ctx->change_count; + mod->compiled = mod_c = calloc(1, sizeof *mod_c); + LY_CHECK_ERR_RET(!mod_c, LOGMEM(mod->ctx), LY_EMEM); + mod_c->mod = mod; + + /* compile augments and deviations of our module from other modules so they can be applied during compilation */ + LY_CHECK_GOTO(ret = lys_precompile_own_augments(&ctx), cleanup); + LY_CHECK_GOTO(ret = lys_precompile_own_deviations(&ctx), cleanup); + + /* data nodes */ + LY_LIST_FOR(sp->data, pnode) { + LY_CHECK_GOTO(ret = lys_compile_node(&ctx, pnode, NULL, 0, NULL), cleanup); + } + + /* top-level RPCs */ + LY_LIST_FOR((struct lysp_node *)sp->rpcs, pnode) { + LY_CHECK_GOTO(ret = lys_compile_node(&ctx, pnode, NULL, 0, NULL), cleanup); + } + + /* top-level notifications */ + LY_LIST_FOR((struct lysp_node *)sp->notifs, pnode) { + LY_CHECK_GOTO(ret = lys_compile_node(&ctx, pnode, NULL, 0, NULL), cleanup); + } + + /* module extension instances */ + COMPILE_EXTS_GOTO(&ctx, sp->exts, mod_c->exts, mod_c, ret, cleanup); + + /* the same for submodules */ + LY_ARRAY_FOR(sp->includes, u) { + submod = sp->includes[u].submodule; + ctx.pmod = (struct lysp_module *)submod; + + LY_LIST_FOR(submod->data, pnode) { + ret = lys_compile_node(&ctx, pnode, NULL, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + LY_LIST_FOR((struct lysp_node *)submod->rpcs, pnode) { + ret = lys_compile_node(&ctx, pnode, NULL, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + LY_LIST_FOR((struct lysp_node *)submod->notifs, pnode) { + ret = lys_compile_node(&ctx, pnode, NULL, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + COMPILE_EXTS_GOTO(&ctx, submod->exts, mod_c->exts, mod_c, ret, cleanup); + } + ctx.pmod = sp; + + /* validate non-instantiated groupings from the parsed schema, + * without it we would accept even the schemas with invalid grouping specification */ + ctx.compile_opts |= LYS_COMPILE_GROUPING; + LY_LIST_FOR(sp->groupings, grp) { + if (!(grp->flags & LYS_USED_GRP)) { + LY_CHECK_GOTO(ret = lys_compile_grouping(&ctx, NULL, grp), cleanup); + } + } + LY_LIST_FOR(sp->data, pnode) { + LY_LIST_FOR((struct lysp_node_grp *)lysp_node_groupings(pnode), grp) { + if (!(grp->flags & LYS_USED_GRP)) { + LY_CHECK_GOTO(ret = lys_compile_grouping(&ctx, pnode, grp), cleanup); + } + } + } + LY_ARRAY_FOR(sp->includes, u) { + submod = sp->includes[u].submodule; + ctx.pmod = (struct lysp_module *)submod; + + LY_LIST_FOR(submod->groupings, grp) { + if (!(grp->flags & LYS_USED_GRP)) { + LY_CHECK_GOTO(ret = lys_compile_grouping(&ctx, NULL, grp), cleanup); + } + } + LY_LIST_FOR(submod->data, pnode) { + LY_LIST_FOR((struct lysp_node_grp *)lysp_node_groupings(pnode), grp) { + if (!(grp->flags & LYS_USED_GRP)) { + LY_CHECK_GOTO(ret = lys_compile_grouping(&ctx, pnode, grp), cleanup); + } + } + } + } + ctx.pmod = sp; + + LOG_LOCBACK(0, 0, 1, 0); + + /* finish compilation for all unresolved module items in the context */ + LY_CHECK_GOTO(ret = lys_compile_unres_mod(&ctx), cleanup); + +cleanup: + LOG_LOCBACK(0, 0, 1, 0); + lys_compile_unres_mod_erase(&ctx, ret); + if (ret) { + lysc_module_free(&ctx.free_ctx, mod_c); + mod->compiled = NULL; + } + return ret; +} + +LY_ERR +lys_compile_identities(struct lys_module *mod) +{ + LY_ERR rc = LY_SUCCESS; + struct lysc_ctx ctx; + struct lysp_submodule *submod; + LY_ARRAY_COUNT_TYPE u; + + /* pre-compile identities of the module and any submodules */ + rc = lys_identity_precompile(NULL, mod->ctx, mod->parsed, mod->parsed->identities, &mod->identities); + LY_CHECK_GOTO(rc, cleanup); + LY_ARRAY_FOR(mod->parsed->includes, u) { + submod = mod->parsed->includes[u].submodule; + rc = lys_identity_precompile(NULL, mod->ctx, (struct lysp_module *)submod, submod->identities, &mod->identities); + LY_CHECK_GOTO(rc, cleanup); + } + + /* prepare context */ + LYSC_CTX_INIT_PMOD(ctx, mod->parsed, NULL); + + if (mod->parsed->identities) { + rc = lys_compile_identities_derived(&ctx, mod->parsed->identities, &mod->identities); + LY_CHECK_GOTO(rc, cleanup); + } + lysc_update_path(&ctx, NULL, "{submodule}"); + LY_ARRAY_FOR(mod->parsed->includes, u) { + submod = mod->parsed->includes[u].submodule; + if (submod->identities) { + ctx.pmod = (struct lysp_module *)submod; + lysc_update_path(&ctx, NULL, submod->name); + rc = lys_compile_identities_derived(&ctx, submod->identities, &mod->identities); + lysc_update_path(&ctx, NULL, NULL); + } + + if (rc) { + break; + } + } + lysc_update_path(&ctx, NULL, NULL); + +cleanup: + /* always needed when using lysc_update_path() */ + LOG_LOCBACK(0, 0, 1, 0); + return rc; +} + +/** + * @brief Check whether a module does not have any (recursive) compiled import. + * + * @param[in] mod Module to examine. + * @return LY_SUCCESS on success. + * @return LY_ERECOMPILE on required recompilation of the dep set. + * @return LY_ERR on error. + */ +static LY_ERR +lys_has_compiled_import_r(struct lys_module *mod) +{ + LY_ARRAY_COUNT_TYPE u; + struct lys_module *m; + + LY_ARRAY_FOR(mod->parsed->imports, u) { + m = mod->parsed->imports[u].module; + if (!m->implemented) { + continue; + } + + if (!m->to_compile) { + /* module was not/will not be compiled in this compilation (so disabled nodes are not present) */ + m->to_compile = 1; + return LY_ERECOMPILE; + } + + /* recursive */ + LY_CHECK_RET(lys_has_compiled_import_r(m)); + } + + return LY_SUCCESS; +} + +LY_ERR +lys_implement(struct lys_module *mod, const char **features, struct lys_glob_unres *unres) +{ + LY_ERR ret; + struct lys_module *m; + + assert(!mod->implemented); + + /* check collision with other implemented revision */ + m = ly_ctx_get_module_implemented(mod->ctx, mod->name); + if (m) { + assert(m != mod); + if (!strcmp(mod->name, "yang") && (strcmp(m->revision, mod->revision) > 0)) { + /* special case for newer internal module, continue */ + LOGVRB("Internal module \"%s@%s\" is already implemented in revision \"%s\", using it instead.", + mod->name, mod->revision ? mod->revision : "", m->revision ? m->revision : ""); + } else { + LOGERR(mod->ctx, LY_EDENIED, "Module \"%s@%s\" is already implemented in revision \"%s\".", + mod->name, mod->revision ? mod->revision : "", m->revision ? m->revision : ""); + return LY_EDENIED; + } + } + + /* set features */ + ret = lys_set_features(mod->parsed, features); + if (ret && (ret != LY_EEXIST)) { + return ret; + } + + /* + * mark the module implemented, which means + * 1) to (re)compile it only ::lys_compile() call is needed + * 2) its compilation will never cause new modules to be implemented (::lys_compile() does not return ::LY_ERECOMPILE) + * but there can be some unres items added that do + */ + mod->implemented = 1; + + /* this module is compiled in this compilation */ + mod->to_compile = 1; + + /* add the module into newly implemented module set */ + LY_CHECK_RET(ly_set_add(&unres->implementing, mod, 1, NULL)); + + /* mark target modules with our augments and deviations */ + LY_CHECK_RET(lys_precompile_augments_deviations(mod, unres)); + + /* check whether this module may reference any modules compiled previously */ + LY_CHECK_RET(lys_has_compiled_import_r(mod)); + + return LY_SUCCESS; +} diff --git a/src/schema_compile.h b/src/schema_compile.h new file mode 100644 index 0000000..d45156e --- /dev/null +++ b/src/schema_compile.h @@ -0,0 +1,397 @@ +/** + * @file schema_compile.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Header for schema compilation. + * + * Copyright (c) 2015 - 2022 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_SCHEMA_COMPILE_H_ +#define LY_SCHEMA_COMPILE_H_ + +#include +#include + +#include "log.h" +#include "plugins_exts.h" +#include "set.h" +#include "tree.h" +#include "tree_schema.h" +#include "tree_schema_free.h" + +struct lyxp_expr; + +/** + * @brief YANG schema compilation context. + */ +struct lysc_ctx { + struct ly_ctx *ctx; /**< libyang context */ + struct lys_module *cur_mod; /**< module currently being compiled, + - identifier/path - used as the current module for unprefixed nodes + - augment - module where the augment is defined + - deviation - module where the deviation is defined + - uses - module where the uses is defined */ + struct lysp_module *pmod; /**< parsed module being processed, + - identifier/path - used for searching imports to resolve prefixed nodes + - augment - module where the augment is defined + - deviation - module where the deviation is defined + - uses - module where the grouping is defined */ + struct lysc_ext_instance *ext; /**< extension instance being processed and serving as a source for its substatements + instead of the module itself */ + struct ly_set groupings; /**< stack for groupings circular check */ + struct ly_set tpdf_chain; /**< stack for typedefs circular check */ + struct ly_set augs; /**< set of compiled non-applied top-level augments (stored ::lysc_augment *) */ + struct ly_set devs; /**< set of compiled non-applied deviations (stored ::lysc_deviation *) */ + struct ly_set uses_augs; /**< set of compiled non-applied uses augments (stored ::lysc_augment *) */ + struct ly_set uses_rfns; /**< set of compiled non-applied uses refines (stored ::lysc_refine *) */ + struct lys_depset_unres *unres; /**< dependency set unres sets */ + uint32_t path_len; /**< number of path bytes used */ + uint32_t compile_opts; /**< various @ref scflags. */ + struct lysf_ctx free_ctx; /**< freeing context for errors/recompilation */ + +#define LYSC_CTX_BUFSIZE 4078 + char path[LYSC_CTX_BUFSIZE];/**< Path identifying the schema node currently being processed */ +}; + +/** + * @brief Initalize local compilation context using libyang context. + * + * @param[out] CCTX Compile context. + * @param[in] CTX libyang context. + */ +#define LYSC_CTX_INIT_CTX(CCTX, CTX) \ + memset(&(CCTX), 0, sizeof (CCTX)); \ + (CCTX).ctx = (CTX); \ + (CCTX).path_len = 1; \ + (CCTX).path[0] = '/'; \ + (CCTX).free_ctx.ctx = (CTX) + +/** + * @brief Initalize local compilation context using a parsed module. + * + * @param[out] CCTX Compile context. + * @param[in] PMOD Parsed module. + * @param[in] EXT Ancestor extension instance. + */ +#define LYSC_CTX_INIT_PMOD(CCTX, PMOD, EXT) \ + memset(&(CCTX), 0, sizeof (CCTX)); \ + (CCTX).ctx = (PMOD)->mod->ctx; \ + (CCTX).cur_mod = (PMOD)->mod; \ + (CCTX).pmod = (PMOD); \ + (CCTX).ext = (EXT); \ + (CCTX).path_len = 1; \ + (CCTX).path[0] = '/'; \ + (CCTX).free_ctx.ctx = (PMOD)->mod->ctx + +/** + * @brief Structure for unresolved items that may depend on any implemented module data in the dependency set + * so their resolution can only be performed after the whole dep set compilation is done. + */ +struct lys_depset_unres { + struct ly_set whens; /**< nodes with when to check */ + struct ly_set musts; /**< set of musts to check */ + struct ly_set leafrefs; /**< to validate target of leafrefs */ + struct ly_set dflts; /**< set of incomplete default values */ + struct ly_set disabled; /**< set of compiled nodes whose if-feature(s) was not satisfied + (stored ::lysc_node *) */ + struct ly_set disabled_leafrefs; /**< subset of the lys_depset_unres.disabled to validate target of disabled leafrefs */ + struct ly_set disabled_bitenums; /**< set of enumation/bits leaves/leaf-lists with bits/enums to disable + (stored ::lysc_node_leaf *) */ +}; + +/** + * @brief Unres structure global for compilation. + */ +struct lys_glob_unres { + struct ly_set dep_sets; /**< set of dependency sets of modules, see ::lys_compile_depset_all() */ + struct ly_set implementing; /**< set of YANG schemas being atomically implemented (compiled); the first added + module is always the explicitly implemented module, the other ones are dependencies */ + struct ly_set creating; /**< set of YANG schemas being atomically created (parsed); it is a subset of implemented + and all these modules are freed if any error occurs */ + struct lys_depset_unres ds_unres; /**< unres specific for the current dependency set */ +}; + +/** + * @brief Structure for storing schema node with a when expression. + */ +struct lysc_unres_when { + struct lysc_node *node; /**< node with the when expression */ + struct lysc_when *when; /**< one when expression of the node */ +}; + +/** + * @brief Structure for storing schema nodes with must expressions and local module for each of them. + */ +struct lysc_unres_must { + struct lysc_node *node; /**< node with the must expression(s) */ + const struct lysp_module **local_mods; /**< sized array of local modules for must(s) */ + struct lysc_ext_instance *ext; /**< ancestor extension instance of the must(s) */ +}; + +/** + * @brief Structure for storing leafref node and its local module. + */ +struct lysc_unres_leafref { + struct lysc_node *node; /**< leaf/leaf-list node with leafref type */ + const struct lysp_module *local_mod; /**< local module of the leafref type */ + struct lysc_ext_instance *ext; /**< ancestor extension instance of the leafref */ +}; + +/** + * @brief Structure for remembering default values of leaves and leaf-lists. They are resolved at schema compilation + * end when the whole schema tree is available. + */ +struct lysc_unres_dflt { + union { + struct lysc_node_leaf *leaf; + struct lysc_node_leaflist *llist; + }; + struct lysp_qname *dflt; + struct lysp_qname *dflts; /**< this is a sized array */ +}; + +/** + * @brief Duplicate string into dictionary + * @param[in] CTX libyang context of the dictionary. + * @param[in] ORIG String to duplicate. + * @param[out] DUP Where to store the result. + * @param[out] RET Where to store the return code. + */ +#define DUP_STRING(CTX, ORIG, DUP, RET) RET = lydict_insert(CTX, ORIG, 0, &(DUP)) +#define DUP_STRING_RET(CTX, ORIG, DUP) LY_CHECK_RET(lydict_insert(CTX, ORIG, 0, &(DUP))) +#define DUP_STRING_GOTO(CTX, ORIG, DUP, RET, GOTO) LY_CHECK_GOTO(RET = lydict_insert(CTX, ORIG, 0, &(DUP)), GOTO) + +#define DUP_ARRAY(CTX, ORIG_ARRAY, NEW_ARRAY, DUP_FUNC) \ + if (ORIG_ARRAY) { \ + LY_ARRAY_COUNT_TYPE __u; \ + LY_ARRAY_CREATE_RET(CTX, NEW_ARRAY, LY_ARRAY_COUNT(ORIG_ARRAY), LY_EMEM); \ + LY_ARRAY_FOR(ORIG_ARRAY, __u) { \ + LY_ARRAY_INCREMENT(NEW_ARRAY); \ + LY_CHECK_RET(DUP_FUNC(CTX, &(ORIG_ARRAY)[__u], &(NEW_ARRAY)[__u])); \ + } \ + } + +#define DUP_ARRAY2(CTX, PMOD, ORIG_ARRAY, NEW_ARRAY, DUP_FUNC) \ + if (ORIG_ARRAY) { \ + LY_ARRAY_COUNT_TYPE __u; \ + LY_ARRAY_CREATE_RET(CTX, NEW_ARRAY, LY_ARRAY_COUNT(ORIG_ARRAY), LY_EMEM); \ + LY_ARRAY_FOR(ORIG_ARRAY, __u) { \ + LY_ARRAY_INCREMENT(NEW_ARRAY); \ + LY_CHECK_RET(DUP_FUNC(CTX, PMOD, &(ORIG_ARRAY)[__u], &(NEW_ARRAY)[__u])); \ + } \ + } + +#define DUP_EXTS(CTX, PMOD, PARENT, PARENT_STMT, ORIG_ARRAY, NEW_ARRAY, DUP_FUNC) \ + if (ORIG_ARRAY) { \ + LY_ARRAY_COUNT_TYPE __u, __new_start; \ + __new_start = LY_ARRAY_COUNT(NEW_ARRAY); \ + LY_ARRAY_CREATE_RET(CTX, NEW_ARRAY, LY_ARRAY_COUNT(ORIG_ARRAY), LY_EMEM); \ + LY_ARRAY_FOR(ORIG_ARRAY, __u) { \ + LY_ARRAY_INCREMENT(NEW_ARRAY); \ + LY_CHECK_RET(DUP_FUNC(CTX, PMOD, PARENT, PARENT_STMT, &(ORIG_ARRAY)[__u], &(NEW_ARRAY)[__new_start + __u])); \ + } \ + } + +#define COMPILE_OP_ARRAY_GOTO(CTX, ARRAY_P, ARRAY_C, PARENT, FUNC, USES_STATUS, RET, GOTO) \ + if (ARRAY_P) { \ + LY_ARRAY_COUNT_TYPE __u = (ARRAY_C) ? LY_ARRAY_COUNT(ARRAY_C) : 0; \ + LY_ARRAY_CREATE_GOTO((CTX)->ctx, ARRAY_C, __u + LY_ARRAY_COUNT(ARRAY_P), RET, GOTO); \ + LY_ARRAY_FOR(ARRAY_P, __u) { \ + LY_ARRAY_INCREMENT(ARRAY_C); \ + RET = FUNC(CTX, &(ARRAY_P)[__u], PARENT, &(ARRAY_C)[LY_ARRAY_COUNT(ARRAY_C) - 1], USES_STATUS); \ + if (RET == LY_EDENIED) { \ + LY_ARRAY_DECREMENT(ARRAY_C); \ + RET = LY_SUCCESS; \ + } else if (RET) { \ + goto GOTO; \ + } \ + } \ + } + +#define COMPILE_ARRAY_GOTO(CTX, ARRAY_P, ARRAY_C, FUNC, RET, GOTO) \ + if (ARRAY_P) { \ + LY_ARRAY_COUNT_TYPE __u = (ARRAY_C) ? LY_ARRAY_COUNT(ARRAY_C) : 0; \ + LY_ARRAY_CREATE_GOTO((CTX)->ctx, ARRAY_C, __u + LY_ARRAY_COUNT(ARRAY_P), RET, GOTO); \ + LY_ARRAY_FOR(ARRAY_P, __u) { \ + LY_ARRAY_INCREMENT(ARRAY_C); \ + RET = FUNC(CTX, &(ARRAY_P)[__u], &(ARRAY_C)[LY_ARRAY_COUNT(ARRAY_C) - 1]); \ + LY_CHECK_GOTO(RET, GOTO); \ + } \ + } + +#define COMPILE_EXTS_GOTO(CTX, EXTS_P, EXT_C, PARENT, RET, GOTO) \ + if (EXTS_P) { \ + LY_ARRAY_COUNT_TYPE __u = (EXT_C) ? LY_ARRAY_COUNT(EXT_C) : 0; \ + LY_ARRAY_CREATE_GOTO((CTX)->ctx, EXT_C, __u + LY_ARRAY_COUNT(EXTS_P), RET, GOTO); \ + LY_ARRAY_FOR(EXTS_P, __u) { \ + LY_ARRAY_INCREMENT(EXT_C); \ + RET = lys_compile_ext(CTX, &(EXTS_P)[__u], &(EXT_C)[LY_ARRAY_COUNT(EXT_C) - 1], PARENT); \ + if (RET == LY_ENOT) { \ + LY_ARRAY_DECREMENT(EXT_C); \ + RET = LY_SUCCESS; \ + } else if (RET) { \ + goto GOTO; \ + } \ + } \ + } + +/** + * @brief Update path in the compile context, which is used for logging where the compilation failed. + * + * @param[in] ctx Compile context with the path. + * @param[in] parent_module Module of the current node's parent to check difference with the currently processed module + * (taken from @p ctx). + * @param[in] name Name of the node to update path with. If NULL, the last segment is removed. If the format is + * `{keyword}`, the following call updates the segment to the form `{keyword='name'}` (to remove this compound segment, + * 2 calls with NULL @p name must be used). + */ +void lysc_update_path(struct lysc_ctx *ctx, const struct lys_module *parent_module, const char *name); + +/** + * @brief Fill in the prepared compiled extension instance structure according to the parsed extension instance. + * + * @param[in] ctx Compilation context. + * @param[in] extp Parsed extension instance. + * @param[in,out] ext Prepared compiled extension instance. + * @param[in] parent Extension instance parent. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the extension is disabled and should be ignored. + * @return LY_ERR on error. + */ +LY_ERR lys_compile_ext(struct lysc_ctx *ctx, struct lysp_ext_instance *extp, struct lysc_ext_instance *ext, void *parent); + +/** + * @brief Compile information from the identity statement + * + * The backlinks to the identities derived from this one are supposed to be filled later via ::lys_compile_identity_bases(). + * + * @param[in] ctx_sc Compile context - alternative to the combination of @p ctx and @p parsed_mod. + * @param[in] ctx libyang context. + * @param[in] parsed_mod Module with the identities. + * @param[in] identities_p Array of the parsed identity definitions to precompile. + * @param[in,out] identities Pointer to the storage of the (pre)compiled identities array where the new identities are + * supposed to be added. The storage is supposed to be initiated to NULL when the first parsed identities are going + * to be processed. + * @return LY_ERR value. + */ +LY_ERR lys_identity_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lysp_module *parsed_mod, + const struct lysp_ident *identities_p, struct lysc_ident **identities); + +/** + * @brief Find and process the referenced base identities from another identity or identityref + * + * For bases in identity set backlinks to them from the base identities. For identityref, store + * the array of pointers to the base identities. So one of the ident or bases parameter must be set + * to distinguish these two use cases. + * + * @param[in] ctx Compile context, not only for logging but also to get the current module to resolve prefixes. + * @param[in] base_pmod Module where to resolve @p bases_p prefixes. + * @param[in] bases_p Array of names (including prefix if necessary) of base identities. + * @param[in] ident Referencing identity to work with, NULL for identityref. + * @param[in] bases Array of bases of identityref to fill in. + * @return LY_ERR value. + */ +LY_ERR lys_compile_identity_bases(struct lysc_ctx *ctx, const struct lysp_module *base_pmod, const char **bases_p, + struct lysc_ident *ident, struct lysc_ident ***bases); + +/** + * @brief Perform a complet compilation of identites in a module and all its submodules. + * + * @param[in] mod Module to process. + * @return LY_ERR value. + */ +LY_ERR lys_compile_identities(struct lys_module *mod); + +/** + * @brief Compile schema into a validated schema linking all the references. Must have been implemented before. + * + * @param[in] mod Pointer to the schema structure holding pointers to both schema structure types. The ::lys_module#parsed + * member is used as input and ::lys_module#compiled is used to hold the result of the compilation. + * @param[in,out] unres Dep set unres structure to add to. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +LY_ERR lys_compile(struct lys_module *mod, struct lys_depset_unres *unres); + +/** + * @brief Check statement's status for invalid combination. + * + * The modX parameters are used just to determine if both flags are in the same module, + * so any of the schema module structure can be used, but both modules must be provided + * in the same type. + * + * @param[in] ctx Compile context for logging. + * @param[in] flags1 Flags of the referencing node. + * @param[in] mod1 Module of the referencing node, + * @param[in] name1 Schema node name of the referencing node. + * @param[in] flags2 Flags of the referenced node. + * @param[in] mod2 Module of the referenced node, + * @param[in] name2 Schema node name of the referenced node. + * @return LY_ERR value + */ +LY_ERR lysc_check_status(struct lysc_ctx *ctx, uint16_t flags1, void *mod1, const char *name1, uint16_t flags2, + void *mod2, const char *name2); + +/** + * @brief Check parsed expression for any prefixes of unimplemented modules. + * + * @param[in] ctx libyang context. + * @param[in] expr Parsed expression. + * @param[in] format Prefix format. + * @param[in] prefix_data Format-specific data (see ::ly_resolve_prefix()). + * @param[in] implement Whether all the non-implemented modules should are implemented or the first + * non-implemented module, if any, returned in @p mod_p. + * @param[in,out] unres Global unres structure of newly implemented modules. + * @param[out] mod_p Module that is not implemented. + * @return LY_SUCCESS on success. + * @return LY_ERECOMPILE if @p implement is set. + * @return LY_ERR on error. + */ +LY_ERR lys_compile_expr_implement(const struct ly_ctx *ctx, const struct lyxp_expr *expr, LY_VALUE_FORMAT format, + void *prefix_data, ly_bool implement, struct lys_glob_unres *unres, const struct lys_module **mod_p); + +/** + * @brief Compile all flagged modules in a dependency set, recursively if recompilation is needed. + * + * Steps taken when adding a new module (::ly_ctx_load_module(), ::lys_parse()): + * + * 1) parse module and add it into context with all imports and includes also parsed and in context + * (::lys_parse_load(), ::lys_parse_in(), lys_parse_localfile() - static) + * 2) implement it (perform one-time compilation tasks - compile identities and add reference to augment/deviation + * target modules, implement those as well, ::_lys_set_implemented()) + * 3) create dep set of the module (::lys_unres_dep_sets_create()) + * 4) (re)compile all the modules in the dep set and collect unres (::lys_compile_dep_set_r()) + * 5) resolve unres (lys_compile_unres_depset() - static), new modules may be implemented like in 2) and if + * require recompilation, free all compiled modules and do 4) + * 6) all modules that needed to be (re)compiled are now, with all their dependencies + * + * What can cause new modules to be implemented when resolving unres in 5): + * - leafref + * - when, must + * - identityref, instance-identifier default value + * - new implemented module augments, deviations + * + * @param[in] ctx libyang context. + * @param[in,out] unres Global unres to use. + * @return LY_ERR value. + */ +LY_ERR lys_compile_depset_all(struct ly_ctx *ctx, struct lys_glob_unres *unres); + +/** + * @brief Implement a single module. Does not actually compile, only marks to_compile! + * + * @param[in] mod Module to implement. + * @param[in] features Features to set, see ::lys_set_features(). + * @param[in,out] unres Global unres to use. + * @return LY_ERR value. + */ +LY_ERR lys_implement(struct lys_module *mod, const char **features, struct lys_glob_unres *unres); + +#endif /* LY_SCHEMA_COMPILE_H_ */ diff --git a/src/schema_compile_amend.c b/src/schema_compile_amend.c new file mode 100644 index 0000000..9ca4e2e --- /dev/null +++ b/src/schema_compile_amend.c @@ -0,0 +1,2547 @@ +/** + * @file schema_compile_amend.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Schema compilation of augments, deviations, and refines. + * + * Copyright (c) 2015 - 2021 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 + +#include "schema_compile_amend.h" + +#include +#include +#include +#include +#include + +#include "common.h" +#include "dict.h" +#include "log.h" +#include "schema_compile.h" +#include "schema_compile_node.h" +#include "schema_features.h" +#include "set.h" +#include "tree.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +/** + * @brief Get module of a single nodeid node name test. + * + * @param[in] ctx libyang context. + * @param[in] nametest Nametest with an optional prefix. + * @param[in] nametest_len Length of @p nametest. + * @param[in] mod Both current and prefix module for resolving prefixes and to return in case of no prefix. + * @param[out] name Optional pointer to the name test without the prefix. + * @param[out] name_len Length of @p name. + * @return Resolved module. + */ +static const struct lys_module * +lys_schema_node_get_module(const struct ly_ctx *ctx, const char *nametest, size_t nametest_len, + const struct lysp_module *mod, const char **name, size_t *name_len) +{ + const struct lys_module *target_mod; + const char *ptr; + + ptr = ly_strnchr(nametest, ':', nametest_len); + if (ptr) { + target_mod = ly_resolve_prefix(ctx, nametest, ptr - nametest, LY_VALUE_SCHEMA, (void *)mod); + if (!target_mod) { + LOGVAL(ctx, LYVE_REFERENCE, + "Invalid absolute-schema-nodeid nametest \"%.*s\" - prefix \"%.*s\" not defined in module \"%s\".", + (int)nametest_len, nametest, (int)(ptr - nametest), nametest, LYSP_MODULE_NAME(mod)); + return NULL; + } + + if (name) { + *name = ptr + 1; + *name_len = nametest_len - ((ptr - nametest) + 1); + } + } else { + target_mod = mod->mod; + if (name) { + *name = nametest; + *name_len = nametest_len; + } + } + + return target_mod; +} + +/** + * @brief Check the syntax of a node-id and collect all the referenced modules. + * + * @param[in] ctx Compile context. + * @param[in] nodeid Node-id to check. + * @param[in] abs Whether @p nodeid is absolute. + * @param[in,out] mod_set Set to add referenced modules into. + * @param[out] expr Optional node-id parsed into an expression. + * @param[out] target_mod Optional target module of the node-id. + * @return LY_ERR value. + */ +static LY_ERR +lys_nodeid_mod_check(struct lysc_ctx *ctx, const char *nodeid, ly_bool abs, struct ly_set *mod_set, + struct lyxp_expr **expr, struct lys_module **target_mod) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *e = NULL; + struct lys_module *tmod = NULL, *mod; + const char *nodeid_type = abs ? "absolute-schema-nodeid" : "descendant-schema-nodeid"; + uint32_t i; + + /* parse */ + ret = lyxp_expr_parse(ctx->ctx, nodeid, strlen(nodeid), 0, &e); + if (ret) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid %s value \"%s\" - invalid syntax.", + nodeid_type, nodeid); + ret = LY_EVALID; + goto cleanup; + } + + if (abs) { + /* absolute schema nodeid */ + i = 0; + } else { + /* descendant schema nodeid */ + if (e->tokens[0] != LYXP_TOKEN_NAMETEST) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid %s value \"%s\" - name test expected instead of \"%.*s\".", + nodeid_type, nodeid, e->tok_len[0], e->expr + e->tok_pos[0]); + ret = LY_EVALID; + goto cleanup; + } + i = 1; + } + + /* check all the tokens */ + for ( ; i < e->used; i += 2) { + if (e->tokens[i] != LYXP_TOKEN_OPER_PATH) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid %s value \"%s\" - \"/\" expected instead of \"%.*s\".", + nodeid_type, nodeid, e->tok_len[i], e->expr + e->tok_pos[i]); + ret = LY_EVALID; + goto cleanup; + } else if (e->used == i + 1) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid %s value \"%s\" - unexpected end of expression.", nodeid_type, e->expr); + ret = LY_EVALID; + goto cleanup; + } else if (e->tokens[i + 1] != LYXP_TOKEN_NAMETEST) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid %s value \"%s\" - name test expected instead of \"%.*s\".", + nodeid_type, nodeid, e->tok_len[i + 1], e->expr + e->tok_pos[i + 1]); + ret = LY_EVALID; + goto cleanup; + } else if (abs) { + mod = (struct lys_module *)lys_schema_node_get_module(ctx->ctx, e->expr + e->tok_pos[i + 1], + e->tok_len[i + 1], ctx->pmod, NULL, NULL); + LY_CHECK_ERR_GOTO(!mod, ret = LY_EVALID, cleanup); + + /* only keep the first module */ + if (!tmod) { + tmod = mod; + } + + /* store the referenced module */ + LY_CHECK_GOTO(ret = ly_set_add(mod_set, mod, 0, NULL), cleanup); + } + } + +cleanup: + if (ret || !expr) { + lyxp_expr_free(ctx->ctx, e); + e = NULL; + } + if (expr) { + *expr = ret ? NULL : e; + } + if (target_mod) { + *target_mod = ret ? NULL : tmod; + } + return ret; +} + +/** + * @brief Check whether 2 schema nodeids match. + * + * @param[in] ctx libyang context. + * @param[in] exp1 First schema nodeid. + * @param[in] exp1p_mod Module of @p exp1 nodes without any prefix. + * @param[in] exp2 Second schema nodeid. + * @param[in] exp2_pmod Module of @p exp2 nodes without any prefix. + * @return Whether the schema nodeids match or not. + */ +static ly_bool +lys_abs_schema_nodeid_match(const struct ly_ctx *ctx, const struct lyxp_expr *exp1, const struct lysp_module *exp1_pmod, + const struct lyxp_expr *exp2, const struct lysp_module *exp2_pmod) +{ + uint32_t i; + const struct lys_module *mod1, *mod2; + const char *name1 = NULL, *name2 = NULL; + size_t name1_len = 0, name2_len = 0; + + if (exp1->used != exp2->used) { + return 0; + } + + for (i = 0; i < exp1->used; ++i) { + assert(exp1->tokens[i] == exp2->tokens[i]); + + if (exp1->tokens[i] == LYXP_TOKEN_NAMETEST) { + /* check modules of all the nodes in the node ID */ + mod1 = lys_schema_node_get_module(ctx, exp1->expr + exp1->tok_pos[i], exp1->tok_len[i], exp1_pmod, + &name1, &name1_len); + assert(mod1); + mod2 = lys_schema_node_get_module(ctx, exp2->expr + exp2->tok_pos[i], exp2->tok_len[i], exp2_pmod, + &name2, &name2_len); + assert(mod2); + + /* compare modules */ + if (mod1 != mod2) { + return 0; + } + + /* compare names */ + if ((name1_len != name2_len) || strncmp(name1, name2, name1_len)) { + return 0; + } + } + } + + return 1; +} + +LY_ERR +lys_precompile_uses_augments_refines(struct lysc_ctx *ctx, struct lysp_node_uses *uses_p, const struct lysc_node *ctx_node) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + struct lysc_augment *aug; + struct lysp_node_augment *aug_p; + struct lysc_refine *rfn; + struct lysp_refine **new_rfn; + LY_ARRAY_COUNT_TYPE u; + uint32_t i; + struct ly_set mod_set = {0}; + + LY_LIST_FOR(uses_p->augments, aug_p) { + lysc_update_path(ctx, NULL, "{augment}"); + lysc_update_path(ctx, NULL, aug_p->nodeid); + + /* parse the nodeid */ + LY_CHECK_GOTO(ret = lys_nodeid_mod_check(ctx, aug_p->nodeid, 0, &mod_set, &exp, NULL), cleanup); + + /* allocate new compiled augment and store it in the set */ + aug = calloc(1, sizeof *aug); + LY_CHECK_ERR_GOTO(!aug, LOGMEM(ctx->ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(&ctx->uses_augs, aug, 1, NULL), cleanup); + + aug->nodeid = exp; + exp = NULL; + aug->aug_pmod = ctx->pmod; + aug->nodeid_ctx_node = ctx_node; + aug->aug_p = aug_p; + + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + } + + LY_ARRAY_FOR(uses_p->refines, u) { + lysc_update_path(ctx, NULL, "{refine}"); + lysc_update_path(ctx, NULL, uses_p->refines[u].nodeid); + + /* parse the nodeid */ + LY_CHECK_GOTO(ret = lys_nodeid_mod_check(ctx, uses_p->refines[u].nodeid, 0, &mod_set, &exp, NULL), cleanup); + + /* try to find the node in already compiled refines */ + rfn = NULL; + for (i = 0; i < ctx->uses_rfns.count; ++i) { + if (lys_abs_schema_nodeid_match(ctx->ctx, exp, ctx->pmod, ((struct lysc_refine *)ctx->uses_rfns.objs[i])->nodeid, + ctx->pmod)) { + rfn = ctx->uses_rfns.objs[i]; + break; + } + } + + if (!rfn) { + /* allocate new compiled refine */ + rfn = calloc(1, sizeof *rfn); + LY_CHECK_ERR_GOTO(!rfn, LOGMEM(ctx->ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(&ctx->uses_rfns, rfn, 1, NULL), cleanup); + + rfn->nodeid = exp; + exp = NULL; + rfn->nodeid_pmod = ctx->cur_mod->parsed; + rfn->nodeid_ctx_node = ctx_node; + rfn->uses_p = uses_p; + } else { + /* just free exp */ + lyxp_expr_free(ctx->ctx, exp); + exp = NULL; + } + + /* add new parsed refine structure */ + LY_ARRAY_NEW_GOTO(ctx->ctx, rfn->rfns, new_rfn, ret, cleanup); + *new_rfn = &uses_p->refines[u]; + + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + } + +cleanup: + if (ret) { + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + } + /* should include only this module, will fail later if not */ + ly_set_erase(&mod_set, NULL); + lyxp_expr_free(ctx->ctx, exp); + return ret; +} + +/** + * @brief Duplicate parsed extension children, recursively. + * + * @param[in] ctx Context. + * @param[in] orig_child First original child to duplicate. + * @param[in,out] child Duplicated children to add to. + * @return LY_ERR value. + */ +static LY_ERR +lysp_ext_children_dup(const struct ly_ctx *ctx, const struct lysp_stmt *orig_child, struct lysp_stmt **child) +{ + struct lysp_stmt *ch = NULL; + + assert(!*child); + + LY_LIST_FOR(orig_child, orig_child) { + /* new child */ + if (!*child) { + *child = ch = calloc(1, sizeof *ch); + LY_CHECK_ERR_RET(!ch, LOGMEM(ctx), LY_EMEM); + } else { + ch->next = calloc(1, sizeof *ch); + LY_CHECK_ERR_RET(!ch->next, LOGMEM(ctx), LY_EMEM); + ch = ch->next; + } + + /* fill */ + DUP_STRING_RET(ctx, orig_child->stmt, ch->stmt); + ch->flags = orig_child->flags; + DUP_STRING_RET(ctx, orig_child->arg, ch->arg); + ch->format = orig_child->format; + LY_CHECK_RET(ly_dup_prefix_data(ctx, orig_child->format, orig_child->prefix_data, &(ch->prefix_data))); + ch->kw = orig_child->kw; + + /* recursive children */ + LY_CHECK_RET(lysp_ext_children_dup(ctx, orig_child->child, &ch->child)); + } + + return LY_SUCCESS; +} + +/** + * @brief Duplicate parsed extension instance. + * + * @param[in] ctx Context. + * @param[in] pmod Current parsed module. + * @param[in] parent Parent of the duplicated ext instance. + * @param[in] parent_stmt Parent statement of the duplicated ext instance (should be @p parent). + * @param[out] ext Duplicated ext instance to fill. + * @return LY_ERR value. + */ +static LY_ERR +lysp_ext_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, void *parent, enum ly_stmt parent_stmt, + const struct lysp_ext_instance *orig_ext, struct lysp_ext_instance *ext) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_set pmods = {0}; + struct lysp_ctx pctx = {.parsed_mods = &pmods}; + + DUP_STRING_GOTO(ctx, orig_ext->name, ext->name, ret, cleanup); + DUP_STRING_GOTO(ctx, orig_ext->argument, ext->argument, ret, cleanup); + ext->format = orig_ext->format; + LY_CHECK_GOTO(ret = ly_dup_prefix_data(ctx, orig_ext->format, orig_ext->prefix_data, &ext->prefix_data), cleanup); + ext->def = orig_ext->def; + + ext->parent = parent; + ext->parent_stmt = parent_stmt; + ext->parent_stmt_index = orig_ext->parent_stmt_index; + ext->flags = orig_ext->flags; + ext->record = orig_ext->record; + + LY_CHECK_GOTO(ret = lysp_ext_children_dup(ctx, orig_ext->child, &ext->child), cleanup); + if (ext->record && ext->record->plugin.parse) { + /* parse again */ + LY_CHECK_GOTO(ret = ly_set_add(&pmods, pmod, 1, NULL), cleanup); + LY_CHECK_GOTO(ret = ext->record->plugin.parse(&pctx, ext), cleanup); + } + +cleanup: + ly_set_erase(&pmods, NULL); + return ret; +} + +static LY_ERR +lysp_restr_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, const struct lysp_restr *orig_restr, + struct lysp_restr *restr) +{ + LY_ERR ret = LY_SUCCESS; + + if (orig_restr) { + DUP_STRING(ctx, orig_restr->arg.str, restr->arg.str, ret); + restr->arg.mod = orig_restr->arg.mod; + DUP_STRING(ctx, orig_restr->emsg, restr->emsg, ret); + DUP_STRING(ctx, orig_restr->eapptag, restr->eapptag, ret); + DUP_STRING(ctx, orig_restr->dsc, restr->dsc, ret); + DUP_STRING(ctx, orig_restr->ref, restr->ref, ret); + DUP_EXTS(ctx, pmod, restr, LY_STMT_MUST, orig_restr->exts, restr->exts, lysp_ext_dup); + } + + return ret; +} + +static LY_ERR +lysp_string_dup(const struct ly_ctx *ctx, const char **orig_str, const char **str) +{ + LY_ERR ret = LY_SUCCESS; + + DUP_STRING(ctx, *orig_str, *str, ret); + + return ret; +} + +LY_ERR +lysp_qname_dup(const struct ly_ctx *ctx, const struct lysp_qname *orig_qname, struct lysp_qname *qname) +{ + LY_ERR ret = LY_SUCCESS; + + if (!orig_qname->str) { + return LY_SUCCESS; + } + + DUP_STRING(ctx, orig_qname->str, qname->str, ret); + assert(orig_qname->mod); + qname->mod = orig_qname->mod; + + return ret; +} + +static LY_ERR +lysp_type_enum_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, const struct lysp_type_enum *orig_enm, + struct lysp_type_enum *enm) +{ + LY_ERR ret = LY_SUCCESS; + + DUP_STRING(ctx, orig_enm->name, enm->name, ret); + DUP_STRING(ctx, orig_enm->dsc, enm->dsc, ret); + DUP_STRING(ctx, orig_enm->ref, enm->ref, ret); + enm->value = orig_enm->value; + DUP_ARRAY(ctx, orig_enm->iffeatures, enm->iffeatures, lysp_qname_dup); + DUP_EXTS(ctx, pmod, enm, LY_STMT_ENUM, orig_enm->exts, enm->exts, lysp_ext_dup); + enm->flags = orig_enm->flags; + + return ret; +} + +static LY_ERR +lysp_type_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, const struct lysp_type *orig_type, + struct lysp_type *type) +{ + LY_ERR ret = LY_SUCCESS; + + /* array macros read previous data so we must zero it */ + memset(type, 0, sizeof *type); + + DUP_STRING_GOTO(ctx, orig_type->name, type->name, ret, done); + + if (orig_type->range) { + type->range = calloc(1, sizeof *type->range); + LY_CHECK_ERR_RET(!type->range, LOGMEM(ctx), LY_EMEM); + LY_CHECK_RET(lysp_restr_dup(ctx, pmod, orig_type->range, type->range)); + } + + if (orig_type->length) { + type->length = calloc(1, sizeof *type->length); + LY_CHECK_ERR_RET(!type->length, LOGMEM(ctx), LY_EMEM); + LY_CHECK_RET(lysp_restr_dup(ctx, pmod, orig_type->length, type->length)); + } + + DUP_ARRAY2(ctx, pmod, orig_type->patterns, type->patterns, lysp_restr_dup); + DUP_ARRAY2(ctx, pmod, orig_type->enums, type->enums, lysp_type_enum_dup); + DUP_ARRAY2(ctx, pmod, orig_type->bits, type->bits, lysp_type_enum_dup); + LY_CHECK_GOTO(ret = lyxp_expr_dup(ctx, orig_type->path, 0, 0, &type->path), done); + DUP_ARRAY(ctx, orig_type->bases, type->bases, lysp_string_dup); + DUP_ARRAY2(ctx, pmod, orig_type->types, type->types, lysp_type_dup); + DUP_EXTS(ctx, pmod, type, LY_STMT_TYPE, orig_type->exts, type->exts, lysp_ext_dup); + + type->pmod = orig_type->pmod; + type->compiled = orig_type->compiled; + + type->fraction_digits = orig_type->fraction_digits; + type->require_instance = orig_type->require_instance; + type->flags = orig_type->flags; + +done: + return ret; +} + +static LY_ERR +lysp_when_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, struct lysp_when *when, + const struct lysp_when *orig_when) +{ + LY_ERR ret = LY_SUCCESS; + + DUP_STRING(ctx, orig_when->cond, when->cond, ret); + DUP_STRING(ctx, orig_when->dsc, when->dsc, ret); + DUP_STRING(ctx, orig_when->ref, when->ref, ret); + DUP_EXTS(ctx, pmod, when, LY_STMT_WHEN, orig_when->exts, when->exts, lysp_ext_dup); + + return ret; +} + +static LY_ERR +lysp_node_common_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, struct lysp_node *node, + const struct lysp_node *orig) +{ + LY_ERR ret = LY_SUCCESS; + + node->parent = NULL; + node->nodetype = orig->nodetype; + node->flags = orig->flags; + node->next = NULL; + DUP_STRING(ctx, orig->name, node->name, ret); + DUP_STRING(ctx, orig->dsc, node->dsc, ret); + DUP_STRING(ctx, orig->ref, node->ref, ret); + DUP_ARRAY(ctx, orig->iffeatures, node->iffeatures, lysp_qname_dup); + DUP_EXTS(ctx, pmod, node, lyplg_ext_nodetype2stmt(node->nodetype), orig->exts, node->exts, lysp_ext_dup); + + return ret; +} + +#define DUP_PWHEN(CTX, PMOD, ORIG, NEW) \ + if (ORIG) { \ + NEW = calloc(1, sizeof *NEW); \ + LY_CHECK_ERR_RET(!NEW, LOGMEM(CTX), LY_EMEM); \ + LY_CHECK_RET(lysp_when_dup(CTX, PMOD, NEW, ORIG)); \ + } + +static LY_ERR +lysp_node_dup(const struct ly_ctx *ctx, const struct lysp_module *pmod, struct lysp_node *node, + const struct lysp_node *orig) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_node_container *cont; + const struct lysp_node_container *orig_cont; + struct lysp_node_leaf *leaf; + const struct lysp_node_leaf *orig_leaf; + struct lysp_node_leaflist *llist; + const struct lysp_node_leaflist *orig_llist; + struct lysp_node_list *list; + const struct lysp_node_list *orig_list; + struct lysp_node_choice *choice; + const struct lysp_node_choice *orig_choice; + struct lysp_node_case *cas; + const struct lysp_node_case *orig_cas; + struct lysp_node_anydata *any; + const struct lysp_node_anydata *orig_any; + struct lysp_node_action *action; + const struct lysp_node_action *orig_action; + struct lysp_node_action_inout *action_inout; + const struct lysp_node_action_inout *orig_action_inout; + struct lysp_node_notif *notif; + const struct lysp_node_notif *orig_notif; + + assert(orig->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_CHOICE | LYS_CASE | LYS_ANYDATA | + LYS_RPC | LYS_ACTION | LYS_NOTIF)); + + /* common part */ + LY_CHECK_RET(lysp_node_common_dup(ctx, pmod, node, orig)); + + /* specific part */ + switch (node->nodetype) { + case LYS_CONTAINER: + cont = (struct lysp_node_container *)node; + orig_cont = (const struct lysp_node_container *)orig; + + DUP_PWHEN(ctx, pmod, orig_cont->when, cont->when); + DUP_ARRAY2(ctx, pmod, orig_cont->musts, cont->musts, lysp_restr_dup); + DUP_STRING(ctx, orig_cont->presence, cont->presence, ret); + /* we do not need the rest */ + break; + case LYS_LEAF: + leaf = (struct lysp_node_leaf *)node; + orig_leaf = (const struct lysp_node_leaf *)orig; + + DUP_PWHEN(ctx, pmod, orig_leaf->when, leaf->when); + DUP_ARRAY2(ctx, pmod, orig_leaf->musts, leaf->musts, lysp_restr_dup); + LY_CHECK_RET(lysp_type_dup(ctx, pmod, &orig_leaf->type, &leaf->type)); + DUP_STRING(ctx, orig_leaf->units, leaf->units, ret); + LY_CHECK_RET(lysp_qname_dup(ctx, &orig_leaf->dflt, &leaf->dflt)); + break; + case LYS_LEAFLIST: + llist = (struct lysp_node_leaflist *)node; + orig_llist = (const struct lysp_node_leaflist *)orig; + + DUP_PWHEN(ctx, pmod, orig_llist->when, llist->when); + DUP_ARRAY2(ctx, pmod, orig_llist->musts, llist->musts, lysp_restr_dup); + LY_CHECK_RET(lysp_type_dup(ctx, pmod, &orig_llist->type, &llist->type)); + DUP_STRING(ctx, orig_llist->units, llist->units, ret); + DUP_ARRAY(ctx, orig_llist->dflts, llist->dflts, lysp_qname_dup); + llist->min = orig_llist->min; + llist->max = orig_llist->max; + break; + case LYS_LIST: + list = (struct lysp_node_list *)node; + orig_list = (const struct lysp_node_list *)orig; + + DUP_PWHEN(ctx, pmod, orig_list->when, list->when); + DUP_ARRAY2(ctx, pmod, orig_list->musts, list->musts, lysp_restr_dup); + DUP_STRING(ctx, orig_list->key, list->key, ret); + /* we do not need these arrays */ + DUP_ARRAY(ctx, orig_list->uniques, list->uniques, lysp_qname_dup); + list->min = orig_list->min; + list->max = orig_list->max; + break; + case LYS_CHOICE: + choice = (struct lysp_node_choice *)node; + orig_choice = (const struct lysp_node_choice *)orig; + + DUP_PWHEN(ctx, pmod, orig_choice->when, choice->when); + /* we do not need children */ + LY_CHECK_RET(lysp_qname_dup(ctx, &orig_choice->dflt, &choice->dflt)); + break; + case LYS_CASE: + cas = (struct lysp_node_case *)node; + orig_cas = (const struct lysp_node_case *)orig; + + DUP_PWHEN(ctx, pmod, orig_cas->when, cas->when); + /* we do not need children */ + break; + case LYS_ANYDATA: + case LYS_ANYXML: + any = (struct lysp_node_anydata *)node; + orig_any = (const struct lysp_node_anydata *)orig; + + DUP_PWHEN(ctx, pmod, orig_any->when, any->when); + DUP_ARRAY2(ctx, pmod, orig_any->musts, any->musts, lysp_restr_dup); + break; + case LYS_RPC: + case LYS_ACTION: + action = (struct lysp_node_action *)node; + orig_action = (const struct lysp_node_action *)orig; + + action->input.nodetype = orig_action->input.nodetype; + action->output.nodetype = orig_action->output.nodetype; + /* we do not need the rest */ + break; + case LYS_INPUT: + case LYS_OUTPUT: + action_inout = (struct lysp_node_action_inout *)node; + orig_action_inout = (const struct lysp_node_action_inout *)orig; + + DUP_ARRAY2(ctx, pmod, orig_action_inout->musts, action_inout->musts, lysp_restr_dup); + /* we do not need the rest */ + break; + case LYS_NOTIF: + notif = (struct lysp_node_notif *)node; + orig_notif = (const struct lysp_node_notif *)orig; + + DUP_ARRAY2(ctx, pmod, orig_notif->musts, notif->musts, lysp_restr_dup); + /* we do not need the rest */ + break; + default: + LOGINT_RET(ctx); + } + + return ret; +} + +/** + * @brief Duplicate a single parsed node. Only attributes that are used in compilation are copied. + * + * @param[in] ctx libyang context. + * @param[in] pmod Current parsed module. + * @param[in] pnode Node to duplicate. + * @param[in] with_links Whether to also copy any links (child, parent pointers). + * @param[out] dup_p Duplicated parsed node. + * @return LY_ERR value. + */ +static LY_ERR +lysp_dup_single(struct lysc_ctx *cctx, const struct lysp_node *pnode, ly_bool with_links, struct lysp_node **dup_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_node *dup = NULL; + + if (!pnode) { + *dup_p = NULL; + return LY_SUCCESS; + } + + switch (pnode->nodetype) { + case LYS_CONTAINER: + dup = calloc(1, sizeof(struct lysp_node_container)); + break; + case LYS_LEAF: + dup = calloc(1, sizeof(struct lysp_node_leaf)); + break; + case LYS_LEAFLIST: + dup = calloc(1, sizeof(struct lysp_node_leaflist)); + break; + case LYS_LIST: + dup = calloc(1, sizeof(struct lysp_node_list)); + break; + case LYS_CHOICE: + dup = calloc(1, sizeof(struct lysp_node_choice)); + break; + case LYS_CASE: + dup = calloc(1, sizeof(struct lysp_node_case)); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + dup = calloc(1, sizeof(struct lysp_node_anydata)); + break; + case LYS_INPUT: + case LYS_OUTPUT: + dup = calloc(1, sizeof(struct lysp_node_action_inout)); + break; + case LYS_ACTION: + case LYS_RPC: + dup = calloc(1, sizeof(struct lysp_node_action)); + break; + case LYS_NOTIF: + dup = calloc(1, sizeof(struct lysp_node_notif)); + break; + default: + LOGINT_RET(cctx->ctx); + } + LY_CHECK_ERR_GOTO(!dup, LOGMEM(cctx->ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = lysp_node_dup(cctx->ctx, cctx->pmod, dup, pnode), cleanup); + + if (with_links) { + /* copy also parent, child, action, and notification pointers */ + dup->parent = pnode->parent; + switch (pnode->nodetype) { + case LYS_CONTAINER: + ((struct lysp_node_container *)dup)->child = ((struct lysp_node_container *)pnode)->child; + ((struct lysp_node_container *)dup)->actions = ((struct lysp_node_container *)pnode)->actions; + ((struct lysp_node_container *)dup)->notifs = ((struct lysp_node_container *)pnode)->notifs; + break; + case LYS_LIST: + ((struct lysp_node_list *)dup)->child = ((struct lysp_node_list *)pnode)->child; + ((struct lysp_node_list *)dup)->actions = ((struct lysp_node_list *)pnode)->actions; + ((struct lysp_node_list *)dup)->notifs = ((struct lysp_node_list *)pnode)->notifs; + break; + case LYS_CHOICE: + ((struct lysp_node_choice *)dup)->child = ((struct lysp_node_choice *)pnode)->child; + break; + case LYS_CASE: + ((struct lysp_node_case *)dup)->child = ((struct lysp_node_case *)pnode)->child; + break; + default: + break; + } + } + +cleanup: + if (ret) { + lysp_dev_node_free(cctx, dup); + } else { + *dup_p = dup; + } + return ret; +} + +#define AMEND_WRONG_NODETYPE(AMEND_STR, OP_STR, PROPERTY) \ + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid %s of %s node - it is not possible to %s \"%s\" property.", \ + AMEND_STR, lys_nodetype2str(target->nodetype), OP_STR, PROPERTY);\ + ret = LY_EVALID; \ + goto cleanup; + +#define AMEND_CHECK_CARDINALITY(ARRAY, MAX, AMEND_STR, PROPERTY) \ + if (LY_ARRAY_COUNT(ARRAY) > MAX) { \ + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid %s of %s with too many (%"LY_PRI_ARRAY_COUNT_TYPE") %s properties.", \ + AMEND_STR, lys_nodetype2str(target->nodetype), LY_ARRAY_COUNT(ARRAY), PROPERTY); \ + ret = LY_EVALID; \ + goto cleanup; \ + } + +/** + * @brief Apply refine. + * + * @param[in] ctx Compile context. + * @param[in] rfn Refine to apply. + * @param[in] rfn_pmod Local module fo the refine. + * @param[in,out] target Refine target. + * @return LY_ERR value. + */ +static LY_ERR +lys_apply_refine(struct lysc_ctx *ctx, struct lysp_refine *rfn, const struct lysp_module *rfn_pmod, struct lysp_node *target) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_module *orig_mod = ctx->cur_mod; + struct lysp_module *orig_pmod = ctx->pmod; + LY_ARRAY_COUNT_TYPE u; + struct lysp_qname *qname; + struct lysp_restr **musts, *must; + uint32_t *num; + + /* use module from the refine */ + ctx->cur_mod = rfn_pmod->mod; + ctx->pmod = (struct lysp_module *)rfn_pmod; + + /* keep the current path and add to it */ + lysc_update_path(ctx, NULL, "{refine}"); + lysc_update_path(ctx, NULL, rfn->nodeid); + + /* default value */ + if (rfn->dflts) { + switch (target->nodetype) { + case LYS_LEAF: + AMEND_CHECK_CARDINALITY(rfn->dflts, 1, "refine", "default"); + + lydict_remove(ctx->ctx, ((struct lysp_node_leaf *)target)->dflt.str); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &rfn->dflts[0], &((struct lysp_node_leaf *)target)->dflt), cleanup); + break; + case LYS_LEAFLIST: + if (rfn->dflts[0].mod->version < LYS_VERSION_1_1) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Invalid refine of default in leaf-list - the default statement is allowed only in YANG 1.1 modules."); + ret = LY_EVALID; + goto cleanup; + } + + FREE_ARRAY(ctx->ctx, ((struct lysp_node_leaflist *)target)->dflts, lysp_qname_free); + ((struct lysp_node_leaflist *)target)->dflts = NULL; + LY_ARRAY_FOR(rfn->dflts, u) { + LY_ARRAY_NEW_GOTO(ctx->ctx, ((struct lysp_node_leaflist *)target)->dflts, qname, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &rfn->dflts[u], qname), cleanup); + } + break; + case LYS_CHOICE: + AMEND_CHECK_CARDINALITY(rfn->dflts, 1, "refine", "default"); + + lydict_remove(ctx->ctx, ((struct lysp_node_choice *)target)->dflt.str); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &rfn->dflts[0], &((struct lysp_node_choice *)target)->dflt), cleanup); + break; + default: + AMEND_WRONG_NODETYPE("refine", "replace", "default"); + } + } + + /* description */ + if (rfn->dsc) { + lydict_remove(ctx->ctx, target->dsc); + DUP_STRING_GOTO(ctx->ctx, rfn->dsc, target->dsc, ret, cleanup); + } + + /* reference */ + if (rfn->ref) { + lydict_remove(ctx->ctx, target->ref); + DUP_STRING_GOTO(ctx->ctx, rfn->ref, target->ref, ret, cleanup); + } + + /* config */ + if (rfn->flags & LYS_CONFIG_MASK) { + if (ctx->compile_opts & LYS_COMPILE_NO_CONFIG) { + LOGWRN(ctx->ctx, "Refining config inside %s has no effect (%s).", + (ctx->compile_opts & (LYS_IS_INPUT | LYS_IS_OUTPUT)) ? "RPC/action" : + ctx->compile_opts & LYS_IS_NOTIF ? "notification" : "a subtree ignoring config", ctx->path); + } else { + target->flags &= ~LYS_CONFIG_MASK; + target->flags |= rfn->flags & LYS_CONFIG_MASK; + } + } + + /* mandatory */ + if (rfn->flags & LYS_MAND_MASK) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_CHOICE: + case LYS_ANYDATA: + case LYS_ANYXML: + break; + default: + AMEND_WRONG_NODETYPE("refine", "replace", "mandatory"); + } + + target->flags &= ~LYS_MAND_MASK; + target->flags |= rfn->flags & LYS_MAND_MASK; + } + + /* presence */ + if (rfn->presence) { + if (target->nodetype != LYS_CONTAINER) { + AMEND_WRONG_NODETYPE("refine", "replace", "presence"); + } + + lydict_remove(ctx->ctx, ((struct lysp_node_container *)target)->presence); + DUP_STRING_GOTO(ctx->ctx, rfn->presence, ((struct lysp_node_container *)target)->presence, ret, cleanup); + } + + /* must */ + if (rfn->musts) { + switch (target->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + case LYS_LEAF: + case LYS_LEAFLIST: + case LYS_ANYDATA: + case LYS_ANYXML: + musts = &((struct lysp_node_container *)target)->musts; + break; + default: + AMEND_WRONG_NODETYPE("refine", "add", "must"); + } + + LY_ARRAY_FOR(rfn->musts, u) { + LY_ARRAY_NEW_GOTO(ctx->ctx, *musts, must, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_restr_dup(ctx->ctx, rfn_pmod, &rfn->musts[u], must), cleanup); + } + } + + /* min-elements */ + if (rfn->flags & LYS_SET_MIN) { + switch (target->nodetype) { + case LYS_LEAFLIST: + num = &((struct lysp_node_leaflist *)target)->min; + break; + case LYS_LIST: + num = &((struct lysp_node_list *)target)->min; + break; + default: + AMEND_WRONG_NODETYPE("refine", "replace", "min-elements"); + } + + *num = rfn->min; + } + + /* max-elements */ + if (rfn->flags & LYS_SET_MAX) { + switch (target->nodetype) { + case LYS_LEAFLIST: + num = &((struct lysp_node_leaflist *)target)->max; + break; + case LYS_LIST: + num = &((struct lysp_node_list *)target)->max; + break; + default: + AMEND_WRONG_NODETYPE("refine", "replace", "max-elements"); + } + + *num = rfn->max; + } + + /* if-feature */ + if (rfn->iffeatures) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + case LYS_LIST: + case LYS_CONTAINER: + case LYS_CHOICE: + case LYS_CASE: + case LYS_ANYDATA: + case LYS_ANYXML: + break; + default: + AMEND_WRONG_NODETYPE("refine", "add", "if-feature"); + } + + LY_ARRAY_FOR(rfn->iffeatures, u) { + LY_ARRAY_NEW_GOTO(ctx->ctx, target->iffeatures, qname, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &rfn->iffeatures[u], qname), cleanup); + } + } + + /* extension instances */ + DUP_EXTS(ctx->ctx, rfn_pmod, target, lyplg_ext_nodetype2stmt(target->nodetype), rfn->exts, target->exts, lysp_ext_dup); + +cleanup: + ctx->cur_mod = orig_mod; + ctx->pmod = orig_pmod; + + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + return ret; +} + +/** + * @brief Apply deviate add. + * + * @param[in] ctx Compile context. + * @param[in] d Deviate add to apply. + * @param[in,out] target Deviation target. + * @return LY_ERR value. + */ +static LY_ERR +lys_apply_deviate_add(struct lysc_ctx *ctx, struct lysp_deviate_add *d, struct lysp_node *target) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; + struct lysp_qname *qname; + uint32_t *num; + struct lysp_restr **musts, *must; + +#define DEV_CHECK_NONPRESENCE(TYPE, MEMBER, PROPERTY, VALUEMEMBER) \ + if (((TYPE)target)->MEMBER) { \ + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid deviation adding \"%s\" property which already exists (with value \"%s\").", \ + PROPERTY, ((TYPE)target)->VALUEMEMBER); \ + ret = LY_EVALID; \ + goto cleanup; \ + } + + /* [units-stmt] */ + if (d->units) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "add", "units"); + } + + DEV_CHECK_NONPRESENCE(struct lysp_node_leaf *, units, "units", units); + DUP_STRING_GOTO(ctx->ctx, d->units, ((struct lysp_node_leaf *)target)->units, ret, cleanup); + } + + /* *must-stmt */ + if (d->musts) { + musts = lysp_node_musts_p(target); + if (!musts) { + AMEND_WRONG_NODETYPE("deviation", "add", "must"); + } + + LY_ARRAY_FOR(d->musts, u) { + LY_ARRAY_NEW_GOTO(ctx->ctx, *musts, must, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_restr_dup(ctx->ctx, ctx->pmod, &d->musts[u], must), cleanup); + } + } + + /* *unique-stmt */ + if (d->uniques) { + if (target->nodetype != LYS_LIST) { + AMEND_WRONG_NODETYPE("deviation", "add", "unique"); + } + + LY_ARRAY_FOR(d->uniques, u) { + LY_ARRAY_NEW_GOTO(ctx->ctx, ((struct lysp_node_list *)target)->uniques, qname, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->uniques[u], qname), cleanup); + } + } + + /* *default-stmt */ + if (d->dflts) { + switch (target->nodetype) { + case LYS_LEAF: + AMEND_CHECK_CARDINALITY(d->dflts, 1, "deviation", "default"); + DEV_CHECK_NONPRESENCE(struct lysp_node_leaf *, dflt.str, "default", dflt.str); + + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflts[0], &((struct lysp_node_leaf *)target)->dflt), cleanup); + break; + case LYS_LEAFLIST: + LY_ARRAY_FOR(d->dflts, u) { + LY_ARRAY_NEW_GOTO(ctx->ctx, ((struct lysp_node_leaflist *)target)->dflts, qname, ret, cleanup); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflts[u], qname), cleanup); + } + break; + case LYS_CHOICE: + AMEND_CHECK_CARDINALITY(d->dflts, 1, "deviation", "default"); + DEV_CHECK_NONPRESENCE(struct lysp_node_choice *, dflt.str, "default", dflt.str); + + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflts[0], &((struct lysp_node_choice *)target)->dflt), cleanup); + break; + default: + AMEND_WRONG_NODETYPE("deviation", "add", "default"); + } + } + + /* [config-stmt] */ + if (d->flags & LYS_CONFIG_MASK) { + switch (target->nodetype) { + case LYS_CONTAINER: + case LYS_LEAF: + case LYS_LEAFLIST: + case LYS_LIST: + case LYS_CHOICE: + case LYS_ANYDATA: + case LYS_ANYXML: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "add", "config"); + } + + if (target->flags & LYS_CONFIG_MASK) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid deviation adding \"config\" property which already exists (with value \"config %s\").", + target->flags & LYS_CONFIG_W ? "true" : "false"); + ret = LY_EVALID; + goto cleanup; + } + + target->flags |= d->flags & LYS_CONFIG_MASK; + } + + /* [mandatory-stmt] */ + if (d->flags & LYS_MAND_MASK) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_CHOICE: + case LYS_ANYDATA: + case LYS_ANYXML: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "add", "mandatory"); + } + + if (target->flags & LYS_MAND_MASK) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid deviation adding \"mandatory\" property which already exists (with value \"mandatory %s\").", + target->flags & LYS_MAND_TRUE ? "true" : "false"); + ret = LY_EVALID; + goto cleanup; + } + + target->flags |= d->flags & LYS_MAND_MASK; + } + + /* [min-elements-stmt] */ + if (d->flags & LYS_SET_MIN) { + switch (target->nodetype) { + case LYS_LEAFLIST: + num = &((struct lysp_node_leaflist *)target)->min; + break; + case LYS_LIST: + num = &((struct lysp_node_list *)target)->min; + break; + default: + AMEND_WRONG_NODETYPE("deviation", "add", "min-elements"); + } + + if (target->flags & LYS_SET_MIN) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid deviation adding \"min-elements\" property which already exists (with value \"%u\").", *num); + ret = LY_EVALID; + goto cleanup; + } + + *num = d->min; + } + + /* [max-elements-stmt] */ + if (d->flags & LYS_SET_MAX) { + switch (target->nodetype) { + case LYS_LEAFLIST: + num = &((struct lysp_node_leaflist *)target)->max; + break; + case LYS_LIST: + num = &((struct lysp_node_list *)target)->max; + break; + default: + AMEND_WRONG_NODETYPE("deviation", "add", "max-elements"); + } + + if (target->flags & LYS_SET_MAX) { + if (*num) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid deviation adding \"max-elements\" property which already exists (with value \"%u\").", + *num); + } else { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid deviation adding \"max-elements\" property which already exists (with value \"unbounded\")."); + } + ret = LY_EVALID; + goto cleanup; + } + + *num = d->max; + } + +cleanup: + return ret; +} + +/** + * @brief Apply deviate delete. + * + * @param[in] ctx Compile context. + * @param[in] d Deviate delete to apply. + * @param[in,out] target Deviation target. + * @return LY_ERR value. + */ +static LY_ERR +lys_apply_deviate_delete(struct lysc_ctx *ctx, struct lysp_deviate_del *d, struct lysp_node *target) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_restr **musts; + LY_ARRAY_COUNT_TYPE u, v; + struct lysp_qname **uniques, **dflts; + +#define DEV_DEL_ARRAY(DEV_ARRAY, ORIG_ARRAY, DEV_MEMBER, ORIG_MEMBER, FREE_FUNC, FREE_CTX, PROPERTY) \ + LY_ARRAY_FOR(d->DEV_ARRAY, u) { \ + int found = 0; \ + LY_ARRAY_FOR(ORIG_ARRAY, v) { \ + if (!strcmp(d->DEV_ARRAY[u]DEV_MEMBER, (ORIG_ARRAY)[v]ORIG_MEMBER)) { \ + found = 1; \ + break; \ + } \ + } \ + if (!found) { \ + LOGVAL(ctx->ctx, LYVE_REFERENCE, \ + "Invalid deviation deleting \"%s\" property \"%s\" which does not match any of the target's property values.", \ + PROPERTY, d->DEV_ARRAY[u]DEV_MEMBER); \ + ret = LY_EVALID; \ + goto cleanup; \ + } \ + LY_ARRAY_DECREMENT(ORIG_ARRAY); \ + FREE_FUNC(FREE_CTX, &(ORIG_ARRAY)[v]); \ + if (v < LY_ARRAY_COUNT(ORIG_ARRAY)) { \ + memmove(&(ORIG_ARRAY)[v], &(ORIG_ARRAY)[v + 1], (LY_ARRAY_COUNT(ORIG_ARRAY) - v) * sizeof *(ORIG_ARRAY)); \ + } \ + } \ + if (!LY_ARRAY_COUNT(ORIG_ARRAY)) { \ + LY_ARRAY_FREE(ORIG_ARRAY); \ + ORIG_ARRAY = NULL; \ + } + +#define DEV_CHECK_PRESENCE_VALUE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \ + if (!((TYPE)target)->MEMBER) { \ + LOGVAL(ctx->ctx, LY_VCODE_DEV_NOT_PRESENT, DEVTYPE, PROPERTY, VALUE); \ + ret = LY_EVALID; \ + goto cleanup; \ + } else if (strcmp(((TYPE)target)->MEMBER, VALUE)) { \ + LOGVAL(ctx->ctx, LYVE_REFERENCE, \ + "Invalid deviation deleting \"%s\" property \"%s\" which does not match the target's property value \"%s\".", \ + PROPERTY, VALUE, ((TYPE)target)->MEMBER); \ + ret = LY_EVALID; \ + goto cleanup; \ + } + + /* [units-stmt] */ + if (d->units) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "delete", "units"); + } + + DEV_CHECK_PRESENCE_VALUE(struct lysp_node_leaf *, units, "deleting", "units", d->units); + lydict_remove(ctx->ctx, ((struct lysp_node_leaf *)target)->units); + ((struct lysp_node_leaf *)target)->units = NULL; + } + + /* *must-stmt */ + if (d->musts) { + musts = lysp_node_musts_p(target); + if (!musts) { + AMEND_WRONG_NODETYPE("deviation", "delete", "must"); + } + + DEV_DEL_ARRAY(musts, *musts, .arg.str, .arg.str, lysp_restr_free, &ctx->free_ctx, "must"); + } + + /* *unique-stmt */ + if (d->uniques) { + if (target->nodetype != LYS_LIST) { + AMEND_WRONG_NODETYPE("deviation", "delete", "unique"); + } + + uniques = &((struct lysp_node_list *)target)->uniques; + DEV_DEL_ARRAY(uniques, *uniques, .str, .str, lysp_qname_free, ctx->ctx, "unique"); + } + + /* *default-stmt */ + if (d->dflts) { + switch (target->nodetype) { + case LYS_LEAF: + AMEND_CHECK_CARDINALITY(d->dflts, 1, "deviation", "default"); + DEV_CHECK_PRESENCE_VALUE(struct lysp_node_leaf *, dflt.str, "deleting", "default", d->dflts[0].str); + + lydict_remove(ctx->ctx, ((struct lysp_node_leaf *)target)->dflt.str); + ((struct lysp_node_leaf *)target)->dflt.str = NULL; + break; + case LYS_LEAFLIST: + dflts = &((struct lysp_node_leaflist *)target)->dflts; + DEV_DEL_ARRAY(dflts, *dflts, .str, .str, lysp_qname_free, ctx->ctx, "default"); + break; + case LYS_CHOICE: + AMEND_CHECK_CARDINALITY(d->dflts, 1, "deviation", "default"); + DEV_CHECK_PRESENCE_VALUE(struct lysp_node_choice *, dflt.str, "deleting", "default", d->dflts[0].str); + + lydict_remove(ctx->ctx, ((struct lysp_node_choice *)target)->dflt.str); + ((struct lysp_node_choice *)target)->dflt.str = NULL; + break; + default: + AMEND_WRONG_NODETYPE("deviation", "delete", "default"); + } + } + +cleanup: + return ret; +} + +/** + * @brief Apply deviate replace. + * + * @param[in] ctx Compile context. + * @param[in] d Deviate replace to apply. + * @param[in,out] target Deviation target. + * @return LY_ERR value. + */ +static LY_ERR +lys_apply_deviate_replace(struct lysc_ctx *ctx, struct lysp_deviate_rpl *d, struct lysp_node *target) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t *num; + +#define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \ + if (!((TYPE)target)->MEMBER) { \ + LOGVAL(ctx->ctx, LY_VCODE_DEV_NOT_PRESENT, DEVTYPE, PROPERTY, VALUE); \ + ret = LY_EVALID; \ + goto cleanup; \ + } + + /* [type-stmt] */ + if (d->type) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "type"); + } + + lysp_type_free(&ctx->free_ctx, &((struct lysp_node_leaf *)target)->type); + lysp_type_dup(ctx->ctx, ctx->pmod, d->type, &((struct lysp_node_leaf *)target)->type); + } + + /* [units-stmt] */ + if (d->units) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "units"); + } + + DEV_CHECK_PRESENCE(struct lysp_node_leaf *, units, "replacing", "units", d->units); + lydict_remove(ctx->ctx, ((struct lysp_node_leaf *)target)->units); + DUP_STRING_GOTO(ctx->ctx, d->units, ((struct lysp_node_leaf *)target)->units, ret, cleanup); + } + + /* [default-stmt] */ + if (d->dflt.str) { + switch (target->nodetype) { + case LYS_LEAF: + DEV_CHECK_PRESENCE(struct lysp_node_leaf *, dflt.str, "replacing", "default", d->dflt.str); + + lydict_remove(ctx->ctx, ((struct lysp_node_leaf *)target)->dflt.str); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflt, &((struct lysp_node_leaf *)target)->dflt), cleanup); + break; + case LYS_CHOICE: + DEV_CHECK_PRESENCE(struct lysp_node_choice *, dflt.str, "replacing", "default", d->dflt); + + lydict_remove(ctx->ctx, ((struct lysp_node_choice *)target)->dflt.str); + LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflt, &((struct lysp_node_choice *)target)->dflt), cleanup); + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "default"); + } + } + + /* [config-stmt] */ + if (d->flags & LYS_CONFIG_MASK) { + switch (target->nodetype) { + case LYS_CONTAINER: + case LYS_LEAF: + case LYS_LEAFLIST: + case LYS_LIST: + case LYS_CHOICE: + case LYS_ANYDATA: + case LYS_ANYXML: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "config"); + } + + if (!(target->flags & LYS_CONFIG_MASK)) { + LOGVAL(ctx->ctx, LY_VCODE_DEV_NOT_PRESENT, "replacing", "config", + d->flags & LYS_CONFIG_W ? "config true" : "config false"); + ret = LY_EVALID; + goto cleanup; + } + + target->flags &= ~LYS_CONFIG_MASK; + target->flags |= d->flags & LYS_CONFIG_MASK; + } + + /* [mandatory-stmt] */ + if (d->flags & LYS_MAND_MASK) { + switch (target->nodetype) { + case LYS_LEAF: + case LYS_CHOICE: + case LYS_ANYDATA: + case LYS_ANYXML: + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "mandatory"); + } + + if (!(target->flags & LYS_MAND_MASK)) { + LOGVAL(ctx->ctx, LY_VCODE_DEV_NOT_PRESENT, "replacing", "mandatory", + d->flags & LYS_MAND_TRUE ? "mandatory true" : "mandatory false"); + ret = LY_EVALID; + goto cleanup; + } + + target->flags &= ~LYS_MAND_MASK; + target->flags |= d->flags & LYS_MAND_MASK; + } + + /* [min-elements-stmt] */ + if (d->flags & LYS_SET_MIN) { + switch (target->nodetype) { + case LYS_LEAFLIST: + num = &((struct lysp_node_leaflist *)target)->min; + break; + case LYS_LIST: + num = &((struct lysp_node_list *)target)->min; + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "min-elements"); + } + + if (!(target->flags & LYS_SET_MIN)) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid deviation replacing \"min-elements\" property which is not present."); + ret = LY_EVALID; + goto cleanup; + } + + *num = d->min; + } + + /* [max-elements-stmt] */ + if (d->flags & LYS_SET_MAX) { + switch (target->nodetype) { + case LYS_LEAFLIST: + num = &((struct lysp_node_leaflist *)target)->max; + break; + case LYS_LIST: + num = &((struct lysp_node_list *)target)->max; + break; + default: + AMEND_WRONG_NODETYPE("deviation", "replace", "max-elements"); + } + + if (!(target->flags & LYS_SET_MAX)) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid deviation replacing \"max-elements\" property which is not present."); + ret = LY_EVALID; + goto cleanup; + } + + *num = d->max; + } + +cleanup: + return ret; +} + +/** + * @brief Apply deviation with all its deviates. + * + * @param[in] ctx Compile context. + * @param[in] dev Deviation to apply. + * @param[in] dev_pmod Local module of the deviation. + * @param[in,out] target Deviation target. + * @return LY_ERR value. + */ +static LY_ERR +lys_apply_deviation(struct lysc_ctx *ctx, struct lysp_deviation *dev, const struct lysp_module *dev_pmod, + struct lysp_node *target) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_module *orig_mod = ctx->cur_mod; + struct lysp_module *orig_pmod = ctx->pmod; + char orig_path[LYSC_CTX_BUFSIZE]; + struct lysp_deviate *d; + + /* clear path and set modules */ + strcpy(orig_path, ctx->path); + ctx->path_len = 1; + ctx->cur_mod = dev_pmod->mod; + ctx->pmod = (struct lysp_module *)dev_pmod; + + /* generate correct path */ + lysc_update_path(ctx, NULL, "{deviation}"); + lysc_update_path(ctx, NULL, dev->nodeid); + + LY_LIST_FOR(dev->deviates, d) { + switch (d->mod) { + case LYS_DEV_ADD: + ret = lys_apply_deviate_add(ctx, (struct lysp_deviate_add *)d, target); + break; + case LYS_DEV_DELETE: + ret = lys_apply_deviate_delete(ctx, (struct lysp_deviate_del *)d, target); + break; + case LYS_DEV_REPLACE: + ret = lys_apply_deviate_replace(ctx, (struct lysp_deviate_rpl *)d, target); + break; + default: + LOGINT(ctx->ctx); + ret = LY_EINT; + } + LY_CHECK_GOTO(ret, cleanup); + } + + /* deviation extension instances */ + DUP_EXTS(ctx->ctx, dev_pmod, target, lyplg_ext_nodetype2stmt(target->nodetype), dev->exts, target->exts, lysp_ext_dup); + +cleanup: + ctx->cur_mod = orig_mod; + ctx->pmod = orig_pmod; + + strcpy(ctx->path, orig_path); + ctx->path_len = strlen(ctx->path); + return ret; +} + +/** + * @brief Check whether a compiled node matches a single schema nodeid name test. + * + * @param[in,out] node Compiled node to consider. On a match it is moved to its parent. + * @param[in] mod Expected module. + * @param[in] name Expected name. + * @param[in] name_len Length of @p name. + * @return Whether it is a match or not. + */ +static ly_bool +lysp_schema_nodeid_match_node(const struct lysc_node **node, const struct lys_module *mod, const char *name, + size_t name_len) +{ + /* compare with the module of the node */ + if ((*node)->module != mod) { + return 0; + } + + /* compare names */ + if (ly_strncmp((*node)->name, name, name_len)) { + return 0; + } + + /* move to next parent */ + *node = (*node)->parent; + + return 1; +} + +/** + * @brief Check whether a compiled ext instance matches a single schema nodeid name test. + * + * @param[in,out] ext Compiled ext instance to consider. On a match it is zeroed to not match again. + * @param[in] mod Expected module. + * @param[in] name Expected name. + * @param[in] name_len Length of @p name. + * @return Whether it is a match or not. + */ +static ly_bool +lysp_schema_nodeid_match_ext(const struct lysc_ext_instance **ext, const struct lys_module *mod, const char *name, + size_t name_len) +{ + /* compare with the module */ + if ((*ext)->module != mod) { + return 0; + } + + /* compare names (argument) */ + if (ly_strncmp((*ext)->argument, name, name_len)) { + return 0; + } + + /* zero */ + *ext = NULL; + + return 1; +} + +/** + * @brief Check whether a node matches specific schema nodeid. + * + * @param[in] exp Parsed nodeid to match. + * @param[in] exp_pmod Module to use for nodes in @p exp without a prefix. + * @param[in] exp_ext Extension instance in which @p exp is defined, it means it targets an extension instance. + * @param[in] ctx_node Initial context node that should match, only for descendant paths. + * @param[in] parent First compiled parent to consider. If @p pnode is NULL, it is condered the node to be matched. + * @param[in] pnode Parsed node to be matched. May be NULL if the target node was already compiled. + * @param[in] pnode_mod Compiled @p pnode to-be module. + * @param[in] pnode_ext Extension instance in which @p pnode is defined. + * @return Whether it is a match or not. + */ +static ly_bool +lysp_schema_nodeid_match(const struct lyxp_expr *exp, const struct lysp_module *exp_pmod, + const struct lysp_ext_instance *exp_ext, const struct lysc_node *ctx_node, const struct lysc_node *parent, + const struct lysp_node *pnode, const struct lys_module *pnode_mod, const struct lysc_ext_instance *pnode_ext) +{ + uint32_t i; + const struct lys_module *mod; + const char *name = NULL; + size_t name_len = 0; + + if (exp_ext && !pnode_ext) { + /* extension instance augment and standard node, will never match */ + return 0; + } else if (!exp_ext && pnode_ext) { + /* standard augment and extension instance node, will never match */ + return 0; + } + + /* compare last node in the node ID */ + i = exp->used - 1; + + /* get exp node ID module */ + mod = lys_schema_node_get_module(exp_pmod->mod->ctx, exp->expr + exp->tok_pos[i], exp->tok_len[i], exp_pmod, &name, &name_len); + assert(mod); + + if (pnode) { + /* compare on the last parsed-only node */ + if ((pnode_mod != mod) || ly_strncmp(pnode->name, name, name_len)) { + return 0; + } + } else { + /* using parent directly */ + if (!lysp_schema_nodeid_match_node(&parent, mod, name, name_len)) { + return 0; + } + } + + /* now compare all the compiled parents */ + while (i > 1) { + i -= 2; + assert(exp->tokens[i] == LYXP_TOKEN_NAMETEST); + + if (!parent && !pnode_ext) { + /* no more parents but path continues */ + return 0; + } + + /* get exp node ID module */ + mod = lys_schema_node_get_module(exp_pmod->mod->ctx, exp->expr + exp->tok_pos[i], exp->tok_len[i], exp_pmod, &name, + &name_len); + assert(mod); + + if (parent) { + /* compare with the parent */ + if (!lysp_schema_nodeid_match_node(&parent, mod, name, name_len)) { + return 0; + } + } else { + /* compare with the ext instance */ + if (!lysp_schema_nodeid_match_ext(&pnode_ext, mod, name, name_len)) { + return 0; + } + } + } + + if (ctx_node && (ctx_node != parent)) { + /* descendant path has not finished in the context node */ + return 0; + } else if (!ctx_node && (parent || pnode_ext)) { + /* some parent/extension was not matched */ + return 0; + } + + return 1; +} + +void +lysc_augment_free(const struct ly_ctx *ctx, struct lysc_augment *aug) +{ + if (aug) { + lyxp_expr_free(ctx, aug->nodeid); + + free(aug); + } +} + +void +lysc_deviation_free(const struct ly_ctx *ctx, struct lysc_deviation *dev) +{ + if (dev) { + lyxp_expr_free(ctx, dev->nodeid); + LY_ARRAY_FREE(dev->devs); + LY_ARRAY_FREE(dev->dev_pmods); + + free(dev); + } +} + +void +lysc_refine_free(const struct ly_ctx *ctx, struct lysc_refine *rfn) +{ + if (rfn) { + lyxp_expr_free(ctx, rfn->nodeid); + LY_ARRAY_FREE(rfn->rfns); + + free(rfn); + } +} + +void +lysp_dev_node_free(struct lysc_ctx *cctx, struct lysp_node *dev_pnode) +{ + if (!dev_pnode) { + return; + } + + switch (dev_pnode->nodetype) { + case LYS_CONTAINER: + ((struct lysp_node_container *)dev_pnode)->child = NULL; + ((struct lysp_node_container *)dev_pnode)->actions = NULL; + ((struct lysp_node_container *)dev_pnode)->notifs = NULL; + break; + case LYS_LIST: + ((struct lysp_node_list *)dev_pnode)->child = NULL; + ((struct lysp_node_list *)dev_pnode)->actions = NULL; + ((struct lysp_node_list *)dev_pnode)->notifs = NULL; + break; + case LYS_CHOICE: + ((struct lysp_node_choice *)dev_pnode)->child = NULL; + break; + case LYS_CASE: + ((struct lysp_node_case *)dev_pnode)->child = NULL; + break; + case LYS_LEAF: + case LYS_LEAFLIST: + case LYS_ANYXML: + case LYS_ANYDATA: + /* no children */ + break; + case LYS_NOTIF: + ((struct lysp_node_notif *)dev_pnode)->child = NULL; + break; + case LYS_RPC: + case LYS_ACTION: + ((struct lysp_node_action *)dev_pnode)->input.child = NULL; + ((struct lysp_node_action *)dev_pnode)->output.child = NULL; + break; + case LYS_INPUT: + case LYS_OUTPUT: + ((struct lysp_node_action_inout *)dev_pnode)->child = NULL; + lysp_node_free(&cctx->free_ctx, dev_pnode); + free(dev_pnode); + return; + default: + LOGINT(cctx->ctx); + return; + } + + lysp_node_free(&cctx->free_ctx, dev_pnode); +} + +LY_ERR +lys_compile_node_deviations_refines(struct lysc_ctx *ctx, const struct lysp_node *pnode, const struct lysc_node *parent, + struct lysp_node **dev_pnode, ly_bool *not_supported) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t i; + LY_ARRAY_COUNT_TYPE u; + struct lysc_refine *rfn; + struct lysc_deviation *dev; + + *dev_pnode = NULL; + *not_supported = 0; + + for (i = 0; i < ctx->uses_rfns.count; ) { + rfn = ctx->uses_rfns.objs[i]; + + if (!lysp_schema_nodeid_match(rfn->nodeid, rfn->nodeid_pmod, NULL, rfn->nodeid_ctx_node, parent, pnode, + ctx->cur_mod, ctx->ext)) { + /* not our target node */ + ++i; + continue; + } + + if (!*dev_pnode) { + /* first refine on this node, create a copy first */ + LY_CHECK_GOTO(ret = lysp_dup_single(ctx, pnode, 1, dev_pnode), cleanup); + } + + /* apply all the refines by changing (the copy of) the parsed node */ + LY_ARRAY_FOR(rfn->rfns, u) { + LY_CHECK_GOTO(ret = lys_apply_refine(ctx, rfn->rfns[u], rfn->nodeid_pmod, *dev_pnode), cleanup); + } + + /* refine was applied, remove it */ + lysc_refine_free(ctx->ctx, rfn); + ly_set_rm_index(&ctx->uses_rfns, i, NULL); + + /* refines use relative paths so more may apply to a single node */ + } + + for (i = 0; i < ctx->devs.count; ++i) { + dev = ctx->devs.objs[i]; + + if (!lysp_schema_nodeid_match(dev->nodeid, dev->dev_pmods[0], NULL, NULL, parent, pnode, ctx->cur_mod, ctx->ext)) { + /* not our target node */ + continue; + } + + if (dev->not_supported) { + /* it is not supported, no more deviations */ + *not_supported = 1; + goto dev_applied; + } + + if (!*dev_pnode) { + /* first deviation on this node, create a copy first */ + LY_CHECK_GOTO(ret = lysp_dup_single(ctx, pnode, 1, dev_pnode), cleanup); + } + + /* apply all the deviates by changing (the copy of) the parsed node */ + LY_ARRAY_FOR(dev->devs, u) { + LY_CHECK_GOTO(ret = lys_apply_deviation(ctx, dev->devs[u], dev->dev_pmods[u], *dev_pnode), cleanup); + } + +dev_applied: + /* deviation was applied, remove it */ + lysc_deviation_free(ctx->ctx, dev); + ly_set_rm_index(&ctx->devs, i, NULL); + + /* all the deviations for one target node are in one structure, we are done */ + break; + } + +cleanup: + if (ret) { + lysp_dev_node_free(ctx, *dev_pnode); + *dev_pnode = NULL; + *not_supported = 0; + } + return ret; +} + +/** + * @brief Compile augment children. + * + * @param[in] ctx Compile context. + * @param[in] aug_when Parsed augment when to inherit. + * @param[in] aug_flags Parsed augment flags. + * @param[in] child First augment child to compile. + * @param[in] target Target node of the augment. + * @param[in] child_unres_disabled Whether the children are to be put into unres disabled set or not. + * @return LY_SUCCESS on success. + * @return LY_EVALID on failure. + */ +static LY_ERR +lys_compile_augment_children(struct lysc_ctx *ctx, struct lysp_when *aug_when, uint16_t aug_flags, struct lysp_node *child, + struct lysc_node *target, ly_bool child_unres_disabled) +{ + LY_ERR rc = LY_SUCCESS; + struct lysp_node *pnode; + struct lysc_node *node; + struct lysc_when *when_shared = NULL; + ly_bool enabled, allow_mand = 0; + struct ly_set child_set = {0}; + uint32_t i, opt_prev = ctx->compile_opts; + + /* check for mandatory nodes + * - new cases augmenting some choice can have mandatory nodes + * - mandatory nodes are allowed only in case the augmentation is made conditional with a when statement + */ + if (aug_when || (target->nodetype == LYS_CHOICE) || (ctx->cur_mod == target->module)) { + allow_mand = 1; + } + + LY_LIST_FOR(child, pnode) { + /* check if the subnode can be connected to the found target (e.g. case cannot be inserted into container) */ + if (((pnode->nodetype == LYS_CASE) && (target->nodetype != LYS_CHOICE)) || + ((pnode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && !(target->nodetype & (LYS_CONTAINER | LYS_LIST))) || + ((pnode->nodetype == LYS_USES) && (target->nodetype == LYS_CHOICE))) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid augment of %s node which is not allowed to contain %s node \"%s\".", + lys_nodetype2str(target->nodetype), lys_nodetype2str(pnode->nodetype), pnode->name); + rc = LY_EVALID; + goto cleanup; + } + + /* compile the children */ + if (target->nodetype == LYS_CHOICE) { + LY_CHECK_GOTO(rc = lys_compile_node_choice_child(ctx, pnode, target, &child_set), cleanup); + } else if (target->nodetype & (LYS_INPUT | LYS_OUTPUT)) { + if (target->nodetype == LYS_INPUT) { + ctx->compile_opts |= LYS_COMPILE_RPC_INPUT; + } else { + ctx->compile_opts |= LYS_COMPILE_RPC_OUTPUT; + } + LY_CHECK_GOTO(rc = lys_compile_node(ctx, pnode, target, aug_flags, &child_set), cleanup); + } else { + LY_CHECK_GOTO(rc = lys_compile_node(ctx, pnode, target, aug_flags, &child_set), cleanup); + } + + /* eval if-features again for the rest of this node processing */ + LY_CHECK_GOTO(rc = lys_eval_iffeatures(ctx->ctx, pnode->iffeatures, &enabled), cleanup); + if (!enabled && !(ctx->compile_opts & (LYS_COMPILE_NO_DISABLED | LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING))) { + ctx->compile_opts |= LYS_COMPILE_DISABLED; + } + + /* since the augment node is not present in the compiled tree, we need to pass some of its + * statements to all its children */ + for (i = 0; i < child_set.count; ++i) { + node = child_set.snodes[i]; + if (!allow_mand && (node->flags & LYS_CONFIG_W) && (node->flags & LYS_MAND_TRUE)) { + node->flags &= ~LYS_MAND_TRUE; + lys_compile_mandatory_parents(target, 0); + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Invalid augment adding mandatory node \"%s\" without making it conditional via when statement.", + node->name); + rc = LY_EVALID; + goto cleanup; + } + + if (aug_when) { + /* pass augment's when to all the children */ + rc = lys_compile_when(ctx, aug_when, aug_flags, target, lysc_data_node(target), node, &when_shared); + LY_CHECK_GOTO(rc, cleanup); + } + + if (child_unres_disabled) { + /* child is disabled by the augment if-features */ + ly_set_add(&ctx->unres->disabled, node, 1, NULL); + } + } + + /* next iter */ + ly_set_erase(&child_set, NULL); + ctx->compile_opts = opt_prev; + } + +cleanup: + ly_set_erase(&child_set, NULL); + ctx->compile_opts = opt_prev; + return rc; +} + +/** + * @brief Compile the parsed augment connecting it into its target. + * + * It is expected that all the data referenced in path are present - augments are ordered so that augment B + * targeting data from augment A is being compiled after augment A. Also the modules referenced in the path + * are already implemented and compiled. + * + * @param[in] ctx Compile context. + * @param[in] aug_p Parsed augment to compile. + * @param[in] target Target node of the augment. + * @return LY_SUCCESS on success. + * @return LY_EVALID on failure. + */ +static LY_ERR +lys_compile_augment(struct lysc_ctx *ctx, struct lysp_node_augment *aug_p, struct lysc_node *target) +{ + LY_ERR rc = LY_SUCCESS; + ly_bool enabled, child_unres_disabled = 0; + uint32_t opt_prev = ctx->compile_opts; + + assert(target->nodetype & (LYS_CONTAINER | LYS_LIST | LYS_CHOICE | LYS_CASE | LYS_INPUT | LYS_OUTPUT | LYS_NOTIF)); + + /* nodetype checks */ + if (aug_p->actions && !lysc_node_actions_p(target)) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid augment of %s node which is not allowed to contain RPC/action node \"%s\".", + lys_nodetype2str(target->nodetype), aug_p->actions->name); + rc = LY_EVALID; + goto cleanup; + } + if (aug_p->notifs && !lysc_node_notifs_p(target)) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid augment of %s node which is not allowed to contain notification node \"%s\".", + lys_nodetype2str(target->nodetype), aug_p->notifs->name); + rc = LY_EVALID; + goto cleanup; + } + + /* augment if-features */ + LY_CHECK_GOTO(rc = lys_eval_iffeatures(ctx->ctx, aug_p->iffeatures, &enabled), cleanup); + if (!enabled && !(ctx->compile_opts & (LYS_COMPILE_NO_DISABLED | LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING))) { + ctx->compile_opts |= LYS_COMPILE_DISABLED; + child_unres_disabled = 1; + } + + /* augment children */ + rc = lys_compile_augment_children(ctx, aug_p->when, aug_p->flags, aug_p->child, target, child_unres_disabled); + LY_CHECK_GOTO(rc, cleanup); + + /* augment actions */ + rc = lys_compile_augment_children(ctx, aug_p->when, aug_p->flags, (struct lysp_node *)aug_p->actions, target, + child_unres_disabled); + LY_CHECK_GOTO(rc, cleanup); + + /* augment notifications */ + rc = lys_compile_augment_children(ctx, aug_p->when, aug_p->flags, (struct lysp_node *)aug_p->notifs, target, + child_unres_disabled); + LY_CHECK_GOTO(rc, cleanup); + + /* compile extensions into the target */ + COMPILE_EXTS_GOTO(ctx, aug_p->exts, target->exts, target, rc, cleanup); + +cleanup: + ctx->compile_opts = opt_prev; + return rc; +} + +LY_ERR +lys_compile_node_augments(struct lysc_ctx *ctx, struct lysc_node *node) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_module *orig_mod = ctx->cur_mod; + struct lysp_module *orig_pmod = ctx->pmod; + uint32_t i; + char orig_path[LYSC_CTX_BUFSIZE]; + struct lysc_augment *aug; + + /* uses augments */ + for (i = 0; i < ctx->uses_augs.count; ) { + aug = ctx->uses_augs.objs[i]; + + if (!lysp_schema_nodeid_match(aug->nodeid, orig_mod->parsed, aug->ext, aug->nodeid_ctx_node, node, NULL, NULL, + ctx->ext)) { + /* not our target node */ + ++i; + continue; + } + + /* use the path and modules from the augment */ + lysc_update_path(ctx, NULL, "{augment}"); + lysc_update_path(ctx, NULL, aug->aug_p->nodeid); + ctx->pmod = (struct lysp_module *)aug->aug_pmod; + + /* apply augment, restore the path */ + ret = lys_compile_augment(ctx, aug->aug_p, node); + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* augment was applied, remove it (index and the whole set may have changed because other augments + * could have been applied) */ + ly_set_rm(&ctx->uses_augs, aug, NULL); + lysc_augment_free(ctx->ctx, aug); + i = 0; + } + + /* top-level augments */ + for (i = 0; i < ctx->augs.count; ) { + aug = ctx->augs.objs[i]; + + if (!lysp_schema_nodeid_match(aug->nodeid, aug->aug_pmod, aug->ext, NULL, node, NULL, NULL, ctx->ext)) { + /* not our target node */ + ++i; + continue; + } + + /* use the path and modules from the augment */ + strcpy(orig_path, ctx->path); + ctx->path_len = 1; + ctx->cur_mod = aug->aug_pmod->mod; + ctx->pmod = (struct lysp_module *)aug->aug_pmod; + lysc_update_path(ctx, NULL, "{augment}"); + lysc_update_path(ctx, NULL, aug->aug_p->nodeid); + + /* apply augment, restore the path */ + ret = lys_compile_augment(ctx, aug->aug_p, node); + strcpy(ctx->path, orig_path); + ctx->path_len = strlen(ctx->path); + LY_CHECK_GOTO(ret, cleanup); + + /* augment was applied, remove it */ + ly_set_rm(&ctx->augs, aug, NULL); + lysc_augment_free(ctx->ctx, aug); + i = 0; + } + +cleanup: + ctx->cur_mod = orig_mod; + ctx->pmod = orig_pmod; + return ret; +} + +/** + * @brief Prepare an absolute-nodeid augment to be applied during data nodes compilation. + * + * @param[in] ctx Compile context. + * @param[in] aug_p Parsed augment to be applied. + * @param[in] pmod Both current and prefix module for @p aug_p. + * @param[in] ext Extension instance in case @p aug_p is defined in one. + * @return LY_ERR value. + */ +static LY_ERR +lys_precompile_own_augment(struct lysc_ctx *ctx, struct lysp_node_augment *aug_p, const struct lysp_module *pmod, + const struct lysp_ext_instance *ext) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *exp = NULL; + struct lysc_augment *aug; + const struct lys_module *mod; + + /* parse its target, it was already parsed and fully checked (except for the existence of the nodes) */ + ret = lyxp_expr_parse(ctx->ctx, aug_p->nodeid, strlen(aug_p->nodeid), 0, &exp); + LY_CHECK_GOTO(ret, cleanup); + + mod = lys_schema_node_get_module(ctx->ctx, exp->expr + exp->tok_pos[1], exp->tok_len[1], pmod, NULL, NULL); + LY_CHECK_ERR_GOTO(!mod, LOGINT(ctx->ctx); ret = LY_EINT, cleanup); + if (mod != ctx->cur_mod) { + /* augment for another module, ignore */ + goto cleanup; + } + + /* allocate new compiled augment and store it in the set */ + aug = calloc(1, sizeof *aug); + LY_CHECK_ERR_GOTO(!aug, LOGMEM(ctx->ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(&ctx->augs, aug, 1, NULL), cleanup); + + aug->nodeid = exp; + exp = NULL; + aug->aug_pmod = pmod; + aug->ext = ext; + aug->aug_p = aug_p; + +cleanup: + lyxp_expr_free(ctx->ctx, exp); + return ret; +} + +/** + * @brief Prepare all top-level augments and extension instance augments to be applied during data nodes compilation. + * + * @param[in] ctx Compile context. + * @param[in] pmod Parsed mod to use. + * @return LY_ERR value. + */ +static LY_ERR +lys_precompile_own_augments_mod(struct lysc_ctx *ctx, const struct lysp_module *pmod) +{ + LY_ARRAY_COUNT_TYPE u, v; + struct lysp_node_augment *aug_p; + + /* module */ + LY_LIST_FOR(pmod->augments, aug_p) { + LY_CHECK_RET(lys_precompile_own_augment(ctx, aug_p, pmod, NULL)); + } + + /* parsed extension instances */ + LY_ARRAY_FOR(pmod->exts, u) { + aug_p = NULL; + LY_ARRAY_FOR(pmod->exts[u].substmts, v) { + if (pmod->exts[u].substmts[v].stmt == LY_STMT_AUGMENT) { + aug_p = *(struct lysp_node_augment **)pmod->exts[u].substmts[v].storage; + break; + } + } + if (!aug_p) { + continue; + } + + LY_CHECK_RET(lys_precompile_own_augment(ctx, aug_p, pmod, &pmod->exts[u])); + } + + return LY_SUCCESS; +} + +LY_ERR +lys_precompile_own_augments(struct lysc_ctx *ctx) +{ + LY_ARRAY_COUNT_TYPE u, v; + const struct lys_module *aug_mod; + const struct lysp_module *submod; + + LY_ARRAY_FOR(ctx->cur_mod->augmented_by, u) { + aug_mod = ctx->cur_mod->augmented_by[u]; + + /* collect all module augments */ + LY_CHECK_RET(lys_precompile_own_augments_mod(ctx, aug_mod->parsed)); + + /* collect all submodules augments */ + LY_ARRAY_FOR(aug_mod->parsed->includes, v) { + submod = (struct lysp_module *)aug_mod->parsed->includes[v].submodule; + + LY_CHECK_RET(lys_precompile_own_augments_mod(ctx, submod)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Prepare a deviation to be applied during data nodes compilation. + * + * @param[in] ctx Compile context. + * @param[in] dev_p Parsed deviation to be applied. + * @param[in] pmod Both current and prefix module for @p dev_p. + * @return LY_ERR value. + */ +static LY_ERR +lys_precompile_own_deviation(struct lysc_ctx *ctx, struct lysp_deviation *dev_p, const struct lysp_module *pmod) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_deviation *dev = NULL; + struct lyxp_expr *exp = NULL; + struct lysp_deviation **new_dev; + const struct lys_module *mod; + const struct lysp_module **new_dev_pmod; + uint32_t i; + + /* parse its target, it was already parsed and fully checked (except for the existence of the nodes) */ + ret = lyxp_expr_parse(ctx->ctx, dev_p->nodeid, strlen(dev_p->nodeid), 0, &exp); + LY_CHECK_GOTO(ret, cleanup); + + mod = lys_schema_node_get_module(ctx->ctx, exp->expr + exp->tok_pos[1], exp->tok_len[1], pmod, NULL, NULL); + LY_CHECK_ERR_GOTO(!mod, LOGINT(ctx->ctx); ret = LY_EINT, cleanup); + if (mod != ctx->cur_mod) { + /* deviation for another module, ignore */ + goto cleanup; + } + + /* try to find the node in already compiled deviations */ + for (i = 0; i < ctx->devs.count; ++i) { + if (lys_abs_schema_nodeid_match(ctx->ctx, exp, pmod, ((struct lysc_deviation *)ctx->devs.objs[i])->nodeid, + ((struct lysc_deviation *)ctx->devs.objs[i])->dev_pmods[0])) { + dev = ctx->devs.objs[i]; + break; + } + } + + if (!dev) { + /* allocate new compiled deviation */ + dev = calloc(1, sizeof *dev); + LY_CHECK_ERR_GOTO(!dev, LOGMEM(ctx->ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(&ctx->devs, dev, 1, NULL), cleanup); + + dev->nodeid = exp; + exp = NULL; + } + + /* add new parsed deviation structure */ + LY_ARRAY_NEW_GOTO(ctx->ctx, dev->devs, new_dev, ret, cleanup); + *new_dev = dev_p; + LY_ARRAY_NEW_GOTO(ctx->ctx, dev->dev_pmods, new_dev_pmod, ret, cleanup); + *new_dev_pmod = pmod; + +cleanup: + lyxp_expr_free(ctx->ctx, exp); + return ret; +} + +LY_ERR +lys_precompile_own_deviations(struct lysc_ctx *ctx) +{ + LY_ARRAY_COUNT_TYPE u, v, w; + struct lys_module *orig_cur_mod; + const struct lys_module *dev_mod; + struct lysc_deviation *dev; + struct lysp_deviate *d; + int not_supported; + uint32_t i; + + LY_ARRAY_FOR(ctx->cur_mod->deviated_by, u) { + dev_mod = ctx->cur_mod->deviated_by[u]; + + /* compile all module deviations */ + LY_ARRAY_FOR(dev_mod->parsed->deviations, v) { + LY_CHECK_RET(lys_precompile_own_deviation(ctx, &dev_mod->parsed->deviations[v], dev_mod->parsed)); + } + + /* compile all submodules deviations */ + LY_ARRAY_FOR(dev_mod->parsed->includes, v) { + LY_ARRAY_FOR(dev_mod->parsed->includes[v].submodule->deviations, w) { + LY_CHECK_RET(lys_precompile_own_deviation(ctx, &dev_mod->parsed->includes[v].submodule->deviations[w], + (struct lysp_module *)dev_mod->parsed->includes[v].submodule)); + } + } + } + + /* set not-supported flags for all the deviations */ + for (i = 0; i < ctx->devs.count; ++i) { + dev = ctx->devs.objs[i]; + not_supported = 0; + + LY_ARRAY_FOR(dev->devs, u) { + LY_LIST_FOR(dev->devs[u]->deviates, d) { + if (d->mod == LYS_DEV_NOT_SUPPORTED) { + not_supported = 1; + break; + } + } + if (not_supported) { + break; + } + } + if (not_supported && (LY_ARRAY_COUNT(dev->devs) > 1)) { + orig_cur_mod = ctx->cur_mod; + ctx->cur_mod = dev->dev_pmods[u]->mod; + lysc_update_path(ctx, NULL, "{deviation}"); + lysc_update_path(ctx, NULL, dev->nodeid->expr); + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Multiple deviations of \"%s\" with one of them being \"not-supported\".", dev->nodeid->expr); + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + ctx->cur_mod = orig_cur_mod; + return LY_EVALID; + } + + dev->not_supported = not_supported; + } + + return LY_SUCCESS; +} + +/** + * @brief Add a module reference into an array, checks for duplicities. + * + * @param[in] ctx Compile context. + * @param[in] mod Module reference to add. + * @param[in,out] mod_array Module sized array to add to. + * @return LY_ERR value. + */ +static LY_ERR +lys_array_add_mod_ref(struct lysc_ctx *ctx, struct lys_module *mod, struct lys_module ***mod_array) +{ + LY_ARRAY_COUNT_TYPE u; + struct lys_module **new_mod; + + LY_ARRAY_FOR(*mod_array, u) { + if ((*mod_array)[u] == mod) { + /* already there */ + return LY_EEXIST; + } + } + + /* add the new module ref */ + LY_ARRAY_NEW_RET(ctx->ctx, *mod_array, new_mod, LY_EMEM); + *new_mod = mod; + + return LY_SUCCESS; +} + +/** + * @brief Check whether all modules in a set are implemented. + * + * @param[in] mod_set Module set to check. + * @return Whether all modules are implemented or not. + */ +static ly_bool +lys_precompile_mod_set_is_all_implemented(const struct ly_set *mod_set) +{ + uint32_t i; + const struct lys_module *mod; + + for (i = 0; i < mod_set->count; ++i) { + mod = mod_set->objs[i]; + if (!mod->implemented) { + return 0; + } + } + + return 1; +} + +/** + * @brief Add references to target modules of top-level augments, deviations, and augments in extension instances + * in a module and all its submodules. + * + * @param[in] pmod Module to process. + * @param[in,out] mod_set Module set to add referenced modules into. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +static LY_ERR +lys_precompile_mod_augments_deviations(struct lysp_module *pmod, struct ly_set *mod_set) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + struct lysc_ctx ctx; + struct lys_module *m; + struct lysp_node_augment *aug; + struct ly_set set = {0}; + + LYSC_CTX_INIT_PMOD(ctx, pmod, NULL); + + LY_LIST_FOR(pmod->augments, aug) { + /* get target module */ + lysc_update_path(&ctx, NULL, "{augment}"); + lysc_update_path(&ctx, NULL, aug->nodeid); + ret = lys_nodeid_mod_check(&ctx, aug->nodeid, 1, &set, NULL, &m); + lysc_update_path(&ctx, NULL, NULL); + lysc_update_path(&ctx, NULL, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* add this module into the target module augmented_by, if not there and implemented */ + if ((lys_array_add_mod_ref(&ctx, pmod->mod, &m->augmented_by) != LY_EEXIST) || + !lys_precompile_mod_set_is_all_implemented(&set)) { + LY_CHECK_GOTO(ret = ly_set_merge(mod_set, &set, 0, NULL), cleanup); + } + ly_set_erase(&set, NULL); + } + + LY_ARRAY_FOR(pmod->deviations, u) { + /* get target module */ + lysc_update_path(&ctx, NULL, "{deviation}"); + lysc_update_path(&ctx, NULL, pmod->deviations[u].nodeid); + ret = lys_nodeid_mod_check(&ctx, pmod->deviations[u].nodeid, 1, &set, NULL, &m); + lysc_update_path(&ctx, NULL, NULL); + lysc_update_path(&ctx, NULL, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* add this module into the target module deviated_by, if not there and implemented */ + if ((lys_array_add_mod_ref(&ctx, pmod->mod, &m->deviated_by) != LY_EEXIST) || + !lys_precompile_mod_set_is_all_implemented(&set)) { + LY_CHECK_GOTO(ret = ly_set_merge(mod_set, &set, 0, NULL), cleanup); + } + ly_set_erase(&set, NULL); + } + + LY_ARRAY_FOR(pmod->exts, u) { + aug = NULL; + LY_ARRAY_FOR(pmod->exts[u].substmts, v) { + if (pmod->exts[u].substmts[v].stmt == LY_STMT_AUGMENT) { + aug = *(struct lysp_node_augment **)pmod->exts[u].substmts[v].storage; + break; + } + } + if (!aug) { + continue; + } + + /* get target module */ + lysc_update_path(&ctx, NULL, "{ext-augment}"); + lysc_update_path(&ctx, NULL, aug->nodeid); + ret = lys_nodeid_mod_check(&ctx, aug->nodeid, 1, &set, NULL, &m); + lysc_update_path(&ctx, NULL, NULL); + lysc_update_path(&ctx, NULL, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* add this module into the target module augmented_by, if not there and implemented */ + if ((lys_array_add_mod_ref(&ctx, pmod->mod, &m->augmented_by) != LY_EEXIST) || + !lys_precompile_mod_set_is_all_implemented(&set)) { + LY_CHECK_GOTO(ret = ly_set_merge(mod_set, &set, 0, NULL), cleanup); + } + ly_set_erase(&set, NULL); + } + +cleanup: + ly_set_erase(&set, NULL); + return ret; +} + +LY_ERR +lys_precompile_augments_deviations(struct lys_module *mod, struct lys_glob_unres *unres) +{ + LY_ERR ret = LY_SUCCESS, r; + LY_ARRAY_COUNT_TYPE u; + struct lys_module *m; + struct lysp_module *submod; + const char **imp_f, *all_f[] = {"*", NULL}; + uint32_t i; + struct ly_set mod_set = {0}; + + /* module */ + LY_CHECK_GOTO(ret = lys_precompile_mod_augments_deviations(mod->parsed, &mod_set), cleanup); + + /* submodules */ + LY_ARRAY_FOR(mod->parsed->includes, u) { + submod = (struct lysp_module *)mod->parsed->includes[u].submodule; + LY_CHECK_GOTO(ret = lys_precompile_mod_augments_deviations(submod, &mod_set), cleanup); + } + + for (i = 0; i < mod_set.count; ++i) { + m = mod_set.objs[i]; + + if (m == mod) { + /* will be applied normally later */ + continue; + } + + /* we do not actually need the target modules compiled with out amends, they just need to be implemented + * not compiled yet and marked for compilation */ + + if (!m->implemented) { + /* implement the target module */ + imp_f = (mod->ctx->flags & LY_CTX_ENABLE_IMP_FEATURES) ? all_f : NULL; + r = lys_implement(m, imp_f, unres); + if (r == LY_ERECOMPILE) { + /* implement all the modules right away to save possible later recompilation */ + ret = r; + continue; + } else if (r) { + /* error */ + ret = r; + goto cleanup; + } + } else if (m->compiled) { + /* target module was already compiled without our amends (augment/deviation), we need to recompile it */ + m->to_compile = 1; + ret = LY_ERECOMPILE; + continue; + } + } + +cleanup: + ly_set_erase(&mod_set, NULL); + return ret; +} + +void +lys_precompile_augments_deviations_revert(struct ly_ctx *ctx, const struct lys_module *mod) +{ + uint32_t i; + LY_ARRAY_COUNT_TYPE u, count; + struct lys_module *m; + + for (i = 0; i < ctx->list.count; ++i) { + m = ctx->list.objs[i]; + + if (m->augmented_by) { + count = LY_ARRAY_COUNT(m->augmented_by); + for (u = 0; u < count; ++u) { + if (m->augmented_by[u] == mod) { + /* keep the order */ + if (u < count - 1) { + memmove(m->augmented_by + u, m->augmented_by + u + 1, (count - u - 1) * sizeof *m->augmented_by); + } + LY_ARRAY_DECREMENT(m->augmented_by); + break; + } + } + if (!LY_ARRAY_COUNT(m->augmented_by)) { + LY_ARRAY_FREE(m->augmented_by); + m->augmented_by = NULL; + } + } + + if (m->deviated_by) { + count = LY_ARRAY_COUNT(m->deviated_by); + for (u = 0; u < count; ++u) { + if (m->deviated_by[u] == mod) { + /* keep the order */ + if (u < count - 1) { + memmove(m->deviated_by + u, m->deviated_by + u + 1, (count - u - 1) * sizeof *m->deviated_by); + } + LY_ARRAY_DECREMENT(m->deviated_by); + break; + } + } + if (!LY_ARRAY_COUNT(m->deviated_by)) { + LY_ARRAY_FREE(m->deviated_by); + m->deviated_by = NULL; + } + } + } +} diff --git a/src/schema_compile_amend.h b/src/schema_compile_amend.h new file mode 100644 index 0000000..344af65 --- /dev/null +++ b/src/schema_compile_amend.h @@ -0,0 +1,181 @@ +/** + * @file schema_compile_amend.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Header for schema compilation of augments, deviations, and refines. + * + * Copyright (c) 2015 - 2022 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_SCHEMA_COMPILE_AMEND_H_ +#define LY_SCHEMA_COMPILE_AMEND_H_ + +#include "log.h" + +struct ly_ctx; +struct lysp_qname; +struct lysp_node; +struct lysc_node; +struct lysc_ctx; +struct lysp_node_uses; +struct lys_glob_unres; +struct lys_module; + +/** + * @brief Compiled parsed augment structure. Just a temporary storage for applying the augment to data. + */ +struct lysc_augment { + struct lyxp_expr *nodeid; /**< augment target */ + const struct lysp_module *aug_pmod; /**< module where the augment is defined, for top-level augments + used to resolve prefixes, for uses augments used as the context pmod */ + const struct lysc_node *nodeid_ctx_node; /**< nodeid context node for relative targets */ + const struct lysp_ext_instance *ext; /**< parent extension instance, in case the augment is from one */ + + struct lysp_node_augment *aug_p; /**< pointer to the parsed augment to apply */ +}; + +/** + * @brief Compiled parsed deviation structure. Just a temporary storage for applying the deviation to data. + */ +struct lysc_deviation { + struct lyxp_expr *nodeid; /**< deviation target, taken from the first deviation in + ::lysc_deviation.dev_pmods array, this module is used for resolving + prefixes used in the nodeid. */ + + struct lysp_deviation **devs; /**< sized array of all the parsed deviations for one target node */ + const struct lysp_module **dev_pmods; /**< sized array of parsed modules of @p devs (where the specific deviations + are defined). */ + ly_bool not_supported; /**< whether this is a not-supported deviation */ +}; + +/** + * @brief Compiled parsed refine structure. Just a temporary storage for applying the refine to data. + */ +struct lysc_refine { + struct lyxp_expr *nodeid; /**< refine target */ + const struct lysp_module *nodeid_pmod; /**< module where the nodeid is defined, used to resolve prefixes */ + const struct lysc_node *nodeid_ctx_node; /**< nodeid context node */ + struct lysp_node_uses *uses_p; /**< parsed uses node of the refine, for tracking recursive refines */ + + struct lysp_refine **rfns; /**< sized array of parsed refines to apply */ +}; + +/** + * @brief Prepare any uses augments and refines in the context to be applied during uses descendant node compilation. + * + * @param[in] ctx Compile context. + * @param[in] uses_p Parsed uses structure with augments and refines. + * @param[in] ctx_node Context node of @p uses_p meaning its first data definition parent. + * @return LY_ERR value. + */ +LY_ERR lys_precompile_uses_augments_refines(struct lysc_ctx *ctx, struct lysp_node_uses *uses_p, + const struct lysc_node *ctx_node); + +/** + * @brief Duplicate qname structure. + * + * @param[in] ctx libyang context. + * @param[in] orig_qname Structure to read from. + * @param[in,out] qname Structure to fill. + * @return LY_ERR value. + */ +LY_ERR lysp_qname_dup(const struct ly_ctx *ctx, const struct lysp_qname *orig_qname, struct lysp_qname *qname); + +/** + * @brief Free a compiled augment temporary structure. + * + * @param[in] ctx libyang context. + * @param[in] aug Structure to free. + */ +void lysc_augment_free(const struct ly_ctx *ctx, struct lysc_augment *aug); + +/** + * @brief Free a compiled deviation temporary structure. + * + * @param[in] ctx libyang context. + * @param[in] dev Structure to free. + */ +void lysc_deviation_free(const struct ly_ctx *ctx, struct lysc_deviation *dev); + +/** + * @brief Free a compiled refine temporary structure. + * + * @param[in] ctx libyang context. + * @param[in] rfn Structure to free. + */ +void lysc_refine_free(const struct ly_ctx *ctx, struct lysc_refine *rfn); + +/** + * @brief Free a duplicate of parsed node. It is returned by ::lys_compile_node_deviations_refines(). + * + * @param[in] ctx libyang context. + * @param[in] dev_pnode Parsed node to free. + */ +void lysp_dev_node_free(struct lysc_ctx *cctx, struct lysp_node *dev_pnode); + +/** + * @brief Compile and apply any precompiled deviations and refines targeting a node. + * + * @param[in] ctx Compile context. + * @param[in] pnode Parsed node to consider. + * @param[in] parent First compiled parent of @p pnode. + * @param[out] dev_pnode Copy of parsed node @p pnode with deviations and refines, if any. NULL if there are none. + * @param[out] not_supported Whether a not-supported deviation is defined for the node. + * @return LY_ERR value. + */ +LY_ERR lys_compile_node_deviations_refines(struct lysc_ctx *ctx, const struct lysp_node *pnode, + const struct lysc_node *parent, struct lysp_node **dev_pnode, ly_bool *not_supported); + +/** + * @brief Compile and apply any precompiled top-level or uses augments targeting a node. + * + * @param[in] ctx Compile context. + * @param[in] node Compiled node to consider. + * @return LY_ERR value. + */ +LY_ERR lys_compile_node_augments(struct lysc_ctx *ctx, struct lysc_node *node); + +/** + * @brief Prepare all top-level augments for the current module to be applied during data nodes compilation. + * + * @param[in] ctx Compile context. + * @return LY_ERR value. + */ +LY_ERR lys_precompile_own_augments(struct lysc_ctx *ctx); + +/** + * @brief Prepare all deviations for the current module to be applied during data nodes compilation. + * + * @param[in] ctx Compile context. + * @return LY_ERR value. + */ +LY_ERR lys_precompile_own_deviations(struct lysc_ctx *ctx); + +/** + * @brief Add references to target modules of top-level augments and deviations in a module and all its submodules. + * Adds the module reference to the target modules and if not implemented, implement them (but not compile). + * + * @param[in] mod Module to process. + * @param[in,out] unres Global unres to use. + * @return LY_SUCCESS on success. + * @return LY_ERECOMPILE on required recompilation of the dep set. + * @return LY_ERR on error. + */ +LY_ERR lys_precompile_augments_deviations(struct lys_module *mod, struct lys_glob_unres *unres); + +/** + * @brief Revert precompilation of module augments and deviations. Meaning remove its reference from + * all the target modules. + * + * @param[in] ctx libyang context. + * @param[in] mod Mod whose precompilation to revert. + */ +void lys_precompile_augments_deviations_revert(struct ly_ctx *ctx, const struct lys_module *mod); + +#endif /* LY_SCHEMA_COMPILE_AMEND_H_ */ diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c new file mode 100644 index 0000000..0b64dcb --- /dev/null +++ b/src/schema_compile_node.c @@ -0,0 +1,4218 @@ +/** + * @file schema_compile_node.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Schema compilation of common nodes. + * + * Copyright (c) 2015 - 2022 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 /* asprintf, strdup */ + +#include "schema_compile_node.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "dict.h" +#include "log.h" +#include "plugins.h" +#include "plugins_internal.h" +#include "plugins_types.h" +#include "schema_compile.h" +#include "schema_compile_amend.h" +#include "schema_features.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +static struct lysc_ext_instance * +lysc_ext_instance_dup(struct ly_ctx *ctx, struct lysc_ext_instance *orig) +{ + /* TODO - extensions, increase refcount */ + (void) ctx; + (void) orig; + return NULL; +} + +/** + * @brief Add a node with a when to unres. + * + * @param[in] ctx Compile context. + * @param[in] when Specific compiled when to check. + * @param[in] node Compiled node with when(s). + * @return LY_ERR value. + */ +static LY_ERR +lysc_unres_when_add(struct lysc_ctx *ctx, struct lysc_when *when, struct lysc_node *node) +{ + LY_ERR rc = LY_SUCCESS; + struct lysc_unres_when *w = NULL; + + /* do not check must(s) in a grouping */ + if (ctx->compile_opts & LYS_COMPILE_GROUPING) { + goto cleanup; + } + + /* add new unres when */ + w = calloc(1, sizeof *w); + LY_CHECK_ERR_GOTO(!w, LOGMEM(ctx->ctx); rc = LY_EMEM, cleanup); + + w->node = node; + w->when = when; + + /* add into the unres set */ + LY_CHECK_ERR_GOTO(ly_set_add(&ctx->unres->whens, w, 1, NULL), LOGMEM(ctx->ctx); rc = LY_EMEM, cleanup); + w = NULL; + +cleanup: + free(w); + return rc; +} + +/** + * @brief Add a node with must(s) to unres. + * + * @param[in] ctx Compile context. + * @param[in] node Compiled node with must(s). + * @param[in] pnode Parsed ndoe with must(s). + * @return LY_ERR value. + */ +static LY_ERR +lysc_unres_must_add(struct lysc_ctx *ctx, struct lysc_node *node, struct lysp_node *pnode) +{ + struct lysc_unres_must *m = NULL; + LY_ARRAY_COUNT_TYPE u; + struct lysc_must *musts; + struct lysp_restr *pmusts; + LY_ERR ret; + + /* do not check must(s) in a grouping */ + if (ctx->compile_opts & LYS_COMPILE_GROUPING) { + return LY_SUCCESS; + } + + musts = lysc_node_musts(node); + pmusts = lysp_node_musts(pnode); + assert(LY_ARRAY_COUNT(musts) == LY_ARRAY_COUNT(pmusts)); + + if (!musts) { + /* no must */ + return LY_SUCCESS; + } + + /* add new unres must */ + m = calloc(1, sizeof *m); + LY_CHECK_ERR_GOTO(!m, ret = LY_EMEM, error); + m->node = node; + + /* add must local modules */ + LY_ARRAY_CREATE_GOTO(ctx->ctx, m->local_mods, LY_ARRAY_COUNT(pmusts), ret, error); + LY_ARRAY_FOR(pmusts, u) { + m->local_mods[u] = pmusts[u].arg.mod; + LY_ARRAY_INCREMENT(m->local_mods); + } + + /* store ext */ + m->ext = ctx->ext; + + LY_CHECK_ERR_GOTO(ly_set_add(&ctx->unres->musts, m, 1, NULL), ret = LY_EMEM, error); + + return LY_SUCCESS; + +error: + if (m) { + LY_ARRAY_FREE(m->local_mods); + free(m); + } + LOGMEM(ctx->ctx); + return ret; +} + +static LY_ERR +lysc_unres_leafref_add(struct lysc_ctx *ctx, struct lysc_node_leaf *leaf, const struct lysp_module *local_mod) +{ + struct lysc_unres_leafref *l = NULL; + struct ly_set *leafrefs_set; + LY_ARRAY_COUNT_TYPE u; + int is_lref = 0; + + if (ctx->compile_opts & LYS_COMPILE_GROUPING) { + /* do not check leafrefs in groupings */ + return LY_SUCCESS; + } + + /* use special set for disabled leafrefs */ + leafrefs_set = ctx->compile_opts & LYS_COMPILE_DISABLED ? &ctx->unres->disabled_leafrefs : &ctx->unres->leafrefs; + + if (leaf->type->basetype == LY_TYPE_LEAFREF) { + /* leafref */ + is_lref = 1; + } else if (leaf->type->basetype == LY_TYPE_UNION) { + /* union with leafrefs */ + LY_ARRAY_FOR(((struct lysc_type_union *)leaf->type)->types, u) { + if (((struct lysc_type_union *)leaf->type)->types[u]->basetype == LY_TYPE_LEAFREF) { + is_lref = 1; + break; + } + } + } + + if (is_lref) { + /* add new unresolved leafref node */ + l = calloc(1, sizeof *l); + LY_CHECK_ERR_RET(!l, LOGMEM(ctx->ctx), LY_EMEM); + + l->node = &leaf->node; + l->local_mod = local_mod; + l->ext = ctx->ext; + + LY_CHECK_ERR_RET(ly_set_add(leafrefs_set, l, 1, NULL), free(l); LOGMEM(ctx->ctx), LY_EMEM); + } + + return LY_SUCCESS; +} + +/** + * @brief Add/replace a leaf default value in unres. + * Can also be used for a single leaf-list default value. + * + * @param[in] ctx Compile context. + * @param[in] leaf Leaf with the default value. + * @param[in] dflt Default value to use. + * @return LY_ERR value. + */ +static LY_ERR +lysc_unres_leaf_dflt_add(struct lysc_ctx *ctx, struct lysc_node_leaf *leaf, struct lysp_qname *dflt) +{ + struct lysc_unres_dflt *r = NULL; + uint32_t i; + + if (ctx->compile_opts & (LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING)) { + return LY_SUCCESS; + } + + for (i = 0; i < ctx->unres->dflts.count; ++i) { + if (((struct lysc_unres_dflt *)ctx->unres->dflts.objs[i])->leaf == leaf) { + /* just replace the default */ + r = ctx->unres->dflts.objs[i]; + lysp_qname_free(ctx->ctx, r->dflt); + free(r->dflt); + break; + } + } + if (!r) { + /* add new unres item */ + r = calloc(1, sizeof *r); + LY_CHECK_ERR_RET(!r, LOGMEM(ctx->ctx), LY_EMEM); + r->leaf = leaf; + + LY_CHECK_RET(ly_set_add(&ctx->unres->dflts, r, 1, NULL)); + } + + r->dflt = malloc(sizeof *r->dflt); + LY_CHECK_GOTO(!r->dflt, error); + LY_CHECK_GOTO(lysp_qname_dup(ctx->ctx, dflt, r->dflt), error); + + return LY_SUCCESS; + +error: + free(r->dflt); + LOGMEM(ctx->ctx); + return LY_EMEM; +} + +/** + * @brief Add/replace a leaf-list default value(s) in unres. + * + * @param[in] ctx Compile context. + * @param[in] llist Leaf-list with the default value. + * @param[in] dflts Sized array of the default values. + * @return LY_ERR value. + */ +static LY_ERR +lysc_unres_llist_dflts_add(struct lysc_ctx *ctx, struct lysc_node_leaflist *llist, struct lysp_qname *dflts) +{ + struct lysc_unres_dflt *r = NULL; + uint32_t i; + + if (ctx->compile_opts & (LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING)) { + return LY_SUCCESS; + } + + for (i = 0; i < ctx->unres->dflts.count; ++i) { + if (((struct lysc_unres_dflt *)ctx->unres->dflts.objs[i])->llist == llist) { + /* just replace the defaults */ + r = ctx->unres->dflts.objs[i]; + lysp_qname_free(ctx->ctx, r->dflt); + free(r->dflt); + r->dflt = NULL; + FREE_ARRAY(ctx->ctx, r->dflts, lysp_qname_free); + r->dflts = NULL; + break; + } + } + if (!r) { + r = calloc(1, sizeof *r); + LY_CHECK_ERR_RET(!r, LOGMEM(ctx->ctx), LY_EMEM); + r->llist = llist; + + LY_CHECK_RET(ly_set_add(&ctx->unres->dflts, r, 1, NULL)); + } + + DUP_ARRAY(ctx->ctx, dflts, r->dflts, lysp_qname_dup); + + return LY_SUCCESS; +} + +/** + * @brief Add a bits/enumeration type to unres. + * + * @param[in] ctx Compile context. + * @param[in] leaf Leaf of type bits/enumeration whose disabled items to free. + * @return LY_ERR value. + */ +static LY_ERR +lysc_unres_bitenum_add(struct lysc_ctx *ctx, struct lysc_node_leaf *leaf) +{ + if (ctx->compile_opts & (LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING)) { + /* skip groupings and redundant for disabled nodes */ + return LY_SUCCESS; + } + + LY_CHECK_RET(ly_set_add(&ctx->unres->disabled_bitenums, leaf, 1, NULL)); + + return LY_SUCCESS; +} + +/** + * @brief Duplicate the compiled pattern structure. + * + * Instead of duplicating memory, the reference counter in the @p orig is increased. + * + * @param[in] orig The pattern structure to duplicate. + * @return The duplicated structure to use. + */ +static struct lysc_pattern * +lysc_pattern_dup(struct lysc_pattern *orig) +{ + ++orig->refcount; + return orig; +} + +/** + * @brief Duplicate the array of compiled patterns. + * + * The sized array itself is duplicated, but the pattern structures are just shadowed by increasing their reference counter. + * + * @param[in] ctx Libyang context for logging. + * @param[in] orig The patterns sized array to duplicate. + * @return New sized array as a copy of @p orig. + * @return NULL in case of memory allocation error. + */ +static struct lysc_pattern ** +lysc_patterns_dup(struct ly_ctx *ctx, struct lysc_pattern **orig) +{ + struct lysc_pattern **dup = NULL; + LY_ARRAY_COUNT_TYPE u; + + assert(orig); + + LY_ARRAY_CREATE_RET(ctx, dup, LY_ARRAY_COUNT(orig), NULL); + LY_ARRAY_FOR(orig, u) { + dup[u] = lysc_pattern_dup(orig[u]); + LY_ARRAY_INCREMENT(dup); + } + return dup; +} + +/** + * @brief Duplicate compiled range structure. + * + * @param[in] ctx Libyang context for logging. + * @param[in] orig The range structure to be duplicated. + * @return New compiled range structure as a copy of @p orig. + * @return NULL in case of memory allocation error. + */ +static struct lysc_range * +lysc_range_dup(struct ly_ctx *ctx, const struct lysc_range *orig) +{ + struct lysc_range *dup; + LY_ERR ret; + + assert(orig); + + dup = calloc(1, sizeof *dup); + LY_CHECK_ERR_RET(!dup, LOGMEM(ctx), NULL); + if (orig->parts) { + LY_ARRAY_CREATE_GOTO(ctx, dup->parts, LY_ARRAY_COUNT(orig->parts), ret, cleanup); + (*((LY_ARRAY_COUNT_TYPE *)(dup->parts) - 1)) = LY_ARRAY_COUNT(orig->parts); + memcpy(dup->parts, orig->parts, LY_ARRAY_COUNT(dup->parts) * sizeof *dup->parts); + } + DUP_STRING_GOTO(ctx, orig->eapptag, dup->eapptag, ret, cleanup); + DUP_STRING_GOTO(ctx, orig->emsg, dup->emsg, ret, cleanup); + dup->exts = lysc_ext_instance_dup(ctx, orig->exts); + + return dup; +cleanup: + free(dup); + (void) ret; /* set but not used due to the return type */ + return NULL; +} + +/** + * @brief Print status into a string. + * + * @param[in] flags Flags with the status to print. + * @return String status. + */ +static const char * +lys_status2str(uint16_t flags) +{ + flags &= LYS_STATUS_MASK; + + switch (flags) { + case 0: + case LYS_STATUS_CURR: + return "current"; + case LYS_STATUS_DEPRC: + return "deprecated"; + case LYS_STATUS_OBSLT: + return "obsolete"; + default: + LOGINT(NULL); + return NULL; + } +} + +/** + * @brief Compile status information of the given statement. + * + * To simplify getting status of the node, the flags are set following inheritance rules, so all the nodes + * has the status correctly set during the compilation. + * + * @param[in] ctx Compile context + * @param[in] parsed_flags Parsed statement flags. + * @param[in] inherited_flags Parsed inherited flags from a schema-only statement (augment, uses, ext instance, ...). + * @param[in] parent_flags Compiled parent node flags. + * @param[in] parent_name Name of the parent node, for logging. + * @param[in] stmt_name Statement name, for logging. + * @param[in,out] stmt_flags Statement flags with the correct status set. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_status(struct lysc_ctx *ctx, uint16_t parsed_flags, uint16_t inherited_flags, uint16_t parent_flags, + const char *parent_name, const char *stmt_name, uint16_t *stmt_flags) +{ + /* normalize to status-only */ + parsed_flags &= LYS_STATUS_MASK; + inherited_flags &= LYS_STATUS_MASK; + parent_flags &= LYS_STATUS_MASK; + + /* check for conflicts */ + if (parent_flags && parsed_flags && (parent_flags > parsed_flags)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Status \"%s\" of \"%s\" is in conflict with \"%s\" status of parent \"%s\".", + lys_status2str(parsed_flags), stmt_name, lys_status2str(parent_flags), parent_name); + return LY_EVALID; + } else if (inherited_flags && parsed_flags && (inherited_flags > parsed_flags)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Inherited schema-only status \"%s\" is in conflict with \"%s\" status of \"%s\".", + lys_status2str(inherited_flags), lys_status2str(parsed_flags), stmt_name); + return LY_EVALID; + } else if (parent_flags && inherited_flags && (parent_flags > inherited_flags)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Status \"%s\" of parent \"%s\" is in conflict with inherited schema-only status \"%s\".", + lys_status2str(parent_flags), parent_name, lys_status2str(inherited_flags)); + return LY_EVALID; + } + + /* clear */ + (*stmt_flags) &= ~LYS_STATUS_MASK; + + if (parsed_flags) { + /* explicit status */ + (*stmt_flags) |= parsed_flags; + } else if (inherited_flags) { + /* inherited status from a schema-only statement */ + (*stmt_flags) |= inherited_flags; + } else if (parent_flags) { + /* inherited status from a parent node */ + (*stmt_flags) |= parent_flags; + } else { + /* default status */ + (*stmt_flags) |= LYS_STATUS_CURR; + } + + return LY_SUCCESS; +} + +/** + * @brief Compile information from the when statement + * + * @param[in] ctx Compile context. + * @param[in] when_p Parsed when structure. + * @param[in] inherited_flags Inherited flags from a schema-only statement. + * @param[in] parent Parent node, if any. + * @param[in] ctx_node Context node for the when statement. + * @param[out] when Pointer where to store pointer to the created compiled when structure. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_when_(struct lysc_ctx *ctx, const struct lysp_when *when_p, uint16_t inherited_flags, + const struct lysc_node *parent, const struct lysc_node *ctx_node, struct lysc_when **when) +{ + LY_ERR ret = LY_SUCCESS; + LY_VALUE_FORMAT format; + + *when = calloc(1, sizeof **when); + LY_CHECK_ERR_RET(!(*when), LOGMEM(ctx->ctx), LY_EMEM); + (*when)->refcount = 1; + LY_CHECK_RET(lyxp_expr_parse(ctx->ctx, when_p->cond, 0, 1, &(*when)->cond)); + LY_CHECK_RET(lyplg_type_prefix_data_new(ctx->ctx, when_p->cond, strlen(when_p->cond), + LY_VALUE_SCHEMA, ctx->pmod, &format, (void **)&(*when)->prefixes)); + (*when)->context = (struct lysc_node *)ctx_node; + DUP_STRING_GOTO(ctx->ctx, when_p->dsc, (*when)->dsc, ret, done); + DUP_STRING_GOTO(ctx->ctx, when_p->ref, (*when)->ref, ret, done); + COMPILE_EXTS_GOTO(ctx, when_p->exts, (*when)->exts, (*when), ret, done); + LY_CHECK_RET(lys_compile_status(ctx, 0, inherited_flags, parent ? parent->flags : 0, parent ? parent->name : NULL, + "when", &(*when)->flags)); + +done: + return ret; +} + +LY_ERR +lys_compile_when(struct lysc_ctx *ctx, const struct lysp_when *when_p, uint16_t inherited_flags, const struct lysc_node *parent, + const struct lysc_node *ctx_node, struct lysc_node *node, struct lysc_when **when_c) +{ + LY_ERR rc = LY_SUCCESS; + struct lysc_when **new_when, ***node_when, *ptr; + + assert(when_p && (node || when_c)); + + if (node) { + /* get the when array */ + node_when = lysc_node_when_p(node); + + /* create new when pointer */ + LY_ARRAY_NEW_GOTO(ctx->ctx, *node_when, new_when, rc, cleanup); + } else { + /* individual when */ + new_when = &ptr; + *new_when = calloc(1, sizeof **new_when); + LY_CHECK_ERR_GOTO(!*new_when, LOGMEM(ctx->ctx); rc = LY_EMEM, cleanup); + } + + if (!when_c || !(*when_c)) { + /* compile when */ + LY_CHECK_GOTO(rc = lys_compile_when_(ctx, when_p, inherited_flags, parent, ctx_node, new_when), cleanup); + + /* remember the compiled when for sharing */ + if (when_c) { + *when_c = *new_when; + } + } else { + /* use the previously compiled when */ + ++(*when_c)->refcount; + *new_when = *when_c; + } + + /* add when to unres */ + LY_CHECK_GOTO(rc = lysc_unres_when_add(ctx, *new_when, node), cleanup); + +cleanup: + return rc; +} + +LY_ERR +lys_compile_must(struct lysc_ctx *ctx, const struct lysp_restr *must_p, struct lysc_must *must) +{ + LY_ERR ret = LY_SUCCESS; + LY_VALUE_FORMAT format; + + LY_CHECK_RET(lyxp_expr_parse(ctx->ctx, must_p->arg.str, 0, 1, &must->cond)); + LY_CHECK_RET(lyplg_type_prefix_data_new(ctx->ctx, must_p->arg.str, strlen(must_p->arg.str), + LY_VALUE_SCHEMA, must_p->arg.mod, &format, (void **)&must->prefixes)); + DUP_STRING_GOTO(ctx->ctx, must_p->eapptag, must->eapptag, ret, done); + DUP_STRING_GOTO(ctx->ctx, must_p->emsg, must->emsg, ret, done); + DUP_STRING_GOTO(ctx->ctx, must_p->dsc, must->dsc, ret, done); + DUP_STRING_GOTO(ctx->ctx, must_p->ref, must->ref, ret, done); + COMPILE_EXTS_GOTO(ctx, must_p->exts, must->exts, must, ret, done); + +done: + return ret; +} + +/** + * @brief Validate and normalize numeric value from a range definition. + * @param[in] ctx Compile context. + * @param[in] basetype Base YANG built-in type of the node connected with the range restriction. Actually only LY_TYPE_DEC64 is important to + * allow processing of the fractions. The fraction point is extracted from the value which is then normalize according to given frdigits into + * valcopy to allow easy parsing and storing of the value. libyang stores decimal number without the decimal point which is always recovered from + * the known fraction-digits value. So, with fraction-digits 2, number 3.14 is stored as 314 and number 1 is stored as 100. + * @param[in] frdigits The fraction-digits of the type in case of LY_TYPE_DEC64. + * @param[in] value String value of the range boundary. + * @param[out] len Number of the processed bytes from the value. Processing stops on the first character which is not part of the number boundary. + * @param[out] valcopy NULL-terminated string with the numeric value to parse and store. + * @return LY_ERR value - LY_SUCCESS, LY_EMEM, LY_EVALID (no number) or LY_EINVAL (decimal64 not matching fraction-digits value). + */ +static LY_ERR +range_part_check_value_syntax(struct lysc_ctx *ctx, LY_DATA_TYPE basetype, uint8_t frdigits, const char *value, + size_t *len, char **valcopy) +{ + size_t fraction = 0, size; + + *len = 0; + + assert(value); + /* parse value */ + if (!isdigit(value[*len]) && (value[*len] != '-') && (value[*len] != '+')) { + return LY_EVALID; + } + + if ((value[*len] == '-') || (value[*len] == '+')) { + ++(*len); + } + + while (isdigit(value[*len])) { + ++(*len); + } + + if ((basetype != LY_TYPE_DEC64) || (value[*len] != '.') || !isdigit(value[*len + 1])) { + if (basetype == LY_TYPE_DEC64) { + goto decimal; + } else { + *valcopy = strndup(value, *len); + return LY_SUCCESS; + } + } + fraction = *len; + + ++(*len); + while (isdigit(value[*len])) { + ++(*len); + } + + if (basetype == LY_TYPE_DEC64) { +decimal: + assert(frdigits); + if (fraction && (*len - 1 - fraction > frdigits)) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Range boundary \"%.*s\" of decimal64 type exceeds defined number (%u) of fraction digits.", + (int)(*len), value, frdigits); + return LY_EINVAL; + } + if (fraction) { + size = (*len) + (frdigits - ((*len) - 1 - fraction)); + } else { + size = (*len) + frdigits + 1; + } + *valcopy = malloc(size * sizeof **valcopy); + LY_CHECK_ERR_RET(!(*valcopy), LOGMEM(ctx->ctx), LY_EMEM); + + (*valcopy)[size - 1] = '\0'; + if (fraction) { + memcpy(&(*valcopy)[0], &value[0], fraction); + memcpy(&(*valcopy)[fraction], &value[fraction + 1], (*len) - 1 - (fraction)); + memset(&(*valcopy)[(*len) - 1], '0', frdigits - ((*len) - 1 - fraction)); + } else { + memcpy(&(*valcopy)[0], &value[0], *len); + memset(&(*valcopy)[*len], '0', frdigits); + } + } + return LY_SUCCESS; +} + +/** + * @brief Check that values in range are in ascendant order. + * @param[in] unsigned_value Flag to note that we are working with unsigned values. + * @param[in] max Flag to distinguish if checking min or max value. min value must be strictly higher than previous, + * max can be also equal. + * @param[in] value Current value to check. + * @param[in] prev_value The last seen value. + * @return LY_SUCCESS or LY_EEXIST for invalid order. + */ +static LY_ERR +range_part_check_ascendancy(ly_bool unsigned_value, ly_bool max, int64_t value, int64_t prev_value) +{ + if (unsigned_value) { + if ((max && ((uint64_t)prev_value > (uint64_t)value)) || (!max && ((uint64_t)prev_value >= (uint64_t)value))) { + return LY_EEXIST; + } + } else { + if ((max && (prev_value > value)) || (!max && (prev_value >= value))) { + return LY_EEXIST; + } + } + return LY_SUCCESS; +} + +/** + * @brief Set min/max value of the range part. + * @param[in] ctx Compile context. + * @param[in] part Range part structure to fill. + * @param[in] max Flag to distinguish if storing min or max value. + * @param[in] prev The last seen value to check that all values in range are specified in ascendant order. + * @param[in] basetype Type of the value to get know implicit min/max values and other checking rules. + * @param[in] first Flag for the first value of the range to avoid ascendancy order. + * @param[in] length_restr Flag to distinguish between range and length restrictions. Only for logging. + * @param[in] frdigits The fraction-digits value in case of LY_TYPE_DEC64 basetype. + * @param[in] base_range Range from the type from which the current type is derived (if not built-in) to get type's min and max values. + * @param[in,out] value Numeric range value to be stored, if not provided the type's min/max value is set. + * @return LY_ERR value - LY_SUCCESS, LY_EDENIED (value brokes type's boundaries), LY_EVALID (not a number), + * LY_EEXIST (value is smaller than the previous one), LY_EINVAL (decimal64 value does not corresponds with the + * frdigits value), LY_EMEM. + */ +static LY_ERR +range_part_minmax(struct lysc_ctx *ctx, struct lysc_range_part *part, ly_bool max, int64_t prev, LY_DATA_TYPE basetype, + ly_bool first, ly_bool length_restr, uint8_t frdigits, struct lysc_range *base_range, const char **value) +{ + LY_ERR ret = LY_SUCCESS; + char *valcopy = NULL; + size_t len = 0; + + if (value) { + ret = range_part_check_value_syntax(ctx, basetype, frdigits, *value, &len, &valcopy); + LY_CHECK_GOTO(ret, finalize); + } + if (!valcopy && base_range) { + if (max) { + part->max_64 = base_range->parts[LY_ARRAY_COUNT(base_range->parts) - 1].max_64; + } else { + part->min_64 = base_range->parts[0].min_64; + } + if (!first) { + ret = range_part_check_ascendancy(basetype <= LY_TYPE_STRING ? 1 : 0, max, max ? part->max_64 : part->min_64, prev); + } + goto finalize; + } + + switch (basetype) { + case LY_TYPE_INT8: /* range */ + if (valcopy) { + ret = ly_parse_int(valcopy, strlen(valcopy), INT64_C(-128), INT64_C(127), LY_BASE_DEC, max ? &part->max_64 : &part->min_64); + } else if (max) { + part->max_64 = INT64_C(127); + } else { + part->min_64 = INT64_C(-128); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(0, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_INT16: /* range */ + if (valcopy) { + ret = ly_parse_int(valcopy, strlen(valcopy), INT64_C(-32768), INT64_C(32767), LY_BASE_DEC, + max ? &part->max_64 : &part->min_64); + } else if (max) { + part->max_64 = INT64_C(32767); + } else { + part->min_64 = INT64_C(-32768); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(0, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_INT32: /* range */ + if (valcopy) { + ret = ly_parse_int(valcopy, strlen(valcopy), INT64_C(-2147483648), INT64_C(2147483647), LY_BASE_DEC, + max ? &part->max_64 : &part->min_64); + } else if (max) { + part->max_64 = INT64_C(2147483647); + } else { + part->min_64 = INT64_C(-2147483648); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(0, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_INT64: /* range */ + case LY_TYPE_DEC64: /* range */ + if (valcopy) { + ret = ly_parse_int(valcopy, strlen(valcopy), INT64_C(-9223372036854775807) - INT64_C(1), INT64_C(9223372036854775807), + LY_BASE_DEC, max ? &part->max_64 : &part->min_64); + } else if (max) { + part->max_64 = INT64_C(9223372036854775807); + } else { + part->min_64 = INT64_C(-9223372036854775807) - INT64_C(1); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(0, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_UINT8: /* range */ + if (valcopy) { + ret = ly_parse_uint(valcopy, strlen(valcopy), UINT64_C(255), LY_BASE_DEC, max ? &part->max_u64 : &part->min_u64); + } else if (max) { + part->max_u64 = UINT64_C(255); + } else { + part->min_u64 = UINT64_C(0); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(1, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_UINT16: /* range */ + if (valcopy) { + ret = ly_parse_uint(valcopy, strlen(valcopy), UINT64_C(65535), LY_BASE_DEC, max ? &part->max_u64 : &part->min_u64); + } else if (max) { + part->max_u64 = UINT64_C(65535); + } else { + part->min_u64 = UINT64_C(0); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(1, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_UINT32: /* range */ + if (valcopy) { + ret = ly_parse_uint(valcopy, strlen(valcopy), UINT64_C(4294967295), LY_BASE_DEC, + max ? &part->max_u64 : &part->min_u64); + } else if (max) { + part->max_u64 = UINT64_C(4294967295); + } else { + part->min_u64 = UINT64_C(0); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(1, max, max ? part->max_64 : part->min_64, prev); + } + break; + case LY_TYPE_UINT64: /* range */ + case LY_TYPE_STRING: /* length */ + case LY_TYPE_BINARY: /* length */ + if (valcopy) { + ret = ly_parse_uint(valcopy, strlen(valcopy), UINT64_C(18446744073709551615), LY_BASE_DEC, + max ? &part->max_u64 : &part->min_u64); + } else if (max) { + part->max_u64 = UINT64_C(18446744073709551615); + } else { + part->min_u64 = UINT64_C(0); + } + if (!ret && !first) { + ret = range_part_check_ascendancy(1, max, max ? part->max_64 : part->min_64, prev); + } + break; + default: + LOGINT(ctx->ctx); + ret = LY_EINT; + } + +finalize: + if (ret == LY_EDENIED) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - value \"%s\" does not fit the type limitations.", + length_restr ? "length" : "range", valcopy ? valcopy : *value); + } else if (ret == LY_EVALID) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - invalid value \"%s\".", + length_restr ? "length" : "range", valcopy ? valcopy : *value); + } else if (ret == LY_EEXIST) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - values are not in ascending order (%s).", + length_restr ? "length" : "range", + (valcopy && basetype != LY_TYPE_DEC64) ? valcopy : value ? *value : max ? "max" : "min"); + } else if (!ret && value) { + *value = *value + len; + } + free(valcopy); + return ret; +} + +LY_ERR +lys_compile_type_range(struct lysc_ctx *ctx, const struct lysp_restr *range_p, LY_DATA_TYPE basetype, ly_bool length_restr, + uint8_t frdigits, struct lysc_range *base_range, struct lysc_range **range) +{ + LY_ERR ret = LY_SUCCESS; + const char *expr; + struct lysc_range_part *parts = NULL, *part; + ly_bool range_expected = 0, uns; + LY_ARRAY_COUNT_TYPE parts_done = 0, u, v; + + assert(range); + assert(range_p); + + expr = range_p->arg.str; + while (1) { + if (isspace(*expr)) { + ++expr; + } else if (*expr == '\0') { + if (range_expected) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - unexpected end of the expression after \"..\" (%s).", + length_restr ? "length" : "range", range_p->arg.str); + ret = LY_EVALID; + goto cleanup; + } else if (!parts || (parts_done == LY_ARRAY_COUNT(parts))) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - unexpected end of the expression (%s).", + length_restr ? "length" : "range", range_p->arg.str); + ret = LY_EVALID; + goto cleanup; + } + parts_done++; + break; + } else if (!strncmp(expr, "min", ly_strlen_const("min"))) { + if (parts) { + /* min cannot be used elsewhere than in the first part */ + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - unexpected data before min keyword (%.*s).", length_restr ? "length" : "range", + (int)(expr - range_p->arg.str), range_p->arg.str); + ret = LY_EVALID; + goto cleanup; + } + expr += ly_strlen_const("min"); + + LY_ARRAY_NEW_GOTO(ctx->ctx, parts, part, ret, cleanup); + LY_CHECK_GOTO(ret = range_part_minmax(ctx, part, 0, 0, basetype, 1, length_restr, frdigits, base_range, NULL), cleanup); + part->max_64 = part->min_64; + } else if (*expr == '|') { + if (!parts || range_expected) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - unexpected beginning of the expression (%s).", length_restr ? "length" : "range", expr); + ret = LY_EVALID; + goto cleanup; + } + expr++; + parts_done++; + /* process next part of the expression */ + } else if (!strncmp(expr, "..", 2)) { + expr += 2; + while (isspace(*expr)) { + expr++; + } + if (!parts || (LY_ARRAY_COUNT(parts) == parts_done)) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - unexpected \"..\" without a lower bound.", length_restr ? "length" : "range"); + ret = LY_EVALID; + goto cleanup; + } + /* continue expecting the upper boundary */ + range_expected = 1; + } else if (isdigit(*expr) || (*expr == '-') || (*expr == '+')) { + /* number */ + if (range_expected) { + part = &parts[LY_ARRAY_COUNT(parts) - 1]; + LY_CHECK_GOTO(ret = range_part_minmax(ctx, part, 1, part->min_64, basetype, 0, length_restr, frdigits, NULL, &expr), cleanup); + range_expected = 0; + } else { + LY_ARRAY_NEW_GOTO(ctx->ctx, parts, part, ret, cleanup); + LY_CHECK_GOTO(ret = range_part_minmax(ctx, part, 0, parts_done ? parts[LY_ARRAY_COUNT(parts) - 2].max_64 : 0, + basetype, parts_done ? 0 : 1, length_restr, frdigits, NULL, &expr), cleanup); + part->max_64 = part->min_64; + } + + /* continue with possible another expression part */ + } else if (!strncmp(expr, "max", ly_strlen_const("max"))) { + expr += ly_strlen_const("max"); + while (isspace(*expr)) { + expr++; + } + if (*expr != '\0') { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid %s restriction - unexpected data after max keyword (%s).", + length_restr ? "length" : "range", expr); + ret = LY_EVALID; + goto cleanup; + } + if (range_expected) { + part = &parts[LY_ARRAY_COUNT(parts) - 1]; + LY_CHECK_GOTO(ret = range_part_minmax(ctx, part, 1, part->min_64, basetype, 0, length_restr, frdigits, base_range, NULL), cleanup); + range_expected = 0; + } else { + LY_ARRAY_NEW_GOTO(ctx->ctx, parts, part, ret, cleanup); + LY_CHECK_GOTO(ret = range_part_minmax(ctx, part, 1, parts_done ? parts[LY_ARRAY_COUNT(parts) - 2].max_64 : 0, + basetype, parts_done ? 0 : 1, length_restr, frdigits, base_range, NULL), cleanup); + part->min_64 = part->max_64; + } + } else { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid %s restriction - unexpected data (%s).", + length_restr ? "length" : "range", expr); + ret = LY_EVALID; + goto cleanup; + } + } + + /* check with the previous range/length restriction */ + if (base_range) { + switch (basetype) { + case LY_TYPE_BINARY: + case LY_TYPE_UINT8: + case LY_TYPE_UINT16: + case LY_TYPE_UINT32: + case LY_TYPE_UINT64: + case LY_TYPE_STRING: + uns = 1; + break; + case LY_TYPE_DEC64: + case LY_TYPE_INT8: + case LY_TYPE_INT16: + case LY_TYPE_INT32: + case LY_TYPE_INT64: + uns = 0; + break; + default: + LOGINT(ctx->ctx); + ret = LY_EINT; + goto cleanup; + } + for (u = v = 0; u < parts_done && v < LY_ARRAY_COUNT(base_range->parts); ++u) { + if ((uns && (parts[u].min_u64 < base_range->parts[v].min_u64)) || (!uns && (parts[u].min_64 < base_range->parts[v].min_64))) { + goto baseerror; + } + /* current lower bound is not lower than the base */ + if (base_range->parts[v].min_64 == base_range->parts[v].max_64) { + /* base has single value */ + if (base_range->parts[v].min_64 == parts[u].min_64) { + /* both lower bounds are the same */ + if (parts[u].min_64 != parts[u].max_64) { + /* current continues with a range */ + goto baseerror; + } else { + /* equal single values, move both forward */ + ++v; + continue; + } + } else { + /* base is single value lower than current range, so the + * value from base range is removed in the current, + * move only base and repeat checking */ + ++v; + --u; + continue; + } + } else { + /* base is the range */ + if (parts[u].min_64 == parts[u].max_64) { + /* current is a single value */ + if ((uns && (parts[u].max_u64 > base_range->parts[v].max_u64)) || (!uns && (parts[u].max_64 > base_range->parts[v].max_64))) { + /* current is behind the base range, so base range is omitted, + * move the base and keep the current for further check */ + ++v; + --u; + } /* else it is within the base range, so move the current, but keep the base */ + continue; + } else { + /* both are ranges - check the higher bound, the lower was already checked */ + if ((uns && (parts[u].max_u64 > base_range->parts[v].max_u64)) || (!uns && (parts[u].max_64 > base_range->parts[v].max_64))) { + /* higher bound is higher than the current higher bound */ + if ((uns && (parts[u].min_u64 > base_range->parts[v].max_u64)) || (!uns && (parts[u].min_64 > base_range->parts[v].max_64))) { + /* but the current lower bound is also higher, so the base range is omitted, + * continue with the same current, but move the base */ + --u; + ++v; + continue; + } + /* current range starts within the base range but end behind it */ + goto baseerror; + } else { + /* current range is smaller than the base, + * move current, but stay with the base */ + continue; + } + } + } + } + if (u != parts_done) { +baseerror: + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s restriction - the derived restriction (%s) is not equally or more limiting.", + length_restr ? "length" : "range", range_p->arg.str); + ret = LY_EVALID; + goto cleanup; + } + } + + if (!(*range)) { + *range = calloc(1, sizeof **range); + LY_CHECK_ERR_RET(!(*range), LOGMEM(ctx->ctx), LY_EMEM); + } + + /* we rewrite the following values as the types chain is being processed */ + if (range_p->eapptag) { + lydict_remove(ctx->ctx, (*range)->eapptag); + LY_CHECK_GOTO(ret = lydict_insert(ctx->ctx, range_p->eapptag, 0, &(*range)->eapptag), cleanup); + } + if (range_p->emsg) { + lydict_remove(ctx->ctx, (*range)->emsg); + LY_CHECK_GOTO(ret = lydict_insert(ctx->ctx, range_p->emsg, 0, &(*range)->emsg), cleanup); + } + if (range_p->dsc) { + lydict_remove(ctx->ctx, (*range)->dsc); + LY_CHECK_GOTO(ret = lydict_insert(ctx->ctx, range_p->dsc, 0, &(*range)->dsc), cleanup); + } + if (range_p->ref) { + lydict_remove(ctx->ctx, (*range)->ref); + LY_CHECK_GOTO(ret = lydict_insert(ctx->ctx, range_p->ref, 0, &(*range)->ref), cleanup); + } + /* extensions are taken only from the last range by the caller */ + + (*range)->parts = parts; + parts = NULL; +cleanup: + LY_ARRAY_FREE(parts); + + return ret; +} + +/** + * @brief Transform characters block in an XML Schema pattern into Perl character ranges. + * + * @param[in] ctx libyang context. + * @param[in] pattern Original pattern. + * @param[in,out] regex Pattern to modify. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_pattern_chblocks_xmlschema2perl(const struct ly_ctx *ctx, const char *pattern, char **regex) +{ +#define URANGE_LEN 19 + char *ublock2urange[][2] = { + {"BasicLatin", "[\\x{0000}-\\x{007F}]"}, + {"Latin-1Supplement", "[\\x{0080}-\\x{00FF}]"}, + {"LatinExtended-A", "[\\x{0100}-\\x{017F}]"}, + {"LatinExtended-B", "[\\x{0180}-\\x{024F}]"}, + {"IPAExtensions", "[\\x{0250}-\\x{02AF}]"}, + {"SpacingModifierLetters", "[\\x{02B0}-\\x{02FF}]"}, + {"CombiningDiacriticalMarks", "[\\x{0300}-\\x{036F}]"}, + {"Greek", "[\\x{0370}-\\x{03FF}]"}, + {"Cyrillic", "[\\x{0400}-\\x{04FF}]"}, + {"Armenian", "[\\x{0530}-\\x{058F}]"}, + {"Hebrew", "[\\x{0590}-\\x{05FF}]"}, + {"Arabic", "[\\x{0600}-\\x{06FF}]"}, + {"Syriac", "[\\x{0700}-\\x{074F}]"}, + {"Thaana", "[\\x{0780}-\\x{07BF}]"}, + {"Devanagari", "[\\x{0900}-\\x{097F}]"}, + {"Bengali", "[\\x{0980}-\\x{09FF}]"}, + {"Gurmukhi", "[\\x{0A00}-\\x{0A7F}]"}, + {"Gujarati", "[\\x{0A80}-\\x{0AFF}]"}, + {"Oriya", "[\\x{0B00}-\\x{0B7F}]"}, + {"Tamil", "[\\x{0B80}-\\x{0BFF}]"}, + {"Telugu", "[\\x{0C00}-\\x{0C7F}]"}, + {"Kannada", "[\\x{0C80}-\\x{0CFF}]"}, + {"Malayalam", "[\\x{0D00}-\\x{0D7F}]"}, + {"Sinhala", "[\\x{0D80}-\\x{0DFF}]"}, + {"Thai", "[\\x{0E00}-\\x{0E7F}]"}, + {"Lao", "[\\x{0E80}-\\x{0EFF}]"}, + {"Tibetan", "[\\x{0F00}-\\x{0FFF}]"}, + {"Myanmar", "[\\x{1000}-\\x{109F}]"}, + {"Georgian", "[\\x{10A0}-\\x{10FF}]"}, + {"HangulJamo", "[\\x{1100}-\\x{11FF}]"}, + {"Ethiopic", "[\\x{1200}-\\x{137F}]"}, + {"Cherokee", "[\\x{13A0}-\\x{13FF}]"}, + {"UnifiedCanadianAboriginalSyllabics", "[\\x{1400}-\\x{167F}]"}, + {"Ogham", "[\\x{1680}-\\x{169F}]"}, + {"Runic", "[\\x{16A0}-\\x{16FF}]"}, + {"Khmer", "[\\x{1780}-\\x{17FF}]"}, + {"Mongolian", "[\\x{1800}-\\x{18AF}]"}, + {"LatinExtendedAdditional", "[\\x{1E00}-\\x{1EFF}]"}, + {"GreekExtended", "[\\x{1F00}-\\x{1FFF}]"}, + {"GeneralPunctuation", "[\\x{2000}-\\x{206F}]"}, + {"SuperscriptsandSubscripts", "[\\x{2070}-\\x{209F}]"}, + {"CurrencySymbols", "[\\x{20A0}-\\x{20CF}]"}, + {"CombiningMarksforSymbols", "[\\x{20D0}-\\x{20FF}]"}, + {"LetterlikeSymbols", "[\\x{2100}-\\x{214F}]"}, + {"NumberForms", "[\\x{2150}-\\x{218F}]"}, + {"Arrows", "[\\x{2190}-\\x{21FF}]"}, + {"MathematicalOperators", "[\\x{2200}-\\x{22FF}]"}, + {"MiscellaneousTechnical", "[\\x{2300}-\\x{23FF}]"}, + {"ControlPictures", "[\\x{2400}-\\x{243F}]"}, + {"OpticalCharacterRecognition", "[\\x{2440}-\\x{245F}]"}, + {"EnclosedAlphanumerics", "[\\x{2460}-\\x{24FF}]"}, + {"BoxDrawing", "[\\x{2500}-\\x{257F}]"}, + {"BlockElements", "[\\x{2580}-\\x{259F}]"}, + {"GeometricShapes", "[\\x{25A0}-\\x{25FF}]"}, + {"MiscellaneousSymbols", "[\\x{2600}-\\x{26FF}]"}, + {"Dingbats", "[\\x{2700}-\\x{27BF}]"}, + {"BraillePatterns", "[\\x{2800}-\\x{28FF}]"}, + {"CJKRadicalsSupplement", "[\\x{2E80}-\\x{2EFF}]"}, + {"KangxiRadicals", "[\\x{2F00}-\\x{2FDF}]"}, + {"IdeographicDescriptionCharacters", "[\\x{2FF0}-\\x{2FFF}]"}, + {"CJKSymbolsandPunctuation", "[\\x{3000}-\\x{303F}]"}, + {"Hiragana", "[\\x{3040}-\\x{309F}]"}, + {"Katakana", "[\\x{30A0}-\\x{30FF}]"}, + {"Bopomofo", "[\\x{3100}-\\x{312F}]"}, + {"HangulCompatibilityJamo", "[\\x{3130}-\\x{318F}]"}, + {"Kanbun", "[\\x{3190}-\\x{319F}]"}, + {"BopomofoExtended", "[\\x{31A0}-\\x{31BF}]"}, + {"EnclosedCJKLettersandMonths", "[\\x{3200}-\\x{32FF}]"}, + {"CJKCompatibility", "[\\x{3300}-\\x{33FF}]"}, + {"CJKUnifiedIdeographsExtensionA", "[\\x{3400}-\\x{4DB5}]"}, + {"CJKUnifiedIdeographs", "[\\x{4E00}-\\x{9FFF}]"}, + {"YiSyllables", "[\\x{A000}-\\x{A48F}]"}, + {"YiRadicals", "[\\x{A490}-\\x{A4CF}]"}, + {"HangulSyllables", "[\\x{AC00}-\\x{D7A3}]"}, + {"PrivateUse", "[\\x{E000}-\\x{F8FF}]"}, + {"CJKCompatibilityIdeographs", "[\\x{F900}-\\x{FAFF}]"}, + {"AlphabeticPresentationForms", "[\\x{FB00}-\\x{FB4F}]"}, + {"ArabicPresentationForms-A", "[\\x{FB50}-\\x{FDFF}]"}, + {"CombiningHalfMarks", "[\\x{FE20}-\\x{FE2F}]"}, + {"CJKCompatibilityForms", "[\\x{FE30}-\\x{FE4F}]"}, + {"SmallFormVariants", "[\\x{FE50}-\\x{FE6F}]"}, + {"ArabicPresentationForms-B", "[\\x{FE70}-\\x{FEFE}]"}, + {"HalfwidthandFullwidthForms", "[\\x{FF00}-\\x{FFEF}]"}, + {"Specials", "[\\x{FEFF}|\\x{FFF0}-\\x{FFFD}]"}, + {NULL, NULL} + }; + + size_t idx, idx2, start, end; + char *perl_regex, *ptr; + + perl_regex = *regex; + + /* substitute Unicode Character Blocks with exact Character Ranges */ + while ((ptr = strstr(perl_regex, "\\p{Is"))) { + start = ptr - perl_regex; + + ptr = strchr(ptr, '}'); + if (!ptr) { + LOGVAL(ctx, LY_VCODE_INREGEXP, pattern, perl_regex + start + 2, "unterminated character property"); + return LY_EVALID; + } + end = (ptr - perl_regex) + 1; + + /* need more space */ + if (end - start < URANGE_LEN) { + perl_regex = ly_realloc(perl_regex, strlen(perl_regex) + (URANGE_LEN - (end - start)) + 1); + *regex = perl_regex; + LY_CHECK_ERR_RET(!perl_regex, LOGMEM(ctx), LY_EMEM); + } + + /* find our range */ + for (idx = 0; ublock2urange[idx][0]; ++idx) { + if (!strncmp(perl_regex + start + ly_strlen_const("\\p{Is"), + ublock2urange[idx][0], strlen(ublock2urange[idx][0]))) { + break; + } + } + if (!ublock2urange[idx][0]) { + LOGVAL(ctx, LY_VCODE_INREGEXP, pattern, perl_regex + start + 5, "unknown block name"); + return LY_EVALID; + } + + /* make the space in the string and replace the block (but we cannot include brackets if it was already enclosed in them) */ + for (idx2 = 0, idx = 0; idx2 < start; ++idx2) { + if ((perl_regex[idx2] == '[') && (!idx2 || (perl_regex[idx2 - 1] != '\\'))) { + ++idx; + } + if ((perl_regex[idx2] == ']') && (!idx2 || (perl_regex[idx2 - 1] != '\\'))) { + --idx; + } + } + if (idx) { + /* skip brackets */ + memmove(perl_regex + start + (URANGE_LEN - 2), perl_regex + end, strlen(perl_regex + end) + 1); + memcpy(perl_regex + start, ublock2urange[idx][1] + 1, URANGE_LEN - 2); + } else { + memmove(perl_regex + start + URANGE_LEN, perl_regex + end, strlen(perl_regex + end) + 1); + memcpy(perl_regex + start, ublock2urange[idx][1], URANGE_LEN); + } + } + + return LY_SUCCESS; +} + +LY_ERR +lys_compile_type_pattern_check(struct ly_ctx *ctx, const char *pattern, pcre2_code **code) +{ + size_t idx, size, brack; + char *perl_regex; + int err_code, compile_opts; + const char *orig_ptr; + PCRE2_SIZE err_offset; + pcre2_code *code_local; + ly_bool escaped; + LY_ERR r; + + /* adjust the expression to a Perl equivalent + * http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#regexs */ + + /* allocate space for the transformed pattern */ + size = strlen(pattern) + 1; + compile_opts = PCRE2_UTF | PCRE2_UCP | PCRE2_ANCHORED | PCRE2_DOLLAR_ENDONLY | PCRE2_NO_AUTO_CAPTURE; +#ifdef PCRE2_ENDANCHORED + compile_opts |= PCRE2_ENDANCHORED; +#else + /* add space for trailing $ anchor */ + size++; +#endif + perl_regex = malloc(size); + LY_CHECK_ERR_RET(!perl_regex, LOGMEM(ctx), LY_EMEM); + perl_regex[0] = '\0'; + + /* we need to replace all "$" and "^" (that are not in "[]") with "\$" and "\^" */ + brack = 0; + idx = 0; + escaped = 0; + orig_ptr = pattern; + while (orig_ptr[0]) { + switch (orig_ptr[0]) { + case '$': + case '^': + if (!brack) { + /* make space for the extra character */ + ++size; + perl_regex = ly_realloc(perl_regex, size); + LY_CHECK_ERR_RET(!perl_regex, LOGMEM(ctx), LY_EMEM); + + /* print escape slash */ + perl_regex[idx] = '\\'; + ++idx; + } + break; + case '\\': + /* escape character found or backslash is escaped */ + escaped = !escaped; + /* copy backslash and continue with the next character */ + perl_regex[idx] = orig_ptr[0]; + ++idx; + ++orig_ptr; + continue; + case '[': + if (!escaped) { + ++brack; + } + break; + case ']': + if (!brack && !escaped) { + /* If ']' does not terminate a character class expression, then pcre2_compile() implicitly escapes the + * ']' character. But this seems to be against the regular expressions rules declared in + * "XML schema: Datatypes" and therefore an error is returned. So for example if pattern is '\[a]' then + * pcre2 match characters '[a]' literally but in YANG such pattern is not allowed. + */ + LOGVAL(ctx, LY_VCODE_INREGEXP, pattern, orig_ptr, "character group doesn't begin with '['"); + free(perl_regex); + return LY_EVALID; + } else if (!escaped) { + --brack; + } + break; + default: + break; + } + + /* copy char */ + perl_regex[idx] = orig_ptr[0]; + + ++idx; + ++orig_ptr; + escaped = 0; + } +#ifndef PCRE2_ENDANCHORED + /* anchor match to end of subject */ + perl_regex[idx++] = '$'; +#endif + perl_regex[idx] = '\0'; + + /* transform character blocks */ + if ((r = lys_compile_pattern_chblocks_xmlschema2perl(ctx, pattern, &perl_regex))) { + free(perl_regex); + return r; + } + + /* must return 0, already checked during parsing */ + code_local = pcre2_compile((PCRE2_SPTR)perl_regex, PCRE2_ZERO_TERMINATED, compile_opts, + &err_code, &err_offset, NULL); + if (!code_local) { + PCRE2_UCHAR err_msg[LY_PCRE2_MSG_LIMIT] = {0}; + + pcre2_get_error_message(err_code, err_msg, LY_PCRE2_MSG_LIMIT); + LOGVAL(ctx, LY_VCODE_INREGEXP, pattern, perl_regex + err_offset, err_msg); + free(perl_regex); + return LY_EVALID; + } + free(perl_regex); + + if (code) { + *code = code_local; + } else { + free(code_local); + } + + return LY_SUCCESS; + +#undef URANGE_LEN +} + +LY_ERR +lys_compile_type_patterns(struct lysc_ctx *ctx, const struct lysp_restr *patterns_p, struct lysc_pattern **base_patterns, + struct lysc_pattern ***patterns) +{ + struct lysc_pattern **pattern; + LY_ARRAY_COUNT_TYPE u; + LY_ERR ret = LY_SUCCESS; + + /* first, copy the patterns from the base type */ + if (base_patterns) { + *patterns = lysc_patterns_dup(ctx->ctx, base_patterns); + LY_CHECK_ERR_RET(!(*patterns), LOGMEM(ctx->ctx), LY_EMEM); + } + + LY_ARRAY_FOR(patterns_p, u) { + LY_ARRAY_NEW_RET(ctx->ctx, (*patterns), pattern, LY_EMEM); + *pattern = calloc(1, sizeof **pattern); + ++(*pattern)->refcount; + + ret = lys_compile_type_pattern_check(ctx->ctx, &patterns_p[u].arg.str[1], &(*pattern)->code); + LY_CHECK_RET(ret); + + if (patterns_p[u].arg.str[0] == LYSP_RESTR_PATTERN_NACK) { + (*pattern)->inverted = 1; + } + DUP_STRING_GOTO(ctx->ctx, &patterns_p[u].arg.str[1], (*pattern)->expr, ret, done); + DUP_STRING_GOTO(ctx->ctx, patterns_p[u].eapptag, (*pattern)->eapptag, ret, done); + DUP_STRING_GOTO(ctx->ctx, patterns_p[u].emsg, (*pattern)->emsg, ret, done); + DUP_STRING_GOTO(ctx->ctx, patterns_p[u].dsc, (*pattern)->dsc, ret, done); + DUP_STRING_GOTO(ctx->ctx, patterns_p[u].ref, (*pattern)->ref, ret, done); + COMPILE_EXTS_GOTO(ctx, patterns_p[u].exts, (*pattern)->exts, (*pattern), ret, done); + } +done: + return ret; +} + +/** + * @brief map of the possible restrictions combination for the specific built-in type. + */ +static uint16_t type_substmt_map[LY_DATA_TYPE_COUNT] = { + 0 /* LY_TYPE_UNKNOWN */, + LYS_SET_LENGTH /* LY_TYPE_BINARY */, + LYS_SET_RANGE /* LY_TYPE_UINT8 */, + LYS_SET_RANGE /* LY_TYPE_UINT16 */, + LYS_SET_RANGE /* LY_TYPE_UINT32 */, + LYS_SET_RANGE /* LY_TYPE_UINT64 */, + LYS_SET_LENGTH | LYS_SET_PATTERN /* LY_TYPE_STRING */, + LYS_SET_BIT /* LY_TYPE_BITS */, + 0 /* LY_TYPE_BOOL */, + LYS_SET_FRDIGITS | LYS_SET_RANGE /* LY_TYPE_DEC64 */, + 0 /* LY_TYPE_EMPTY */, + LYS_SET_ENUM /* LY_TYPE_ENUM */, + LYS_SET_BASE /* LY_TYPE_IDENT */, + LYS_SET_REQINST /* LY_TYPE_INST */, + LYS_SET_REQINST | LYS_SET_PATH /* LY_TYPE_LEAFREF */, + LYS_SET_TYPE /* LY_TYPE_UNION */, + LYS_SET_RANGE /* LY_TYPE_INT8 */, + LYS_SET_RANGE /* LY_TYPE_INT16 */, + LYS_SET_RANGE /* LY_TYPE_INT32 */, + LYS_SET_RANGE /* LY_TYPE_INT64 */ +}; + +/** + * @brief stringification of the YANG built-in data types + */ +const char *ly_data_type2str[LY_DATA_TYPE_COUNT] = { + LY_TYPE_UNKNOWN_STR, + LY_TYPE_BINARY_STR, + LY_TYPE_UINT8_STR, + LY_TYPE_UINT16_STR, + LY_TYPE_UINT32_STR, + LY_TYPE_UINT64_STR, + LY_TYPE_STRING_STR, + LY_TYPE_BITS_STR, + LY_TYPE_BOOL_STR, + LY_TYPE_DEC64_STR, + LY_TYPE_EMPTY_STR, + LY_TYPE_ENUM_STR, + LY_TYPE_IDENT_STR, + LY_TYPE_INST_STR, + LY_TYPE_LEAFREF_STR, + LY_TYPE_UNION_STR, + LY_TYPE_INT8_STR, + LY_TYPE_INT16_STR, + LY_TYPE_INT32_STR, + LY_TYPE_INT64_STR +}; + +LY_ERR +lys_compile_type_enums(struct lysc_ctx *ctx, const struct lysp_type_enum *enums_p, LY_DATA_TYPE basetype, + struct lysc_type_bitenum_item *base_enums, struct lysc_type_bitenum_item **bitenums) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v, match = 0; + int32_t highest_value = INT32_MIN, cur_val = INT32_MIN; + uint32_t highest_position = 0, cur_pos = 0; + struct lysc_type_bitenum_item *e, storage; + ly_bool enabled; + + if (base_enums && (ctx->pmod->version < LYS_VERSION_1_1)) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "%s type can be subtyped only in YANG 1.1 modules.", + basetype == LY_TYPE_ENUM ? "Enumeration" : "Bits"); + return LY_EVALID; + } + + LY_ARRAY_FOR(enums_p, u) { + /* perform all checks */ + if (base_enums) { + /* check the enum/bit presence in the base type - the set of enums/bits in the derived type must be a subset */ + LY_ARRAY_FOR(base_enums, v) { + if (!strcmp(enums_p[u].name, base_enums[v].name)) { + break; + } + } + if (v == LY_ARRAY_COUNT(base_enums)) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid %s - derived type adds new item \"%s\".", + basetype == LY_TYPE_ENUM ? "enumeration" : "bits", enums_p[u].name); + return LY_EVALID; + } + match = v; + } + + if (basetype == LY_TYPE_ENUM) { + if (enums_p[u].flags & LYS_SET_VALUE) { + /* value assigned by model */ + cur_val = (int32_t)enums_p[u].value; + /* check collision with other values */ + LY_ARRAY_FOR(*bitenums, v) { + if (cur_val == (*bitenums)[v].value) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid enumeration - value %d collide in items \"%s\" and \"%s\".", + cur_val, enums_p[u].name, (*bitenums)[v].name); + return LY_EVALID; + } + } + } else if (base_enums) { + /* inherit the assigned value */ + cur_val = base_enums[match].value; + } else { + /* assign value automatically */ + if (u == 0) { + cur_val = 0; + } else if (highest_value == INT32_MAX) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid enumeration - it is not possible to auto-assign enum value for " + "\"%s\" since the highest value is already 2147483647.", enums_p[u].name); + return LY_EVALID; + } else { + cur_val = highest_value + 1; + } + } + + /* save highest value for auto assing */ + if (highest_value < cur_val) { + highest_value = cur_val; + } + } else { /* LY_TYPE_BITS */ + if (enums_p[u].flags & LYS_SET_VALUE) { + /* value assigned by model */ + cur_pos = (uint32_t)enums_p[u].value; + /* check collision with other values */ + LY_ARRAY_FOR(*bitenums, v) { + if (cur_pos == (*bitenums)[v].position) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid bits - position %u collide in items \"%s\" and \"%s\".", + cur_pos, enums_p[u].name, (*bitenums)[v].name); + return LY_EVALID; + } + } + } else if (base_enums) { + /* inherit the assigned value */ + cur_pos = base_enums[match].position; + } else { + /* assign value automatically */ + if (u == 0) { + cur_pos = 0; + } else if (highest_position == UINT32_MAX) { + /* counter overflow */ + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid bits - it is not possible to auto-assign bit position for " + "\"%s\" since the highest value is already 4294967295.", enums_p[u].name); + return LY_EVALID; + } else { + cur_pos = highest_position + 1; + } + } + + /* save highest position for auto assing */ + if (highest_position < cur_pos) { + highest_position = cur_pos; + } + } + + /* the assigned values must not change from the derived type */ + if (base_enums) { + if (basetype == LY_TYPE_ENUM) { + if (cur_val != base_enums[match].value) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid enumeration - value of the item \"%s\" has changed from %d to %d in the derived type.", + enums_p[u].name, base_enums[match].value, cur_val); + return LY_EVALID; + } + } else { + if (cur_pos != base_enums[match].position) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid bits - position of the item \"%s\" has changed from %u to %u in the derived type.", + enums_p[u].name, base_enums[match].position, cur_pos); + return LY_EVALID; + } + } + } + + /* add new enum/bit */ + LY_ARRAY_NEW_RET(ctx->ctx, *bitenums, e, LY_EMEM); + DUP_STRING_GOTO(ctx->ctx, enums_p[u].name, e->name, ret, done); + DUP_STRING_GOTO(ctx->ctx, enums_p[u].dsc, e->dsc, ret, done); + DUP_STRING_GOTO(ctx->ctx, enums_p[u].ref, e->ref, ret, done); + e->flags = (enums_p[u].flags & LYS_FLAGS_COMPILED_MASK) | (basetype == LY_TYPE_ENUM ? LYS_IS_ENUM : 0); + if (basetype == LY_TYPE_ENUM) { + e->value = cur_val; + } else { + e->position = cur_pos; + } + COMPILE_EXTS_GOTO(ctx, enums_p[u].exts, e->exts, e, ret, done); + + /* evaluate if-ffeatures */ + LY_CHECK_RET(lys_eval_iffeatures(ctx->ctx, enums_p[u].iffeatures, &enabled)); + if (!enabled) { + /* set only flag, later resolved and removed */ + e->flags |= LYS_DISABLED; + } + + if (basetype == LY_TYPE_BITS) { + /* keep bits ordered by position */ + for (v = u; v && (*bitenums)[v - 1].position > e->position; --v) {} + if (v != u) { + memcpy(&storage, e, sizeof *e); + memmove(&(*bitenums)[v + 1], &(*bitenums)[v], (u - v) * sizeof **bitenums); + memcpy(&(*bitenums)[v], &storage, sizeof storage); + } + } + } + +done: + return ret; +} + +static LY_ERR +lys_compile_type_union(struct lysc_ctx *ctx, struct lysp_type *ptypes, struct lysp_node *context_pnode, uint16_t context_flags, + const char *context_name, struct lysc_type ***utypes_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type **utypes = *utypes_p; + struct lysc_type_union *un_aux = NULL; + + LY_ARRAY_CREATE_GOTO(ctx->ctx, utypes, LY_ARRAY_COUNT(ptypes), ret, error); + for (LY_ARRAY_COUNT_TYPE u = 0, additional = 0; u < LY_ARRAY_COUNT(ptypes); ++u) { + ret = lys_compile_type(ctx, context_pnode, context_flags, context_name, &ptypes[u], &utypes[u + additional], + NULL, NULL); + LY_CHECK_GOTO(ret, error); + if (utypes[u + additional]->basetype == LY_TYPE_UNION) { + /* add space for additional types from the union subtype */ + un_aux = (struct lysc_type_union *)utypes[u + additional]; + LY_ARRAY_CREATE_GOTO(ctx->ctx, utypes, + LY_ARRAY_COUNT(ptypes) + additional + LY_ARRAY_COUNT(un_aux->types) - LY_ARRAY_COUNT(utypes), ret, error); + + /* copy subtypes of the subtype union */ + for (LY_ARRAY_COUNT_TYPE v = 0; v < LY_ARRAY_COUNT(un_aux->types); ++v) { + if (un_aux->types[v]->basetype == LY_TYPE_LEAFREF) { + struct lysc_type_leafref *lref; + + /* duplicate the whole structure because of the instance-specific path resolving for realtype */ + utypes[u + additional] = calloc(1, sizeof(struct lysc_type_leafref)); + LY_CHECK_ERR_GOTO(!utypes[u + additional], LOGMEM(ctx->ctx); ret = LY_EMEM, error); + lref = (struct lysc_type_leafref *)utypes[u + additional]; + + lref->basetype = LY_TYPE_LEAFREF; + ret = lyxp_expr_dup(ctx->ctx, ((struct lysc_type_leafref *)un_aux->types[v])->path, 0, 0, &lref->path); + LY_CHECK_GOTO(ret, error); + lref->refcount = 1; + lref->require_instance = ((struct lysc_type_leafref *)un_aux->types[v])->require_instance; + ret = lyplg_type_prefix_data_dup(ctx->ctx, LY_VALUE_SCHEMA_RESOLVED, + ((struct lysc_type_leafref *)un_aux->types[v])->prefixes, (void **)&lref->prefixes); + LY_CHECK_GOTO(ret, error); + /* TODO extensions */ + + } else { + utypes[u + additional] = un_aux->types[v]; + LY_ATOMIC_INC_BARRIER(un_aux->types[v]->refcount); + } + ++additional; + LY_ARRAY_INCREMENT(utypes); + } + /* compensate u increment in main loop */ + --additional; + + /* free the replaced union subtype */ + lysc_type_free(&ctx->free_ctx, (struct lysc_type *)un_aux); + un_aux = NULL; + } else { + LY_ARRAY_INCREMENT(utypes); + } + } + + *utypes_p = utypes; + return LY_SUCCESS; + +error: + if (un_aux) { + lysc_type_free(&ctx->free_ctx, (struct lysc_type *)un_aux); + } + *utypes_p = utypes; + return ret; +} + +/** + * @brief The core of the lys_compile_type() - compile information about the given type (from typedef or leaf/leaf-list). + * @param[in] ctx Compile context. + * @param[in] context_pnode Schema node where the type/typedef is placed to correctly find the base types. + * @param[in] context_flags Flags of the context node or the referencing typedef to correctly check status of referencing and referenced objects. + * @param[in] context_name Name of the context node or referencing typedef for logging. + * @param[in] type_p Parsed type to compile. + * @param[in] basetype Base YANG built-in type of the type to compile. + * @param[in] tpdfname Name of the type's typedef, serves as a flag - if it is leaf/leaf-list's type, it is NULL. + * @param[in] base The latest base (compiled) type from which the current type is being derived. + * @param[out] type Newly created type structure with the filled information about the type. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t context_flags, const char *context_name, + struct lysp_type *type_p, LY_DATA_TYPE basetype, const char *tpdfname, const struct lysc_type *base, + struct lysc_type **type) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_bin *bin; + struct lysc_type_num *num; + struct lysc_type_str *str; + struct lysc_type_bits *bits; + struct lysc_type_enum *enumeration; + struct lysc_type_dec *dec; + struct lysc_type_identityref *idref; + struct lysc_type_leafref *lref; + struct lysc_type_union *un; + + switch (basetype) { + case LY_TYPE_BINARY: + bin = (struct lysc_type_bin *)(*type); + + /* RFC 7950 9.8.1, 9.4.4 - length, number of octets it contains */ + if (type_p->length) { + LY_CHECK_RET(lys_compile_type_range(ctx, type_p->length, basetype, 1, 0, + base ? ((struct lysc_type_bin *)base)->length : NULL, &bin->length)); + if (!tpdfname) { + COMPILE_EXTS_GOTO(ctx, type_p->length->exts, bin->length->exts, bin->length, ret, cleanup); + } + } + break; + case LY_TYPE_BITS: + /* RFC 7950 9.7 - bits */ + bits = (struct lysc_type_bits *)(*type); + if (type_p->bits) { + LY_CHECK_RET(lys_compile_type_enums(ctx, type_p->bits, basetype, + base ? (struct lysc_type_bitenum_item *)((struct lysc_type_bits *)base)->bits : NULL, + (struct lysc_type_bitenum_item **)&bits->bits)); + } + + if (!base && !type_p->flags) { + /* type derived from bits built-in type must contain at least one bit */ + if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "bit", "bits type ", tpdfname); + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "bit", "bits type", ""); + } + return LY_EVALID; + } + break; + case LY_TYPE_DEC64: + dec = (struct lysc_type_dec *)(*type); + + /* RFC 7950 9.3.4 - fraction-digits */ + if (!base) { + if (!type_p->fraction_digits) { + if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "fraction-digits", "decimal64 type ", tpdfname); + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "fraction-digits", "decimal64 type", ""); + } + return LY_EVALID; + } + dec->fraction_digits = type_p->fraction_digits; + } else { + if (type_p->fraction_digits) { + /* fraction digits is prohibited in types not directly derived from built-in decimal64 */ + if (tpdfname) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid fraction-digits substatement for type \"%s\" not directly derived from decimal64 built-in type.", + tpdfname); + } else { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid fraction-digits substatement for type not directly derived from decimal64 built-in type."); + } + return LY_EVALID; + } + dec->fraction_digits = ((struct lysc_type_dec *)base)->fraction_digits; + } + + /* RFC 7950 9.2.4 - range */ + if (type_p->range) { + LY_CHECK_RET(lys_compile_type_range(ctx, type_p->range, basetype, 0, dec->fraction_digits, + base ? ((struct lysc_type_dec *)base)->range : NULL, &dec->range)); + if (!tpdfname) { + COMPILE_EXTS_GOTO(ctx, type_p->range->exts, dec->range->exts, dec->range, ret, cleanup); + } + } + break; + case LY_TYPE_STRING: + str = (struct lysc_type_str *)(*type); + + /* RFC 7950 9.4.4 - length */ + if (type_p->length) { + LY_CHECK_RET(lys_compile_type_range(ctx, type_p->length, basetype, 1, 0, + base ? ((struct lysc_type_str *)base)->length : NULL, &str->length)); + if (!tpdfname) { + COMPILE_EXTS_GOTO(ctx, type_p->length->exts, str->length->exts, str->length, ret, cleanup); + } + } else if (base && ((struct lysc_type_str *)base)->length) { + str->length = lysc_range_dup(ctx->ctx, ((struct lysc_type_str *)base)->length); + } + + /* RFC 7950 9.4.5 - pattern */ + if (type_p->patterns) { + LY_CHECK_RET(lys_compile_type_patterns(ctx, type_p->patterns, + base ? ((struct lysc_type_str *)base)->patterns : NULL, &str->patterns)); + } else if (base && ((struct lysc_type_str *)base)->patterns) { + str->patterns = lysc_patterns_dup(ctx->ctx, ((struct lysc_type_str *)base)->patterns); + } + break; + case LY_TYPE_ENUM: + enumeration = (struct lysc_type_enum *)(*type); + + /* RFC 7950 9.6 - enum */ + if (type_p->enums) { + LY_CHECK_RET(lys_compile_type_enums(ctx, type_p->enums, basetype, + base ? ((struct lysc_type_enum *)base)->enums : NULL, &enumeration->enums)); + } + + if (!base && !type_p->flags) { + /* type derived from enumerations built-in type must contain at least one enum */ + if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "enum", "enumeration type ", tpdfname); + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "enum", "enumeration type", ""); + } + return LY_EVALID; + } + break; + case LY_TYPE_INT8: + case LY_TYPE_UINT8: + case LY_TYPE_INT16: + case LY_TYPE_UINT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT32: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + num = (struct lysc_type_num *)(*type); + + /* RFC 6020 9.2.4 - range */ + if (type_p->range) { + LY_CHECK_RET(lys_compile_type_range(ctx, type_p->range, basetype, 0, 0, + base ? ((struct lysc_type_num *)base)->range : NULL, &num->range)); + if (!tpdfname) { + COMPILE_EXTS_GOTO(ctx, type_p->range->exts, num->range->exts, num->range, ret, cleanup); + } + } + break; + case LY_TYPE_IDENT: + idref = (struct lysc_type_identityref *)(*type); + + /* RFC 7950 9.10.2 - base */ + if (type_p->bases) { + if (base) { + /* only the directly derived identityrefs can contain base specification */ + if (tpdfname) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid base substatement for the type \"%s\" not directly derived from identityref built-in type.", + tpdfname); + } else { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid base substatement for the type not directly derived from identityref built-in type."); + } + return LY_EVALID; + } + LY_CHECK_RET(lys_compile_identity_bases(ctx, type_p->pmod, type_p->bases, NULL, &idref->bases)); + } + + if (!base && !type_p->flags) { + /* type derived from identityref built-in type must contain at least one base */ + if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "base", "identityref type ", tpdfname); + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "base", "identityref type", ""); + } + return LY_EVALID; + } + break; + case LY_TYPE_LEAFREF: + lref = (struct lysc_type_leafref *)*type; + + /* RFC 7950 9.9.3 - require-instance */ + if (type_p->flags & LYS_SET_REQINST) { + if (type_p->pmod->version < LYS_VERSION_1_1) { + if (tpdfname) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Leafref type \"%s\" can be restricted by require-instance statement only in YANG 1.1 modules.", tpdfname); + } else { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Leafref type can be restricted by require-instance statement only in YANG 1.1 modules."); + } + return LY_EVALID; + } + lref->require_instance = type_p->require_instance; + } else if (base) { + /* inherit */ + lref->require_instance = ((struct lysc_type_leafref *)base)->require_instance; + } else { + /* default is true */ + lref->require_instance = 1; + } + if (type_p->path) { + LY_VALUE_FORMAT format; + + LY_CHECK_RET(lyxp_expr_dup(ctx->ctx, type_p->path, 0, 0, &lref->path)); + LY_CHECK_RET(lyplg_type_prefix_data_new(ctx->ctx, type_p->path->expr, strlen(type_p->path->expr), + LY_VALUE_SCHEMA, type_p->pmod, &format, (void **)&lref->prefixes)); + } else if (base) { + LY_CHECK_RET(lyxp_expr_dup(ctx->ctx, ((struct lysc_type_leafref *)base)->path, 0, 0, &lref->path)); + LY_CHECK_RET(lyplg_type_prefix_data_dup(ctx->ctx, LY_VALUE_SCHEMA_RESOLVED, + ((struct lysc_type_leafref *)base)->prefixes, (void **)&lref->prefixes)); + } else if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "path", "leafref type ", tpdfname); + return LY_EVALID; + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "path", "leafref type", ""); + return LY_EVALID; + } + break; + case LY_TYPE_INST: + /* RFC 7950 9.9.3 - require-instance */ + if (type_p->flags & LYS_SET_REQINST) { + ((struct lysc_type_instanceid *)(*type))->require_instance = type_p->require_instance; + } else { + /* default is true */ + ((struct lysc_type_instanceid *)(*type))->require_instance = 1; + } + break; + case LY_TYPE_UNION: + un = (struct lysc_type_union *)(*type); + + /* RFC 7950 7.4 - type */ + if (type_p->types) { + if (base) { + /* only the directly derived union can contain types specification */ + if (tpdfname) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid type substatement for the type \"%s\" not directly derived from union built-in type.", + tpdfname); + } else { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, + "Invalid type substatement for the type not directly derived from union built-in type."); + } + return LY_EVALID; + } + /* compile the type */ + LY_CHECK_RET(lys_compile_type_union(ctx, type_p->types, context_pnode, context_flags, context_name, &un->types)); + } + + if (!base && !type_p->flags) { + /* type derived from union built-in type must contain at least one type */ + if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "type", "union type ", tpdfname); + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "type", "union type", ""); + } + return LY_EVALID; + } + break; + case LY_TYPE_BOOL: + case LY_TYPE_EMPTY: + case LY_TYPE_UNKNOWN: /* just to complete switch */ + break; + } + + if (tpdfname) { + switch (basetype) { + case LY_TYPE_BINARY: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_bin)); + break; + case LY_TYPE_BITS: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_bits)); + break; + case LY_TYPE_DEC64: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_dec)); + break; + case LY_TYPE_STRING: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_str)); + break; + case LY_TYPE_ENUM: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_enum)); + break; + case LY_TYPE_INT8: + case LY_TYPE_UINT8: + case LY_TYPE_INT16: + case LY_TYPE_UINT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT32: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_num)); + break; + case LY_TYPE_IDENT: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_identityref)); + break; + case LY_TYPE_LEAFREF: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_leafref)); + break; + case LY_TYPE_INST: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_instanceid)); + break; + case LY_TYPE_UNION: + type_p->compiled = *type; + *type = calloc(1, sizeof(struct lysc_type_union)); + break; + case LY_TYPE_BOOL: + case LY_TYPE_EMPTY: + case LY_TYPE_UNKNOWN: /* just to complete switch */ + break; + } + } + LY_CHECK_ERR_RET(!(*type), LOGMEM(ctx->ctx), LY_EMEM); + +cleanup: + return ret; +} + +LY_ERR +lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t context_flags, const char *context_name, + const struct lysp_type *type_p, struct lysc_type **type, const char **units, struct lysp_qname **dflt) +{ + LY_ERR ret = LY_SUCCESS; + ly_bool dummyloops = 0; + struct type_context { + const struct lysp_tpdf *tpdf; + struct lysp_node *node; + } *tctx, *tctx_prev = NULL, *tctx_iter; + LY_DATA_TYPE basetype = LY_TYPE_UNKNOWN; + struct lysc_type *base = NULL, *prev_type; + struct ly_set tpdf_chain = {0}; + struct lyplg_type *plugin; + + (*type) = NULL; + if (dflt) { + *dflt = NULL; + } + + tctx = calloc(1, sizeof *tctx); + LY_CHECK_ERR_RET(!tctx, LOGMEM(ctx->ctx), LY_EMEM); + for (ret = lysp_type_find(type_p->name, context_pnode, type_p->pmod, ctx->ext, &basetype, &tctx->tpdf, &tctx->node); + ret == LY_SUCCESS; + ret = lysp_type_find(tctx_prev->tpdf->type.name, tctx_prev->node, tctx_prev->tpdf->type.pmod, ctx->ext, + &basetype, &tctx->tpdf, &tctx->node)) { + if (basetype) { + break; + } + + /* check status */ + ret = lysc_check_status(ctx, context_flags, (void *)type_p->pmod, context_name, tctx->tpdf->flags, + (void *)tctx->tpdf->type.pmod, tctx->node ? tctx->node->name : tctx->tpdf->name); + LY_CHECK_ERR_GOTO(ret, free(tctx), cleanup); + + if (units && !*units) { + /* inherit units */ + DUP_STRING(ctx->ctx, tctx->tpdf->units, *units, ret); + LY_CHECK_ERR_GOTO(ret, free(tctx), cleanup); + } + if (dflt && !*dflt && tctx->tpdf->dflt.str) { + /* inherit default */ + *dflt = (struct lysp_qname *)&tctx->tpdf->dflt; + } + if (dummyloops && (!units || *units) && dflt && *dflt) { + basetype = ((struct type_context *)tpdf_chain.objs[tpdf_chain.count - 1])->tpdf->type.compiled->basetype; + break; + } + + if (tctx->tpdf->type.compiled && (tctx->tpdf->type.compiled->refcount == 1)) { + /* context recompilation - everything was freed previously (the only reference is from the parsed type itself) + * and we need now recompile the type again in the updated context. */ + lysc_type_free(&ctx->free_ctx, tctx->tpdf->type.compiled); + ((struct lysp_tpdf *)tctx->tpdf)->type.compiled = NULL; + } + + if (tctx->tpdf->type.compiled) { + /* it is not necessary to continue, the rest of the chain was already compiled, + * but we still may need to inherit default and units values, so start dummy loops */ + basetype = tctx->tpdf->type.compiled->basetype; + ret = ly_set_add(&tpdf_chain, tctx, 1, NULL); + LY_CHECK_ERR_GOTO(ret, free(tctx), cleanup); + + if ((units && !*units) || (dflt && !*dflt)) { + dummyloops = 1; + goto preparenext; + } else { + tctx = NULL; + break; + } + } + + /* circular typedef reference detection */ + for (uint32_t u = 0; u < tpdf_chain.count; u++) { + /* local part */ + tctx_iter = (struct type_context *)tpdf_chain.objs[u]; + if (tctx_iter->tpdf == tctx->tpdf) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid \"%s\" type reference - circular chain of types detected.", tctx->tpdf->name); + free(tctx); + ret = LY_EVALID; + goto cleanup; + } + } + for (uint32_t u = 0; u < ctx->tpdf_chain.count; u++) { + /* global part for unions corner case */ + tctx_iter = (struct type_context *)ctx->tpdf_chain.objs[u]; + if (tctx_iter->tpdf == tctx->tpdf) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid \"%s\" type reference - circular chain of types detected.", tctx->tpdf->name); + free(tctx); + ret = LY_EVALID; + goto cleanup; + } + } + + /* store information for the following processing */ + ret = ly_set_add(&tpdf_chain, tctx, 1, NULL); + LY_CHECK_ERR_GOTO(ret, free(tctx), cleanup); + +preparenext: + /* prepare next loop */ + tctx_prev = tctx; + tctx = calloc(1, sizeof *tctx); + LY_CHECK_ERR_RET(!tctx, LOGMEM(ctx->ctx), LY_EMEM); + } + free(tctx); + + /* allocate type according to the basetype */ + switch (basetype) { + case LY_TYPE_BINARY: + *type = calloc(1, sizeof(struct lysc_type_bin)); + break; + case LY_TYPE_BITS: + *type = calloc(1, sizeof(struct lysc_type_bits)); + break; + case LY_TYPE_BOOL: + case LY_TYPE_EMPTY: + *type = calloc(1, sizeof(struct lysc_type)); + break; + case LY_TYPE_DEC64: + *type = calloc(1, sizeof(struct lysc_type_dec)); + break; + case LY_TYPE_ENUM: + *type = calloc(1, sizeof(struct lysc_type_enum)); + break; + case LY_TYPE_IDENT: + *type = calloc(1, sizeof(struct lysc_type_identityref)); + break; + case LY_TYPE_INST: + *type = calloc(1, sizeof(struct lysc_type_instanceid)); + break; + case LY_TYPE_LEAFREF: + *type = calloc(1, sizeof(struct lysc_type_leafref)); + break; + case LY_TYPE_STRING: + *type = calloc(1, sizeof(struct lysc_type_str)); + break; + case LY_TYPE_UNION: + *type = calloc(1, sizeof(struct lysc_type_union)); + break; + case LY_TYPE_INT8: + case LY_TYPE_UINT8: + case LY_TYPE_INT16: + case LY_TYPE_UINT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT32: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + *type = calloc(1, sizeof(struct lysc_type_num)); + break; + case LY_TYPE_UNKNOWN: + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Referenced type \"%s\" not found.", tctx_prev ? tctx_prev->tpdf->type.name : type_p->name); + ret = LY_EVALID; + goto cleanup; + } + LY_CHECK_ERR_GOTO(!(*type), LOGMEM(ctx->ctx), cleanup); + if (~type_substmt_map[basetype] & type_p->flags) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid type restrictions for %s type.", + ly_data_type2str[basetype]); + free(*type); + (*type) = NULL; + ret = LY_EVALID; + goto cleanup; + } + + /* get restrictions from the referred typedefs */ + for (uint32_t u = tpdf_chain.count - 1; u + 1 > 0; --u) { + tctx = (struct type_context *)tpdf_chain.objs[u]; + + /* remember the typedef context for circular check */ + ret = ly_set_add(&ctx->tpdf_chain, tctx, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + + if (tctx->tpdf->type.compiled) { + /* already compiled */ + base = tctx->tpdf->type.compiled; + continue; + } + + /* try to find loaded user type plugins */ + plugin = lyplg_type_plugin_find(tctx->tpdf->type.pmod->mod->name, tctx->tpdf->type.pmod->mod->revision, + tctx->tpdf->name); + if (!plugin && base) { + /* use the base type implementation if available */ + plugin = base->plugin; + } + if (!plugin) { + /* use the internal built-in type implementation */ + plugin = lyplg_type_plugin_find("", NULL, ly_data_type2str[basetype]); + } + assert(plugin); + + if ((basetype != LY_TYPE_LEAFREF) && (u != tpdf_chain.count - 1) && !(tctx->tpdf->type.flags) && + (plugin == base->plugin)) { + /* no change, reuse the compiled base */ + ((struct lysp_tpdf *)tctx->tpdf)->type.compiled = base; + LY_ATOMIC_INC_BARRIER(base->refcount); + continue; + } + + LY_ATOMIC_INC_BARRIER((*type)->refcount); + if (~type_substmt_map[basetype] & tctx->tpdf->type.flags) { + LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid type \"%s\" restriction(s) for %s type.", + tctx->tpdf->name, ly_data_type2str[basetype]); + ret = LY_EVALID; + goto cleanup; + } else if ((basetype == LY_TYPE_EMPTY) && tctx->tpdf->dflt.str) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Invalid type \"%s\" - \"empty\" type must not have a default value (%s).", + tctx->tpdf->name, tctx->tpdf->dflt.str); + ret = LY_EVALID; + goto cleanup; + } + + (*type)->basetype = basetype; + (*type)->plugin = plugin; + + /* collect extensions */ + COMPILE_EXTS_GOTO(ctx, tctx->tpdf->type.exts, (*type)->exts, (*type), ret, cleanup); + + /* compile the new typedef */ + prev_type = *type; + ret = lys_compile_type_(ctx, tctx->node, tctx->tpdf->flags, tctx->tpdf->name, + &((struct lysp_tpdf *)tctx->tpdf)->type, basetype, tctx->tpdf->name, base, type); + LY_CHECK_GOTO(ret, cleanup); + base = prev_type; + } + /* remove the processed typedef contexts from the stack for circular check */ + ctx->tpdf_chain.count = ctx->tpdf_chain.count - tpdf_chain.count; + + /* process the type definition in leaf */ + if (type_p->flags || !base || (basetype == LY_TYPE_LEAFREF)) { + /* get restrictions from the node itself */ + (*type)->basetype = basetype; + (*type)->plugin = base ? base->plugin : lyplg_type_plugin_find("", NULL, ly_data_type2str[basetype]); + LY_ATOMIC_INC_BARRIER((*type)->refcount); + ret = lys_compile_type_(ctx, context_pnode, context_flags, context_name, (struct lysp_type *)type_p, basetype, + NULL, base, type); + LY_CHECK_GOTO(ret, cleanup); + } else if ((basetype != LY_TYPE_BOOL) && (basetype != LY_TYPE_EMPTY)) { + /* no specific restriction in leaf's type definition, copy from the base */ + free(*type); + (*type) = base; + LY_ATOMIC_INC_BARRIER((*type)->refcount); + } + + COMPILE_EXTS_GOTO(ctx, type_p->exts, (*type)->exts, (*type), ret, cleanup); + +cleanup: + ly_set_erase(&tpdf_chain, free); + return ret; +} + +/** + * @brief Check uniqness of the node/action/notification name. + * + * Data nodes, actions/RPCs and Notifications are stored separately (in distinguish lists) in the schema + * structures, but they share the namespace so we need to check their name collisions. + * + * @param[in] ctx Compile context. + * @param[in] parent Parent of the nodes to check, can be NULL. + * @param[in] name Name of the item to find in the given lists. + * @param[in] exclude Node that was just added that should be excluded from the name checking. + * @return LY_SUCCESS in case of unique name, LY_EEXIST otherwise. + */ +static LY_ERR +lys_compile_node_uniqness(struct lysc_ctx *ctx, const struct lysc_node *parent, const char *name, + const struct lysc_node *exclude) +{ + const struct lysc_node *iter, *iter2; + const struct lysc_node_action *actions; + const struct lysc_node_notif *notifs; + uint32_t getnext_flags; + struct ly_set parent_choices = {0}; + +#define CHECK_NODE(iter, exclude, name) (iter != (void *)exclude && (iter)->module == exclude->module && !strcmp(name, (iter)->name)) + + if (exclude->nodetype == LYS_CASE) { + /* check restricted only to all the cases */ + assert(parent->nodetype == LYS_CHOICE); + LY_LIST_FOR(lysc_node_child(parent), iter) { + if (CHECK_NODE(iter, exclude, name)) { + LOGVAL(ctx->ctx, LY_VCODE_DUPIDENT, name, "case"); + return LY_EEXIST; + } + } + + return LY_SUCCESS; + } + + /* no reason for our parent to be choice anymore */ + assert(!parent || (parent->nodetype != LYS_CHOICE)); + + if (parent && (parent->nodetype == LYS_CASE)) { + /* move to the first data definition parent */ + + /* but remember the choice nodes on the parents path to avoid believe they collide with our node */ + iter = lysc_data_parent(parent); + do { + parent = parent->parent; + if (parent && (parent->nodetype == LYS_CHOICE)) { + ly_set_add(&parent_choices, (void *)parent, 1, NULL); + } + } while (parent != iter); + } + + getnext_flags = LYS_GETNEXT_WITHCHOICE; + if (parent && (parent->nodetype & (LYS_RPC | LYS_ACTION))) { + /* move to the inout to avoid traversing a not-filled-yet (the other) node */ + if (exclude->flags & LYS_IS_OUTPUT) { + getnext_flags |= LYS_GETNEXT_OUTPUT; + parent = lysc_node_child(parent)->next; + } else { + parent = lysc_node_child(parent); + } + } + + iter = NULL; + if (!parent && ctx->ext) { + while ((iter = lys_getnext_ext(iter, parent, ctx->ext, getnext_flags))) { + if (!ly_set_contains(&parent_choices, (void *)iter, NULL) && CHECK_NODE(iter, exclude, name)) { + goto error; + } + + /* we must compare with both the choice and all its nested data-definiition nodes (but not recursively) */ + if (iter->nodetype == LYS_CHOICE) { + iter2 = NULL; + while ((iter2 = lys_getnext_ext(iter2, iter, NULL, 0))) { + if (CHECK_NODE(iter2, exclude, name)) { + goto error; + } + } + } + } + } else { + while ((iter = lys_getnext(iter, parent, ctx->cur_mod->compiled, getnext_flags))) { + if (!ly_set_contains(&parent_choices, (void *)iter, NULL) && CHECK_NODE(iter, exclude, name)) { + goto error; + } + + /* we must compare with both the choice and all its nested data-definiition nodes (but not recursively) */ + if (iter->nodetype == LYS_CHOICE) { + iter2 = NULL; + while ((iter2 = lys_getnext(iter2, iter, NULL, 0))) { + if (CHECK_NODE(iter2, exclude, name)) { + goto error; + } + } + } + } + + actions = parent ? lysc_node_actions(parent) : ctx->cur_mod->compiled->rpcs; + LY_LIST_FOR((struct lysc_node *)actions, iter) { + if (CHECK_NODE(iter, exclude, name)) { + goto error; + } + } + + notifs = parent ? lysc_node_notifs(parent) : ctx->cur_mod->compiled->notifs; + LY_LIST_FOR((struct lysc_node *)notifs, iter) { + if (CHECK_NODE(iter, exclude, name)) { + goto error; + } + } + } + ly_set_erase(&parent_choices, NULL); + return LY_SUCCESS; + +error: + ly_set_erase(&parent_choices, NULL); + LOGVAL(ctx->ctx, LY_VCODE_DUPIDENT, name, "data definition/RPC/action/notification"); + return LY_EEXIST; + +#undef CHECK_NODE +} + +LY_ERR +lys_compile_node_connect(struct lysc_ctx *ctx, struct lysc_node *parent, struct lysc_node *node) +{ + struct lysc_node **children, *anchor = NULL; + int insert_after = 0; + + node->parent = parent; + + if (parent) { + if (node->nodetype == LYS_INPUT) { + assert(parent->nodetype & (LYS_ACTION | LYS_RPC)); + /* input node is part of the action but link it with output */ + node->next = &((struct lysc_node_action *)parent)->output.node; + node->prev = node->next; + return LY_SUCCESS; + } else if (node->nodetype == LYS_OUTPUT) { + /* output node is part of the action but link it with input */ + node->next = NULL; + node->prev = &((struct lysc_node_action *)parent)->input.node; + return LY_SUCCESS; + } else if (node->nodetype == LYS_ACTION) { + children = (struct lysc_node **)lysc_node_actions_p(parent); + } else if (node->nodetype == LYS_NOTIF) { + children = (struct lysc_node **)lysc_node_notifs_p(parent); + } else { + children = lysc_node_child_p(parent); + } + assert(children); + + if (!(*children)) { + /* first child */ + *children = node; + } else if (node->flags & LYS_KEY) { + /* special handling of adding keys */ + assert(node->module == parent->module); + anchor = *children; + if (anchor->flags & LYS_KEY) { + while ((anchor->flags & LYS_KEY) && anchor->next) { + anchor = anchor->next; + } + /* insert after the last key */ + insert_after = 1; + } /* else insert before anchor (at the beginning) */ + } else if ((*children)->prev->module == node->module) { + /* last child is from the same module, keep the order and insert at the end */ + anchor = (*children)->prev; + insert_after = 1; + } else if (parent->module == node->module) { + /* adding module child after some augments were connected */ + for (anchor = *children; anchor->module == node->module; anchor = anchor->next) {} + } else { + /* some augments are already connected and we are connecting new ones, + * keep module name order and insert the node into the children list */ + anchor = *children; + do { + anchor = anchor->prev; + + /* check that we have not found the last augment node from our module or + * the first augment node from a "smaller" module or + * the first node from a local module */ + if ((anchor->module == node->module) || (strcmp(anchor->module->name, node->module->name) < 0) || + (anchor->module == parent->module)) { + /* insert after */ + insert_after = 1; + break; + } + + /* we have traversed all the nodes, insert before anchor (as the first node) */ + } while (anchor->prev->next); + } + + /* insert */ + if (anchor) { + if (insert_after) { + node->next = anchor->next; + node->prev = anchor; + anchor->next = node; + if (node->next) { + /* middle node */ + node->next->prev = node; + } else { + /* last node */ + (*children)->prev = node; + } + } else { + node->next = anchor; + node->prev = anchor->prev; + anchor->prev = node; + if (anchor == *children) { + /* first node */ + *children = node; + } else { + /* middle node */ + node->prev->next = node; + } + } + } + + /* check the name uniqueness (even for an only child, it may be in case) */ + if (lys_compile_node_uniqness(ctx, parent, node->name, node)) { + return LY_EEXIST; + } + } else { + /* top-level element */ + struct lysc_node **list; + + if (ctx->ext) { + lyplg_ext_get_storage_p(ctx->ext, LY_STMT_DATA_NODE_MASK, (const void ***)&list); + } else if (node->nodetype == LYS_RPC) { + list = (struct lysc_node **)&ctx->cur_mod->compiled->rpcs; + } else if (node->nodetype == LYS_NOTIF) { + list = (struct lysc_node **)&ctx->cur_mod->compiled->notifs; + } else { + list = &ctx->cur_mod->compiled->data; + } + if (!(*list)) { + *list = node; + } else { + /* insert at the end of the module's top-level nodes list */ + (*list)->prev->next = node; + node->prev = (*list)->prev; + (*list)->prev = node; + } + + /* check the name uniqueness on top-level */ + if (lys_compile_node_uniqness(ctx, NULL, node->name, node)) { + return LY_EEXIST; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Set config and operation flags for a node. + * + * @param[in] ctx Compile context. + * @param[in] node Compiled node flags to set. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_config(struct lysc_ctx *ctx, struct lysc_node *node) +{ + /* case never has any explicit config */ + assert((node->nodetype != LYS_CASE) || !(node->flags & LYS_CONFIG_MASK)); + + if (ctx->compile_opts & LYS_COMPILE_NO_CONFIG) { + /* ignore config statements inside Notification/RPC/action/... data */ + node->flags &= ~LYS_CONFIG_MASK; + } else if (!(node->flags & LYS_CONFIG_MASK)) { + /* config not explicitly set, inherit it from parent */ + assert(!node->parent || (node->parent->flags & LYS_CONFIG_MASK) || (node->parent->nodetype & LYS_AUGMENT)); + if (node->parent && (node->parent->flags & LYS_CONFIG_MASK)) { + node->flags |= node->parent->flags & LYS_CONFIG_MASK; + } else { + /* default is config true */ + node->flags |= LYS_CONFIG_W; + } + } else { + /* config set explicitly */ + node->flags |= LYS_SET_CONFIG; + } + + if (node->parent && (node->parent->flags & LYS_CONFIG_R) && (node->flags & LYS_CONFIG_W)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Configuration node cannot be child of any state data node."); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Set various flags of the compiled nodes + * + * @param[in] ctx Compile context. + * @param[in] parsed_flags Parsed node flags. + * @param[in] inherited_flags Inherited flags from a schema-only statement. + * @param[in,out] node Compiled node where the flags will be set. + */ +static LY_ERR +lys_compile_node_flags(struct lysc_ctx *ctx, uint16_t parsed_flags, uint16_t inherited_flags, struct lysc_node *node) +{ + uint16_t parent_flags; + const char *parent_name; + + /* copy flags except for status */ + node->flags = (parsed_flags & LYS_FLAGS_COMPILED_MASK) & ~LYS_STATUS_MASK; + + /* inherit config flags */ + LY_CHECK_RET(lys_compile_config(ctx, node)); + + /* compile status */ + parent_flags = node->parent ? node->parent->flags : 0; + parent_name = node->parent ? node->parent->name : NULL; + LY_CHECK_RET(lys_compile_status(ctx, parsed_flags, inherited_flags, parent_flags, parent_name, node->name, &node->flags)); + + /* other flags */ + if ((ctx->compile_opts & LYS_IS_INPUT) && (node->nodetype != LYS_INPUT)) { + node->flags |= LYS_IS_INPUT; + } else if ((ctx->compile_opts & LYS_IS_OUTPUT) && (node->nodetype != LYS_OUTPUT)) { + node->flags |= LYS_IS_OUTPUT; + } else if ((ctx->compile_opts & LYS_IS_NOTIF) && (node->nodetype != LYS_NOTIF)) { + node->flags |= LYS_IS_NOTIF; + } + + return LY_SUCCESS; +} + +static LY_ERR +lys_compile_node_(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *parent, uint16_t inherited_flags, + LY_ERR (*node_compile_spec)(struct lysc_ctx *, struct lysp_node *, struct lysc_node *), + struct lysc_node *node, struct ly_set *child_set) +{ + LY_ERR ret = LY_SUCCESS; + ly_bool not_supported, enabled; + struct lysp_node *dev_pnode = NULL; + struct lysp_when *pwhen = NULL; + uint32_t prev_opts = ctx->compile_opts; + + node->nodetype = pnode->nodetype; + node->module = ctx->cur_mod; + node->parent = parent; + node->prev = node; + node->priv = ctx->ctx->flags & LY_CTX_SET_PRIV_PARSED ? pnode : NULL; + + /* compile any deviations for this node */ + LY_CHECK_GOTO(ret = lys_compile_node_deviations_refines(ctx, pnode, parent, &dev_pnode, ¬_supported), error); + if (not_supported && !(ctx->compile_opts & (LYS_COMPILE_NO_DISABLED | LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING))) { + /* if not supported, keep it just like disabled nodes by if-feature */ + ly_set_add(&ctx->unres->disabled, node, 1, NULL); + ctx->compile_opts |= LYS_COMPILE_DISABLED; + } + if (dev_pnode) { + pnode = dev_pnode; + } + + DUP_STRING_GOTO(ctx->ctx, pnode->name, node->name, ret, error); + DUP_STRING_GOTO(ctx->ctx, pnode->dsc, node->dsc, ret, error); + DUP_STRING_GOTO(ctx->ctx, pnode->ref, node->ref, ret, error); + + /* if-features */ + LY_CHECK_GOTO(ret = lys_eval_iffeatures(ctx->ctx, pnode->iffeatures, &enabled), error); + if (!enabled && !(ctx->compile_opts & (LYS_COMPILE_NO_DISABLED | LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING))) { + ly_set_add(&ctx->unres->disabled, node, 1, NULL); + ctx->compile_opts |= LYS_COMPILE_DISABLED; + } + + /* config, status and other flags */ + LY_CHECK_GOTO(ret = lys_compile_node_flags(ctx, pnode->flags, inherited_flags, node), error); + + /* list ordering */ + if (node->nodetype & (LYS_LIST | LYS_LEAFLIST)) { + if ((node->flags & (LYS_CONFIG_R | LYS_IS_OUTPUT | LYS_IS_NOTIF)) && (node->flags & LYS_ORDBY_MASK)) { + LOGVRB("The ordered-by statement is ignored in lists representing %s (%s).", + (node->flags & LYS_IS_OUTPUT) ? "RPC/action output parameters" : + (ctx->compile_opts & LYS_IS_NOTIF) ? "notification content" : "state data", ctx->path); + node->flags &= ~LYS_ORDBY_MASK; + node->flags |= LYS_ORDBY_SYSTEM; + } else if (!(node->flags & LYS_ORDBY_MASK)) { + /* default ordering is system */ + node->flags |= LYS_ORDBY_SYSTEM; + } + } + + /* insert into parent's children/compiled module (we can no longer free the node separately on error) */ + LY_CHECK_GOTO(ret = lys_compile_node_connect(ctx, parent, node), cleanup); + + if ((pwhen = lysp_node_when(pnode))) { + /* compile when */ + ret = lys_compile_when(ctx, pwhen, pnode->flags, node, lysc_data_node(node), node, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + /* nodetype-specific part */ + LY_CHECK_GOTO(ret = node_compile_spec(ctx, pnode, node), cleanup); + + /* final compilation tasks that require the node to be connected */ + COMPILE_EXTS_GOTO(ctx, pnode->exts, node->exts, node, ret, cleanup); + if (node->flags & LYS_MAND_TRUE) { + /* inherit LYS_MAND_TRUE in parent containers */ + lys_compile_mandatory_parents(parent, 1); + } + + if (child_set) { + /* add the new node into set */ + LY_CHECK_GOTO(ret = ly_set_add(child_set, node, 1, NULL), cleanup); + } + + goto cleanup; + +error: + lysc_node_free(&ctx->free_ctx, node, 0); + +cleanup: + if (ret && dev_pnode) { + LOGVAL(ctx->ctx, LYVE_OTHER, "Compilation of a deviated and/or refined node failed."); + } + ctx->compile_opts = prev_opts; + lysp_dev_node_free(ctx, dev_pnode); + return ret; +} + +LY_ERR +lys_compile_node_action_inout(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_node *child_p; + uint32_t prev_options = ctx->compile_opts; + + struct lysp_node_action_inout *inout_p = (struct lysp_node_action_inout *)pnode; + struct lysc_node_action_inout *inout = (struct lysc_node_action_inout *)node; + + COMPILE_ARRAY_GOTO(ctx, inout_p->musts, inout->musts, lys_compile_must, ret, done); + COMPILE_EXTS_GOTO(ctx, inout_p->exts, inout->exts, inout, ret, done); + ctx->compile_opts |= (inout_p->nodetype == LYS_INPUT) ? LYS_COMPILE_RPC_INPUT : LYS_COMPILE_RPC_OUTPUT; + + LY_LIST_FOR(inout_p->child, child_p) { + LY_CHECK_GOTO(ret = lys_compile_node(ctx, child_p, node, 0, NULL), done); + } + + /* connect any augments */ + LY_CHECK_GOTO(ret = lys_compile_node_augments(ctx, node), done); + + ctx->compile_opts = prev_options; + +done: + return ret; +} + +/** + * @brief Compile parsed action node information. + * + * @param[in] ctx Compile context + * @param[in] pnode Parsed action node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the action-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_action(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + LY_ERR ret; + struct lysp_node_action *action_p = (struct lysp_node_action *)pnode; + struct lysc_node_action *action = (struct lysc_node_action *)node; + struct lysp_node_action_inout *input, implicit_input = { + .nodetype = LYS_INPUT, + .name = "input", + .parent = pnode, + }; + struct lysp_node_action_inout *output, implicit_output = { + .nodetype = LYS_OUTPUT, + .name = "output", + .parent = pnode, + }; + + /* input */ + lysc_update_path(ctx, action->module, "input"); + if (action_p->input.nodetype == LYS_UNKNOWN) { + input = &implicit_input; + } else { + input = &action_p->input; + } + ret = lys_compile_node_(ctx, &input->node, &action->node, 0, lys_compile_node_action_inout, &action->input.node, NULL); + lysc_update_path(ctx, NULL, NULL); + LY_CHECK_GOTO(ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, &action->input.node, &input->node); + LY_CHECK_GOTO(ret, done); + + /* output */ + lysc_update_path(ctx, action->module, "output"); + if (action_p->output.nodetype == LYS_UNKNOWN) { + output = &implicit_output; + } else { + output = &action_p->output; + } + ret = lys_compile_node_(ctx, &output->node, &action->node, 0, lys_compile_node_action_inout, &action->output.node, NULL); + lysc_update_path(ctx, NULL, NULL); + LY_CHECK_GOTO(ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, &action->output.node, &output->node); + LY_CHECK_GOTO(ret, done); + +done: + return ret; +} + +/** + * @brief Compile parsed action node information. + * @param[in] ctx Compile context + * @param[in] pnode Parsed action node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the action-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_notif(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_node_notif *notif_p = (struct lysp_node_notif *)pnode; + struct lysc_node_notif *notif = (struct lysc_node_notif *)node; + struct lysp_node *child_p; + + COMPILE_ARRAY_GOTO(ctx, notif_p->musts, notif->musts, lys_compile_must, ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, node, pnode); + LY_CHECK_GOTO(ret, done); + + LY_LIST_FOR(notif_p->child, child_p) { + ret = lys_compile_node(ctx, child_p, node, 0, NULL); + LY_CHECK_GOTO(ret, done); + } + + /* connect any augments */ + LY_CHECK_GOTO(ret = lys_compile_node_augments(ctx, node), done); + +done: + return ret; +} + +/** + * @brief Compile parsed container node information. + * @param[in] ctx Compile context + * @param[in] pnode Parsed container node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the container-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_container(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + struct lysp_node_container *cont_p = (struct lysp_node_container *)pnode; + struct lysc_node_container *cont = (struct lysc_node_container *)node; + struct lysp_node *child_p; + LY_ERR ret = LY_SUCCESS; + + if (cont_p->presence) { + /* presence container */ + cont->flags |= LYS_PRESENCE; + } + + /* more cases when the container has meaning but is kept NP for convenience: + * - when condition + * - direct child action/notification + */ + + LY_LIST_FOR(cont_p->child, child_p) { + ret = lys_compile_node(ctx, child_p, node, 0, NULL); + LY_CHECK_GOTO(ret, done); + } + + COMPILE_ARRAY_GOTO(ctx, cont_p->musts, cont->musts, lys_compile_must, ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, node, pnode); + LY_CHECK_GOTO(ret, done); + + /* connect any augments */ + LY_CHECK_GOTO(ret = lys_compile_node_augments(ctx, node), done); + + LY_LIST_FOR((struct lysp_node *)cont_p->actions, child_p) { + ret = lys_compile_node(ctx, child_p, node, 0, NULL); + LY_CHECK_GOTO(ret, done); + } + LY_LIST_FOR((struct lysp_node *)cont_p->notifs, child_p) { + ret = lys_compile_node(ctx, child_p, node, 0, NULL); + LY_CHECK_GOTO(ret, done); + } + +done: + return ret; +} + +/** + * @brief Compile type in leaf/leaf-list node and do all the necessary checks. + * @param[in] ctx Compile context. + * @param[in] context_node Schema node where the type/typedef is placed to correctly find the base types. + * @param[in] type_p Parsed type to compile. + * @param[in,out] leaf Compiled leaf structure (possibly cast leaf-list) to provide node information and to store the compiled type information. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_node_type(struct lysc_ctx *ctx, struct lysp_node *context_node, struct lysp_type *type_p, + struct lysc_node_leaf *leaf) +{ + struct lysp_qname *dflt; + struct lysc_type **t; + LY_ARRAY_COUNT_TYPE u, count; + ly_bool in_unres = 0; + + LY_CHECK_RET(lys_compile_type(ctx, context_node, leaf->flags, leaf->name, type_p, &leaf->type, + leaf->units ? NULL : &leaf->units, &dflt)); + + /* store default value, if any */ + if (dflt && !(leaf->flags & LYS_SET_DFLT)) { + LY_CHECK_RET(lysc_unres_leaf_dflt_add(ctx, leaf, dflt)); + } + + /* store leafref(s) to be resolved */ + LY_CHECK_RET(lysc_unres_leafref_add(ctx, leaf, type_p->pmod)); + + /* type-specific checks */ + if (leaf->type->basetype == LY_TYPE_UNION) { + t = ((struct lysc_type_union *)leaf->type)->types; + count = LY_ARRAY_COUNT(t); + } else { + t = &leaf->type; + count = 1; + } + for (u = 0; u < count; ++u) { + if (t[u]->basetype == LY_TYPE_EMPTY) { + if ((leaf->nodetype == LYS_LEAFLIST) && (ctx->pmod->version < LYS_VERSION_1_1)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Leaf-list of type \"empty\" is allowed only in YANG 1.1 modules."); + return LY_EVALID; + } + } else if (!in_unres && ((t[u]->basetype == LY_TYPE_BITS) || (t[u]->basetype == LY_TYPE_ENUM))) { + /* store in unres for all disabled bits/enums to be removed */ + LY_CHECK_RET(lysc_unres_bitenum_add(ctx, leaf)); + in_unres = 1; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Compile parsed leaf node information. + * @param[in] ctx Compile context + * @param[in] pnode Parsed leaf node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the leaf-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_leaf(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + struct lysp_node_leaf *leaf_p = (struct lysp_node_leaf *)pnode; + struct lysc_node_leaf *leaf = (struct lysc_node_leaf *)node; + LY_ERR ret = LY_SUCCESS; + + COMPILE_ARRAY_GOTO(ctx, leaf_p->musts, leaf->musts, lys_compile_must, ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, node, pnode); + LY_CHECK_GOTO(ret, done); + + if (leaf_p->units) { + LY_CHECK_GOTO(ret = lydict_insert(ctx->ctx, leaf_p->units, 0, &leaf->units), done); + leaf->flags |= LYS_SET_UNITS; + } + + /* compile type */ + ret = lys_compile_node_type(ctx, pnode, &leaf_p->type, leaf); + LY_CHECK_GOTO(ret, done); + + /* store/update default value */ + if (leaf_p->dflt.str) { + LY_CHECK_RET(lysc_unres_leaf_dflt_add(ctx, leaf, &leaf_p->dflt)); + leaf->flags |= LYS_SET_DFLT; + } + + /* checks */ + if ((leaf->flags & LYS_SET_DFLT) && (leaf->flags & LYS_MAND_TRUE)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid mandatory leaf with a default value."); + return LY_EVALID; + } + +done: + return ret; +} + +/** + * @brief Compile parsed leaf-list node information. + * @param[in] ctx Compile context + * @param[in] pnode Parsed leaf-list node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the leaf-list-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_leaflist(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + struct lysp_node_leaflist *llist_p = (struct lysp_node_leaflist *)pnode; + struct lysc_node_leaflist *llist = (struct lysc_node_leaflist *)node; + LY_ERR ret = LY_SUCCESS; + + COMPILE_ARRAY_GOTO(ctx, llist_p->musts, llist->musts, lys_compile_must, ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, node, pnode); + LY_CHECK_GOTO(ret, done); + + if (llist_p->units) { + LY_CHECK_GOTO(ret = lydict_insert(ctx->ctx, llist_p->units, 0, &llist->units), done); + llist->flags |= LYS_SET_UNITS; + } + + /* compile type */ + ret = lys_compile_node_type(ctx, pnode, &llist_p->type, (struct lysc_node_leaf *)llist); + LY_CHECK_GOTO(ret, done); + + /* store/update default values */ + if (llist_p->dflts) { + if (ctx->pmod->version < LYS_VERSION_1_1) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Leaf-list default values are allowed only in YANG 1.1 modules."); + return LY_EVALID; + } + + LY_CHECK_GOTO(lysc_unres_llist_dflts_add(ctx, llist, llist_p->dflts), done); + llist->flags |= LYS_SET_DFLT; + } + + llist->min = llist_p->min; + if (llist->min) { + llist->flags |= LYS_MAND_TRUE; + } + llist->max = llist_p->max ? llist_p->max : UINT32_MAX; + + if (llist->flags & LYS_CONFIG_R) { + /* state leaf-list is always ordered-by user */ + llist->flags &= ~LYS_ORDBY_SYSTEM; + llist->flags |= LYS_ORDBY_USER; + } + + /* checks */ + if ((llist->flags & LYS_SET_DFLT) && (llist->flags & LYS_MAND_TRUE)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "The default statement is present on leaf-list with a nonzero min-elements."); + return LY_EVALID; + } + + if (llist->min > llist->max) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Leaf-list min-elements %u is bigger than max-elements %u.", + llist->min, llist->max); + return LY_EVALID; + } + +done: + return ret; +} + +/** + * @brief Find the node according to the given descendant/absolute schema nodeid. + * Used in unique, refine and augment statements. + * + * @param[in] ctx Compile context + * @param[in] nodeid Descendant-schema-nodeid (according to the YANG grammar) + * @param[in] nodeid_len Length of the given nodeid, if it is not NULL-terminated string. + * @param[in] ctx_node Context node for a relative nodeid. + * @param[in] format Format of any prefixes. + * @param[in] prefix_data Format-specific prefix data (see ::ly_resolve_prefix). + * @param[in] nodetype Optional (can be 0) restriction for target's nodetype. If target exists, but does not match + * the given nodetype, LY_EDENIED is returned (and target is provided), but no error message is printed. + * The value can be even an ORed value to allow multiple nodetypes. + * @param[out] target Found target node if any. + * @param[out] result_flag Output parameter to announce if the schema nodeid goes through the action's input/output or a Notification. + * The LYSC_OPT_RPC_INPUT, LYSC_OPT_RPC_OUTPUT and LYSC_OPT_NOTIFICATION are used as flags. + * @return LY_ERR values - LY_ENOTFOUND, LY_EVALID, LY_EDENIED or LY_SUCCESS. + */ +static LY_ERR +lysc_resolve_schema_nodeid(struct lysc_ctx *ctx, const char *nodeid, size_t nodeid_len, const struct lysc_node *ctx_node, + LY_VALUE_FORMAT format, void *prefix_data, uint16_t nodetype, const struct lysc_node **target, uint16_t *result_flag) +{ + LY_ERR ret = LY_EVALID; + const char *name, *prefix, *id; + size_t name_len, prefix_len; + const struct lys_module *mod = NULL; + const char *nodeid_type; + uint32_t getnext_extra_flag = 0; + uint16_t current_nodetype = 0; + + assert(nodeid); + assert(target); + assert(result_flag); + *target = NULL; + *result_flag = 0; + + id = nodeid; + + if (ctx_node) { + /* descendant-schema-nodeid */ + nodeid_type = "descendant"; + + if (*id == '/') { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid descendant-schema-nodeid value \"%.*s\" - absolute-schema-nodeid used.", + (int)(nodeid_len ? nodeid_len : strlen(nodeid)), nodeid); + return LY_EVALID; + } + } else { + /* absolute-schema-nodeid */ + nodeid_type = "absolute"; + + if (*id != '/') { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid absolute-schema-nodeid value \"%.*s\" - missing starting \"/\".", + (int)(nodeid_len ? nodeid_len : strlen(nodeid)), nodeid); + return LY_EVALID; + } + ++id; + } + + while (*id && (ret = ly_parse_nodeid(&id, &prefix, &prefix_len, &name, &name_len)) == LY_SUCCESS) { + if (prefix) { + mod = ly_resolve_prefix(ctx->ctx, prefix, prefix_len, format, prefix_data); + if (!mod) { + /* module must always be found */ + assert(prefix); + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid %s-schema-nodeid value \"%.*s\" - prefix \"%.*s\" not defined in module \"%s\".", + nodeid_type, (int)(id - nodeid), nodeid, (int)prefix_len, prefix, LYSP_MODULE_NAME(ctx->pmod)); + return LY_ENOTFOUND; + } + } else { + switch (format) { + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + /* use the current module */ + mod = ctx->cur_mod; + break; + case LY_VALUE_JSON: + case LY_VALUE_LYB: + if (!ctx_node) { + LOGINT_RET(ctx->ctx); + } + /* inherit the module of the previous context node */ + mod = ctx_node->module; + break; + case LY_VALUE_CANON: + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + /* not really defined */ + LOGINT_RET(ctx->ctx); + } + } + + if (ctx_node && (ctx_node->nodetype & (LYS_RPC | LYS_ACTION))) { + /* move through input/output manually */ + if (mod != ctx_node->module) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid %s-schema-nodeid value \"%.*s\" - target node not found.", + nodeid_type, (int)(id - nodeid), nodeid); + return LY_ENOTFOUND; + } + if (!ly_strncmp("input", name, name_len)) { + ctx_node = &((struct lysc_node_action *)ctx_node)->input.node; + } else if (!ly_strncmp("output", name, name_len)) { + ctx_node = &((struct lysc_node_action *)ctx_node)->output.node; + getnext_extra_flag = LYS_GETNEXT_OUTPUT; + } else { + /* only input or output is valid */ + ctx_node = NULL; + } + } else if (ctx->ext && !ctx_node) { + /* top-level extension nodes */ + ctx_node = lysc_ext_find_node(ctx->ext, mod, name, name_len, 0, LYS_GETNEXT_WITHCHOICE | LYS_GETNEXT_WITHCASE); + } else { + ctx_node = lys_find_child(ctx_node, mod, name, name_len, 0, + getnext_extra_flag | LYS_GETNEXT_WITHCHOICE | LYS_GETNEXT_WITHCASE); + getnext_extra_flag = 0; + } + if (!ctx_node) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid %s-schema-nodeid value \"%.*s\" - target node not found.", + nodeid_type, (int)(id - nodeid), nodeid); + return LY_ENOTFOUND; + } + current_nodetype = ctx_node->nodetype; + + if (current_nodetype == LYS_NOTIF) { + (*result_flag) |= LYS_COMPILE_NOTIFICATION; + } else if (current_nodetype == LYS_INPUT) { + (*result_flag) |= LYS_COMPILE_RPC_INPUT; + } else if (current_nodetype == LYS_OUTPUT) { + (*result_flag) |= LYS_COMPILE_RPC_OUTPUT; + } + + if (!*id || (nodeid_len && ((size_t)(id - nodeid) >= nodeid_len))) { + break; + } + if (*id != '/') { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid %s-schema-nodeid value \"%.*s\" - missing \"/\" as node-identifier separator.", + nodeid_type, (int)(id - nodeid + 1), nodeid); + return LY_EVALID; + } + ++id; + } + + if (ret == LY_SUCCESS) { + *target = ctx_node; + if (nodetype && !(current_nodetype & nodetype)) { + return LY_EDENIED; + } + } else { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Invalid %s-schema-nodeid value \"%.*s\" - unexpected end of expression.", + nodeid_type, (int)(nodeid_len ? nodeid_len : strlen(nodeid)), nodeid); + } + + return ret; +} + +/** + * @brief Compile information about list's uniques. + * @param[in] ctx Compile context. + * @param[in] uniques Sized array list of unique statements. + * @param[in] list Compiled list where the uniques are supposed to be resolved and stored. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_node_list_unique(struct lysc_ctx *ctx, struct lysp_qname *uniques, struct lysc_node_list *list) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_node_leaf **key, ***unique; + struct lysc_node *parent; + const char *keystr, *delim; + size_t len; + LY_ARRAY_COUNT_TYPE v; + int8_t config; /* -1 - not yet seen; 0 - LYS_CONFIG_R; 1 - LYS_CONFIG_W */ + uint16_t flags; + + LY_ARRAY_FOR(uniques, v) { + config = -1; + LY_ARRAY_NEW_RET(ctx->ctx, list->uniques, unique, LY_EMEM); + keystr = uniques[v].str; + while (keystr) { + delim = strpbrk(keystr, " \t\n"); + if (delim) { + len = delim - keystr; + while (isspace(*delim)) { + ++delim; + } + } else { + len = strlen(keystr); + } + + /* unique node must be present */ + LY_ARRAY_NEW_RET(ctx->ctx, *unique, key, LY_EMEM); + ret = lysc_resolve_schema_nodeid(ctx, keystr, len, &list->node, LY_VALUE_SCHEMA, (void *)uniques[v].mod, + LYS_LEAF, (const struct lysc_node **)key, &flags); + if (ret != LY_SUCCESS) { + if (ret == LY_EDENIED) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Unique's descendant-schema-nodeid \"%.*s\" refers to %s node instead of a leaf.", + (int)len, keystr, lys_nodetype2str((*key)->nodetype)); + } + return LY_EVALID; + } else if (flags) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Unique's descendant-schema-nodeid \"%.*s\" refers into %s node.", + (int)len, keystr, flags & LYS_IS_NOTIF ? "notification" : "RPC/action"); + return LY_EVALID; + } + + /* all referenced leafs must be of the same config type */ + if ((config != -1) && ((((*key)->flags & LYS_CONFIG_W) && (config == 0)) || + (((*key)->flags & LYS_CONFIG_R) && (config == 1)))) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Unique statement \"%s\" refers to leaves with different config type.", uniques[v].str); + return LY_EVALID; + } else if ((*key)->flags & LYS_CONFIG_W) { + config = 1; + } else { /* LYS_CONFIG_R */ + config = 0; + } + + /* we forbid referencing nested lists because it is unspecified what instance of such a list to use */ + for (parent = (*key)->parent; parent != (struct lysc_node *)list; parent = parent->parent) { + if (parent->nodetype == LYS_LIST) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Unique statement \"%s\" refers to a leaf in nested list \"%s\".", uniques[v].str, parent->name); + return LY_EVALID; + } + } + + /* check status */ + LY_CHECK_RET(lysc_check_status(ctx, list->flags, uniques[v].mod->mod, list->name, + (*key)->flags, (*key)->module, (*key)->name)); + + /* mark leaf as unique */ + (*key)->flags |= LYS_UNIQUE; + + /* next unique value in line */ + keystr = delim; + } + /* next unique definition */ + } + + return LY_SUCCESS; +} + +/** + * @brief Compile parsed list node information. + * @param[in] ctx Compile context + * @param[in] pnode Parsed list node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the list-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_list(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + struct lysp_node_list *list_p = (struct lysp_node_list *)pnode; + struct lysc_node_list *list = (struct lysc_node_list *)node; + struct lysp_node *child_p; + struct lysc_node *parent; + struct lysc_node_leaf *key, *prev_key = NULL; + size_t len; + const char *keystr, *delim; + LY_ERR ret = LY_SUCCESS; + + list->min = list_p->min; + if (list->min) { + list->flags |= LYS_MAND_TRUE; + } + list->max = list_p->max ? list_p->max : (uint32_t)-1; + + LY_LIST_FOR(list_p->child, child_p) { + LY_CHECK_RET(lys_compile_node(ctx, child_p, node, 0, NULL)); + } + + COMPILE_ARRAY_GOTO(ctx, list_p->musts, list->musts, lys_compile_must, ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, node, pnode); + LY_CHECK_GOTO(ret, done); + + /* keys */ + if (list->flags & LYS_CONFIG_W) { + parent = node; + if (ctx->compile_opts & LYS_COMPILE_GROUPING) { + /* compiling individual grouping, we can check this only if there is an explicit config set */ + while (parent) { + if (parent->flags & LYS_SET_CONFIG) { + break; + } + parent = parent->parent; + } + } + + if (parent && (!list_p->key || !list_p->key[0])) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Missing key in list representing configuration data."); + return LY_EVALID; + } + } + + /* find all the keys (must be direct children) */ + keystr = list_p->key; + if (!keystr) { + /* keyless list */ + list->flags &= ~LYS_ORDBY_SYSTEM; + list->flags |= LYS_KEYLESS | LYS_ORDBY_USER; + } + while (keystr) { + delim = strpbrk(keystr, " \t\n"); + if (delim) { + len = delim - keystr; + while (isspace(*delim)) { + ++delim; + } + } else { + len = strlen(keystr); + } + + /* key node must be present */ + key = (struct lysc_node_leaf *)lys_find_child(node, node->module, keystr, len, LYS_LEAF, LYS_GETNEXT_NOCHOICE); + if (!key) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "The list's key \"%.*s\" not found.", (int)len, keystr); + return LY_EVALID; + } + /* keys must be unique */ + if (key->flags & LYS_KEY) { + /* the node was already marked as a key */ + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Duplicated key identifier \"%.*s\".", (int)len, keystr); + return LY_EVALID; + } + + lysc_update_path(ctx, list->module, key->name); + /* key must have the same config flag as the list itself */ + if ((list->flags & LYS_CONFIG_MASK) != (key->flags & LYS_CONFIG_MASK)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Key of a configuration list must not be a state leaf."); + return LY_EVALID; + } + if (ctx->pmod->version < LYS_VERSION_1_1) { + /* YANG 1.0 denies key to be of empty type */ + if (key->type->basetype == LY_TYPE_EMPTY) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "List's key cannot be of \"empty\" type until it is in YANG 1.1 module."); + return LY_EVALID; + } + } else { + /* when and if-feature are illegal on list keys */ + if (key->when) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "List's key must not have any \"when\" statement."); + return LY_EVALID; + } + /* unable to check if-features but compilation would fail if disabled */ + } + + /* check status */ + LY_CHECK_RET(lysc_check_status(ctx, list->flags, list->module, list->name, key->flags, key->module, key->name)); + + /* ignore default values of the key */ + if (key->dflt) { + key->dflt->realtype->plugin->free(ctx->ctx, key->dflt); + lysc_type_free(&ctx->free_ctx, (struct lysc_type *)key->dflt->realtype); + free(key->dflt); + key->dflt = NULL; + } + /* mark leaf as key */ + key->flags |= LYS_KEY; + + /* move it to the correct position */ + if ((prev_key && ((struct lysc_node *)prev_key != key->prev)) || (!prev_key && key->prev->next)) { + /* fix links in closest previous siblings of the key */ + if (key->next) { + key->next->prev = key->prev; + } else { + /* last child */ + list->child->prev = key->prev; + } + if (key->prev->next) { + key->prev->next = key->next; + } + /* fix links in the key */ + if (prev_key) { + key->prev = &prev_key->node; + key->next = prev_key->next; + } else { + key->prev = list->child->prev; + key->next = list->child; + } + /* fix links in closes future siblings of the key */ + if (prev_key) { + if (prev_key->next) { + prev_key->next->prev = &key->node; + } else { + list->child->prev = &key->node; + } + prev_key->next = &key->node; + } else { + list->child->prev = &key->node; + } + /* fix links in parent */ + if (!key->prev->next) { + list->child = &key->node; + } + } + + /* next key value */ + prev_key = key; + keystr = delim; + lysc_update_path(ctx, NULL, NULL); + } + + /* connect any augments */ + LY_CHECK_GOTO(ret = lys_compile_node_augments(ctx, node), done); + + /* uniques */ + if (list_p->uniques) { + LY_CHECK_RET(lys_compile_node_list_unique(ctx, list_p->uniques, list)); + } + + LY_LIST_FOR((struct lysp_node *)list_p->actions, child_p) { + ret = lys_compile_node(ctx, child_p, node, 0, NULL); + LY_CHECK_GOTO(ret, done); + } + LY_LIST_FOR((struct lysp_node *)list_p->notifs, child_p) { + ret = lys_compile_node(ctx, child_p, node, 0, NULL); + LY_CHECK_GOTO(ret, done); + } + + /* checks */ + if (list->min > list->max) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "List min-elements %u is bigger than max-elements %u.", list->min, list->max); + return LY_EVALID; + } + +done: + return ret; +} + +/** + * @brief Do some checks and set the default choice's case. + * + * Selects (and stores into ::lysc_node_choice#dflt) the default case and set LYS_SET_DFLT flag on it. + * + * @param[in] ctx Compile context. + * @param[in] dflt Name of the default branch. Can even contain a prefix. + * @param[in,out] ch The compiled choice node, its dflt member is filled to point to the default case node of the choice. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_node_choice_dflt(struct lysc_ctx *ctx, struct lysp_qname *dflt, struct lysc_node_choice *ch) +{ + struct lysc_node *iter; + const struct lys_module *mod; + const char *prefix = NULL, *name; + size_t prefix_len = 0; + + /* could use lys_parse_nodeid(), but it checks syntax which is already done in this case by the parsers */ + name = strchr(dflt->str, ':'); + if (name) { + prefix = dflt->str; + prefix_len = name - prefix; + ++name; + } else { + name = dflt->str; + } + if (prefix) { + mod = ly_resolve_prefix(ctx->ctx, prefix, prefix_len, LY_VALUE_SCHEMA, (void *)dflt->mod); + if (!mod) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Default case prefix \"%.*s\" not found " + "in imports of \"%s\".", (int)prefix_len, prefix, LYSP_MODULE_NAME(dflt->mod)); + return LY_EVALID; + } + } else { + mod = ch->module; + } + + ch->dflt = (struct lysc_node_case *)lys_find_child(&ch->node, mod, name, 0, LYS_CASE, LYS_GETNEXT_WITHCASE); + if (!ch->dflt) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Default case \"%s\" not found.", dflt->str); + return LY_EVALID; + } + + /* no mandatory nodes directly under the default case */ + LY_LIST_FOR(ch->dflt->child, iter) { + if (iter->parent != (struct lysc_node *)ch->dflt) { + break; + } + if (iter->flags & LYS_MAND_TRUE) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Mandatory node \"%s\" under the default case \"%s\".", iter->name, dflt->str); + return LY_EVALID; + } + } + + if (ch->flags & LYS_MAND_TRUE) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Invalid mandatory choice with a default case."); + return LY_EVALID; + } + + ch->dflt->flags |= LYS_SET_DFLT; + return LY_SUCCESS; +} + +LY_ERR +lys_compile_node_choice_child(struct lysc_ctx *ctx, struct lysp_node *child_p, struct lysc_node *node, + struct ly_set *child_set) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_node *child_p_next = child_p->next; + struct lysp_node_case *cs_p; + struct lysc_node_case *cs_c; + + if (child_p->nodetype == LYS_CASE) { + /* standard case under choice */ + ret = lys_compile_node(ctx, child_p, node, 0, child_set); + } else { + /* we need the implicit case first, so create a fake parsed (shorthand) case */ + cs_p = calloc(1, sizeof *cs_p); + LY_CHECK_ERR_RET(!cs_p, LOGMEM(ctx->ctx), LY_EMEM); + cs_p->nodetype = LYS_CASE; + DUP_STRING_GOTO(ctx->ctx, child_p->name, cs_p->name, ret, revert_sh_case); + cs_p->child = child_p; + + /* make the child the only case child */ + child_p->next = NULL; + + /* compile it normally */ + LY_CHECK_GOTO(ret = lys_compile_node(ctx, (struct lysp_node *)cs_p, node, 0, child_set), revert_sh_case); + + if (((struct lysc_node_choice *)node)->cases) { + /* find our case node */ + cs_c = (struct lysc_node_case *)((struct lysc_node_choice *)node)->cases; + while (cs_c->name != cs_p->name) { + cs_c = (struct lysc_node_case *)cs_c->next; + assert(cs_c); + } + + if (ctx->ctx->flags & LY_CTX_SET_PRIV_PARSED) { + /* compiled case node cannot point to his corresponding parsed node + * because it exists temporarily so it must be set to NULL + */ + assert(cs_c->priv == cs_p); + cs_c->priv = NULL; + } + + /* status is copied from his child and not from his parent as usual. */ + if (cs_c->child) { + cs_c->flags &= ~LYS_STATUS_MASK; + cs_c->flags |= (LYS_STATUS_MASK & cs_c->child->flags); + } + } /* else it was removed by a deviation */ + +revert_sh_case: + /* free the parsed shorthand case and correct pointers back */ + cs_p->child = NULL; + lysp_node_free(&ctx->free_ctx, (struct lysp_node *)cs_p); + child_p->next = child_p_next; + } + + return ret; +} + +/** + * @brief Compile parsed choice node information. + * + * @param[in] ctx Compile context + * @param[in] pnode Parsed choice node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the choice-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_choice(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + struct lysp_node_choice *ch_p = (struct lysp_node_choice *)pnode; + struct lysc_node_choice *ch = (struct lysc_node_choice *)node; + struct lysp_node *child_p; + LY_ERR ret = LY_SUCCESS; + + assert(node->nodetype == LYS_CHOICE); + + LY_LIST_FOR(ch_p->child, child_p) { + LY_CHECK_GOTO(ret = lys_compile_node_choice_child(ctx, child_p, node, NULL), done); + } + + /* connect any augments */ + LY_CHECK_GOTO(ret = lys_compile_node_augments(ctx, node), done); + + /* default branch */ + if (ch_p->dflt.str) { + LY_CHECK_GOTO(ret = lys_compile_node_choice_dflt(ctx, &ch_p->dflt, ch), done); + } + +done: + return ret; +} + +/** + * @brief Compile parsed anydata or anyxml node information. + * + * @param[in] ctx Compile context + * @param[in] pnode Parsed anydata or anyxml node. + * @param[in,out] node Pre-prepared structure from lys_compile_node() with filled generic node information + * is enriched with the any-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_node_any(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + struct lysp_node_anydata *any_p = (struct lysp_node_anydata *)pnode; + struct lysc_node_anydata *any = (struct lysc_node_anydata *)node; + LY_ERR ret = LY_SUCCESS; + + COMPILE_ARRAY_GOTO(ctx, any_p->musts, any->musts, lys_compile_must, ret, done); + + /* add must(s) to unres */ + ret = lysc_unres_must_add(ctx, node, pnode); + LY_CHECK_GOTO(ret, done); + + if (any->flags & LYS_CONFIG_W) { + LOGVRB("Use of %s to define configuration data is not recommended. %s", + lyplg_ext_stmt2str(any->nodetype == LYS_ANYDATA ? LY_STMT_ANYDATA : LY_STMT_ANYXML), ctx->path); + } +done: + return ret; +} + +/** + * @brief Prepare the case structure in choice node for the new data node. + * + * It is able to handle implicit as well as explicit cases and the situation when the case has multiple data nodes and the case was already + * created in the choice when the first child was processed. + * + * @param[in] ctx Compile context. + * @param[in] pnode Node image from the parsed tree. If the case is explicit, it is the LYS_CASE node, but in case of implicit case, + * it is the LYS_CHOICE, LYS_AUGMENT or LYS_GROUPING node. + * @param[in] ch The compiled choice structure where the new case structures are created (if needed). + * @param[in] child The new data node being part of a case (no matter if explicit or implicit). + * @return The case structure where the child node belongs to, NULL in case of error. Note that the child is not connected into the siblings list, + * it is linked from the case structure only in case it is its first child. + */ +static LY_ERR +lys_compile_node_case(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node) +{ + LY_ERR ret = LY_SUCCESS; + struct lysp_node *child_p; + struct lysp_node_case *cs_p = (struct lysp_node_case *)pnode; + + if (pnode->nodetype & (LYS_CHOICE | LYS_AUGMENT | LYS_GROUPING)) { + /* we have to add an implicit case node into the parent choice */ + } else if (pnode->nodetype == LYS_CASE) { + /* explicit parent case */ + LY_LIST_FOR(cs_p->child, child_p) { + LY_CHECK_GOTO(ret = lys_compile_node(ctx, child_p, node, 0, NULL), done); + } + } else { + LOGINT_RET(ctx->ctx); + } + + /* connect any augments */ + LY_CHECK_GOTO(ret = lys_compile_node_augments(ctx, node), done); + +done: + return ret; +} + +void +lys_compile_mandatory_parents(struct lysc_node *parent, ly_bool add) +{ + const struct lysc_node *iter; + + if (add) { /* set flag */ + for ( ; parent && parent->nodetype == LYS_CONTAINER && !(parent->flags & LYS_MAND_TRUE) && !(parent->flags & LYS_PRESENCE); + parent = parent->parent) { + parent->flags |= LYS_MAND_TRUE; + } + } else { /* unset flag */ + for ( ; parent && parent->nodetype == LYS_CONTAINER && (parent->flags & LYS_MAND_TRUE); parent = parent->parent) { + for (iter = lysc_node_child(parent); iter; iter = iter->next) { + if (iter->flags & LYS_MAND_TRUE) { + /* there is another mandatory node */ + return; + } + } + /* unset mandatory flag - there is no mandatory children in the non-presence container */ + parent->flags &= ~LYS_MAND_TRUE; + } + } +} + +/** + * @brief Get the grouping with the specified name from given groupings sized array. + * + * @param[in] node Linked list of nodes with groupings. + * @param[in] name Name of the grouping to find, + * @return NULL when there is no grouping with the specified name + * @return Pointer to the grouping of the specified @p name. + */ +static struct lysp_node_grp * +match_grouping(const struct lysp_node_grp *node, const char *name) +{ + LY_LIST_FOR(node, node) { + if ((node->nodetype == LYS_GROUPING) && !strcmp(node->name, name)) { + return (struct lysp_node_grp *)node; + } + } + + return NULL; +} + +/** + * @brief Find grouping for a uses. + * + * @param[in] ctx Compile context. + * @param[in] uses_p Parsed uses node. + * @param[out] gpr_p Found grouping on success. + * @param[out] grp_pmod Module of @p grp_p on success. + * @return LY_ERR value. + */ +static LY_ERR +lys_compile_uses_find_grouping(struct lysc_ctx *ctx, struct lysp_node_uses *uses_p, struct lysp_node_grp **grp_p, + struct lysp_module **grp_pmod) +{ + struct lysp_node *pnode; + struct lysp_node_grp *grp; + const struct lysp_node_grp *ext_grp; + LY_ARRAY_COUNT_TYPE u; + const char *id, *name, *prefix, *local_pref; + size_t prefix_len, name_len; + struct lysp_module *pmod, *found = NULL; + const struct lys_module *mod; + + *grp_p = NULL; + *grp_pmod = NULL; + + /* search for the grouping definition */ + id = uses_p->name; + LY_CHECK_RET(ly_parse_nodeid(&id, &prefix, &prefix_len, &name, &name_len), LY_EVALID); + local_pref = ctx->pmod->is_submod ? ((struct lysp_submodule *)ctx->pmod)->prefix : ctx->pmod->mod->prefix; + if (!prefix || !ly_strncmp(local_pref, prefix, prefix_len)) { + /* current module, search local groupings first */ + pmod = ctx->pmod->mod->parsed; /* make sure that we will start in main_module, not submodule */ + for (pnode = uses_p->parent; !found && pnode; pnode = pnode->parent) { + if ((grp = match_grouping(lysp_node_groupings(pnode), name))) { + found = ctx->pmod; + break; + } + } + + /* if in an extension, search possible groupings in it */ + if (!found && ctx->ext) { + lyplg_ext_parsed_get_storage(ctx->ext, LY_STMT_GROUPING, sizeof ext_grp, (const void **)&ext_grp); + if ((grp = match_grouping(ext_grp, name))) { + found = ctx->pmod; + } + } + } else { + /* foreign module, find it first */ + mod = ly_resolve_prefix(ctx->ctx, prefix, prefix_len, LY_VALUE_SCHEMA, ctx->pmod); + if (!mod) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid prefix used for grouping \"%s\" reference.", uses_p->name); + return LY_EVALID; + } + pmod = mod->parsed; + } + + if (!found) { + /* search in top-level groupings of the main module ... */ + if ((grp = match_grouping(pmod->groupings, name))) { + found = pmod; + } else { + /* ... and all the submodules */ + LY_ARRAY_FOR(pmod->includes, u) { + if ((grp = match_grouping(pmod->includes[u].submodule->groupings, name))) { + found = (struct lysp_module *)pmod->includes[u].submodule; + break; + } + } + } + } + if (!found) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Grouping \"%s\" referenced by a uses statement not found.", uses_p->name); + return LY_EVALID; + } + + if (!(ctx->compile_opts & LYS_COMPILE_GROUPING)) { + /* remember that the grouping is instantiated to avoid its standalone validation */ + grp->flags |= LYS_USED_GRP; + } + + *grp_p = grp; + *grp_pmod = found; + return LY_SUCCESS; +} + +/** + * @brief Compile uses grouping children. + * + * @param[in] ctx Compile context. + * @param[in] uses_p Parsed uses. + * @param[in] inherited_flags Inherited flags from the uses. + * @param[in] child First grouping child to compile. + * @param[in] grp_mod Grouping parsed module. + * @param[in] parent Uses compiled parent, may be NULL if top-level. + * @param[in,out] child_set Set of all compiled child nodes. + * @param[in] child_unres_disabled Whether the children are to be put into unres disabled set or not. + * @return LY_SUCCESS on success. + * @return LY_EVALID on failure. + */ +static LY_ERR +lys_compile_uses_children(struct lysc_ctx *ctx, struct lysp_node_uses *uses_p, uint16_t inherited_flags, + struct lysp_node *child, struct lysp_module *grp_mod, struct lysc_node *parent, struct ly_set *child_set, + ly_bool child_unres_disabled) +{ + LY_ERR rc = LY_SUCCESS; + struct lysp_module *mod_old = ctx->pmod; + uint32_t child_i, opt_prev = ctx->compile_opts; + ly_bool enabled; + struct lysp_node *pnode; + struct lysc_node *node; + struct lysc_when *when_shared = NULL; + + assert(child_set); + + child_i = child_set->count; + LY_LIST_FOR(child, pnode) { + /* compile the nodes with their parsed (grouping) module */ + ctx->pmod = grp_mod; + LY_CHECK_GOTO(rc = lys_compile_node(ctx, pnode, parent, inherited_flags, child_set), cleanup); + + /* eval if-features again for the rest of this node processing */ + LY_CHECK_GOTO(rc = lys_eval_iffeatures(ctx->ctx, pnode->iffeatures, &enabled), cleanup); + if (!enabled && !(ctx->compile_opts & (LYS_COMPILE_NO_DISABLED | LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING))) { + ctx->compile_opts |= LYS_COMPILE_DISABLED; + } + + /* restore the parsed module */ + ctx->pmod = mod_old; + + /* since the uses node is not present in the compiled tree, we need to pass some of its + * statements to all its children */ + while (child_i < child_set->count) { + node = child_set->snodes[child_i]; + + if (uses_p->when) { + /* pass uses when to all the children */ + rc = lys_compile_when(ctx, uses_p->when, inherited_flags, parent, lysc_data_node(parent), node, &when_shared); + LY_CHECK_GOTO(rc, cleanup); + } + + if (child_unres_disabled) { + /* child is disabled by the uses if-features */ + ly_set_add(&ctx->unres->disabled, node, 1, NULL); + } + + /* child processed */ + ++child_i; + } + + /* next iter */ + ctx->compile_opts = opt_prev; + } + +cleanup: + ctx->compile_opts = opt_prev; + return rc; +} + +/** + * @brief Compile parsed uses statement - resolve target grouping and connect its content into parent. + * If present, also apply uses's modificators. + * + * @param[in] ctx Compile context + * @param[in] uses_p Parsed uses schema node. + * @param[in] parent Compiled parent node where the content of the referenced grouping is supposed to be connected. It is + * NULL for top-level nodes, in such a case the module where the node will be connected is taken from + * the compile context. + * @param[in] inherited_flags Inherited flags from a schema-only statement. + * @param[in] child_set Optional set of all the compiled children. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +static LY_ERR +lys_compile_uses(struct lysc_ctx *ctx, struct lysp_node_uses *uses_p, struct lysc_node *parent, uint16_t inherited_flags, + struct ly_set *child_set) +{ + LY_ERR rc = LY_SUCCESS; + ly_bool enabled, child_unres_disabled = 0; + uint32_t i, grp_stack_count, opt_prev = ctx->compile_opts; + struct lysp_node_grp *grp = NULL; + uint16_t uses_flags = 0; + struct lysp_module *grp_mod; + struct ly_set uses_child_set = {0}; + + /* find the referenced grouping */ + LY_CHECK_RET(lys_compile_uses_find_grouping(ctx, uses_p, &grp, &grp_mod)); + + /* grouping must not reference themselves - stack in ctx maintains list of groupings currently being applied */ + grp_stack_count = ctx->groupings.count; + LY_CHECK_RET(ly_set_add(&ctx->groupings, (void *)grp, 0, NULL)); + if (grp_stack_count == ctx->groupings.count) { + /* the target grouping is already in the stack, so we are already inside it -> circular dependency */ + LOGVAL(ctx->ctx, LYVE_REFERENCE, + "Grouping \"%s\" references itself through a uses statement.", grp->name); + return LY_EVALID; + } + + /* nodetype checks */ + if (grp->actions && (parent && !lysc_node_actions_p(parent))) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid child %s \"%s\" of uses parent %s \"%s\" node.", + grp->actions->name, lys_nodetype2str(grp->actions->nodetype), + parent->name, lys_nodetype2str(parent->nodetype)); + rc = LY_EVALID; + goto cleanup; + } + if (grp->notifs && (parent && !lysc_node_notifs_p(parent))) { + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid child %s \"%s\" of uses parent %s \"%s\" node.", + grp->notifs->name, lys_nodetype2str(grp->notifs->nodetype), + parent->name, lys_nodetype2str(parent->nodetype)); + rc = LY_EVALID; + goto cleanup; + } + + /* check status */ + rc = lysc_check_status(ctx, uses_p->flags, ctx->pmod, uses_p->name, grp->flags, grp_mod, grp->name); + LY_CHECK_GOTO(rc, cleanup); + + /* compile any augments and refines so they can be applied during the grouping nodes compilation */ + rc = lys_precompile_uses_augments_refines(ctx, uses_p, parent); + LY_CHECK_GOTO(rc, cleanup); + + /* compile special uses status flags */ + rc = lys_compile_status(ctx, uses_p->flags, inherited_flags, parent ? parent->flags : 0, + parent ? parent->name : NULL, "", &uses_flags); + LY_CHECK_GOTO(rc, cleanup); + + /* uses if-features */ + LY_CHECK_GOTO(rc = lys_eval_iffeatures(ctx->ctx, uses_p->iffeatures, &enabled), cleanup); + if (!enabled && !(ctx->compile_opts & (LYS_COMPILE_NO_DISABLED | LYS_COMPILE_DISABLED | LYS_COMPILE_GROUPING))) { + ctx->compile_opts |= LYS_COMPILE_DISABLED; + child_unres_disabled = 1; + } + + /* uses grouping children */ + rc = lys_compile_uses_children(ctx, uses_p, uses_flags, grp->child, grp_mod, parent, + child_set ? child_set : &uses_child_set, child_unres_disabled); + LY_CHECK_GOTO(rc, cleanup); + + /* uses grouping RPCs/actions */ + rc = lys_compile_uses_children(ctx, uses_p, uses_flags, (struct lysp_node *)grp->actions, grp_mod, parent, + child_set ? child_set : &uses_child_set, child_unres_disabled); + LY_CHECK_GOTO(rc, cleanup); + + /* uses grouping notifications */ + rc = lys_compile_uses_children(ctx, uses_p, uses_flags, (struct lysp_node *)grp->notifs, grp_mod, parent, + child_set ? child_set : &uses_child_set, child_unres_disabled); + LY_CHECK_GOTO(rc, cleanup); + + /* check that all augments were applied */ + for (i = 0; i < ctx->uses_augs.count; ++i) { + if (((struct lysc_augment *)ctx->uses_augs.objs[i])->aug_p->parent != (struct lysp_node *)uses_p) { + /* augment of some parent uses, irrelevant now */ + continue; + } + + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Augment target node \"%s\" in grouping \"%s\" was not found.", + ((struct lysc_augment *)ctx->uses_augs.objs[i])->nodeid->expr, grp->name); + rc = LY_ENOTFOUND; + } + LY_CHECK_GOTO(rc, cleanup); + + /* check that all refines were applied */ + for (i = 0; i < ctx->uses_rfns.count; ++i) { + if (((struct lysc_refine *)ctx->uses_rfns.objs[i])->uses_p != uses_p) { + /* refine of some parent uses, irrelevant now */ + continue; + } + + LOGVAL(ctx->ctx, LYVE_REFERENCE, "Refine(s) target node \"%s\" in grouping \"%s\" was not found.", + ((struct lysc_refine *)ctx->uses_rfns.objs[i])->nodeid->expr, grp->name); + rc = LY_ENOTFOUND; + } + LY_CHECK_GOTO(rc, cleanup); + + /* compile uses and grouping extensions into the parent */ + COMPILE_EXTS_GOTO(ctx, uses_p->exts, parent->exts, parent, rc, cleanup); + COMPILE_EXTS_GOTO(ctx, grp->exts, parent->exts, parent, rc, cleanup); + +cleanup: + /* restore previous context */ + ctx->compile_opts = opt_prev; + + /* remove the grouping from the stack for circular groupings dependency check */ + ly_set_rm_index(&ctx->groupings, ctx->groupings.count - 1, NULL); + assert(ctx->groupings.count == grp_stack_count); + + ly_set_erase(&uses_child_set, NULL); + return rc; +} + +static int +lys_compile_grouping_pathlog(struct lysc_ctx *ctx, struct lysp_node *node, char **path) +{ + struct lysp_node *iter; + int len = 0; + + *path = NULL; + for (iter = node; iter && len >= 0; iter = iter->parent) { + char *s = *path; + char *id; + + switch (iter->nodetype) { + case LYS_USES: + LY_CHECK_RET(asprintf(&id, "{uses='%s'}", iter->name) == -1, -1); + break; + case LYS_GROUPING: + LY_CHECK_RET(asprintf(&id, "{grouping='%s'}", iter->name) == -1, -1); + break; + case LYS_AUGMENT: + LY_CHECK_RET(asprintf(&id, "{augment='%s'}", iter->name) == -1, -1); + break; + default: + id = strdup(iter->name); + break; + } + + if (!iter->parent) { + /* print prefix */ + len = asprintf(path, "/%s:%s%s", ctx->cur_mod->name, id, s ? s : ""); + } else { + /* prefix is the same as in parent */ + len = asprintf(path, "/%s%s", id, s ? s : ""); + } + free(s); + free(id); + } + + if (len < 0) { + free(*path); + *path = NULL; + } else if (len == 0) { + *path = strdup("/"); + len = 1; + } + return len; +} + +LY_ERR +lys_compile_grouping(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysp_node_grp *grp) +{ + LY_ERR rc = LY_SUCCESS; + char *path; + int len; + + /* use grouping status to avoid errors */ + struct lysp_node_uses fake_uses = { + .parent = pnode, + .nodetype = LYS_USES, + .flags = grp->flags & LYS_STATUS_MASK, .next = NULL, + .name = grp->name, + .dsc = NULL, .ref = NULL, .when = NULL, .iffeatures = NULL, .exts = NULL, + .refines = NULL, .augments = NULL + }; + struct lysc_node_container fake_container = { + .nodetype = LYS_CONTAINER, + .flags = 0, + .module = ctx->cur_mod, + .parent = NULL, .next = NULL, + .prev = &fake_container.node, + .name = "fake", + .dsc = NULL, .ref = NULL, .exts = NULL, .when = NULL, + .child = NULL, .musts = NULL, .actions = NULL, .notifs = NULL + }; + + /* compile fake container flags */ + LY_CHECK_GOTO(rc = lys_compile_node_flags(ctx, pnode ? pnode->flags : 0, 0, &fake_container.node), cleanup); + + if (grp->parent) { + LOGWRN(ctx->ctx, "Locally scoped grouping \"%s\" not used.", grp->name); + } + + len = lys_compile_grouping_pathlog(ctx, grp->parent, &path); + if (len < 0) { + LOGMEM(ctx->ctx); + return LY_EMEM; + } + strncpy(ctx->path, path, LYSC_CTX_BUFSIZE - 1); + ctx->path_len = (uint32_t)len; + free(path); + + lysc_update_path(ctx, NULL, "{grouping}"); + lysc_update_path(ctx, NULL, grp->name); + rc = lys_compile_uses(ctx, &fake_uses, &fake_container.node, 0, NULL); + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + + ctx->path_len = 1; + ctx->path[1] = '\0'; + +cleanup: + lysc_node_container_free(&ctx->free_ctx, &fake_container); + FREE_ARRAY(&ctx->free_ctx, fake_container.exts, lysc_ext_instance_free); + return rc; +} + +LY_ERR +lys_compile_node(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *parent, uint16_t inherited_flags, + struct ly_set *child_set) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_node *node = NULL; + uint32_t prev_opts = ctx->compile_opts; + + LY_ERR (*node_compile_spec)(struct lysc_ctx *, struct lysp_node *, struct lysc_node *); + + if (pnode->nodetype != LYS_USES) { + lysc_update_path(ctx, parent ? parent->module : NULL, pnode->name); + } else { + lysc_update_path(ctx, NULL, "{uses}"); + lysc_update_path(ctx, NULL, pnode->name); + } + + switch (pnode->nodetype) { + case LYS_CONTAINER: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_container)); + node_compile_spec = lys_compile_node_container; + break; + case LYS_LEAF: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_leaf)); + node_compile_spec = lys_compile_node_leaf; + break; + case LYS_LIST: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_list)); + node_compile_spec = lys_compile_node_list; + break; + case LYS_LEAFLIST: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_leaflist)); + node_compile_spec = lys_compile_node_leaflist; + break; + case LYS_CHOICE: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_choice)); + node_compile_spec = lys_compile_node_choice; + break; + case LYS_CASE: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_case)); + node_compile_spec = lys_compile_node_case; + break; + case LYS_ANYXML: + case LYS_ANYDATA: + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_anydata)); + node_compile_spec = lys_compile_node_any; + break; + case LYS_RPC: + case LYS_ACTION: + if (ctx->compile_opts & (LYS_IS_INPUT | LYS_IS_OUTPUT | LYS_IS_NOTIF)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Action \"%s\" is placed inside %s.", pnode->name, + (ctx->compile_opts & LYS_IS_NOTIF) ? "notification" : "another RPC/action"); + return LY_EVALID; + } + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_action)); + node_compile_spec = lys_compile_node_action; + ctx->compile_opts |= LYS_COMPILE_NO_CONFIG; + break; + case LYS_NOTIF: + if (ctx->compile_opts & (LYS_IS_INPUT | LYS_IS_OUTPUT | LYS_IS_NOTIF)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, + "Notification \"%s\" is placed inside %s.", pnode->name, + (ctx->compile_opts & LYS_IS_NOTIF) ? "another notification" : "RPC/action"); + return LY_EVALID; + } + node = (struct lysc_node *)calloc(1, sizeof(struct lysc_node_notif)); + node_compile_spec = lys_compile_node_notif; + ctx->compile_opts |= LYS_COMPILE_NOTIFICATION; + break; + case LYS_USES: + ret = lys_compile_uses(ctx, (struct lysp_node_uses *)pnode, parent, inherited_flags, child_set); + lysc_update_path(ctx, NULL, NULL); + lysc_update_path(ctx, NULL, NULL); + return ret; + default: + LOGINT(ctx->ctx); + return LY_EINT; + } + LY_CHECK_ERR_RET(!node, LOGMEM(ctx->ctx), LY_EMEM); + + ret = lys_compile_node_(ctx, pnode, parent, inherited_flags, node_compile_spec, node, child_set); + + ctx->compile_opts = prev_opts; + lysc_update_path(ctx, NULL, NULL); + return ret; +} diff --git a/src/schema_compile_node.h b/src/schema_compile_node.h new file mode 100644 index 0000000..e463de6 --- /dev/null +++ b/src/schema_compile_node.h @@ -0,0 +1,202 @@ +/** + * @file schema_compile_node.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Header for schema compilation of common nodes. + * + * Copyright (c) 2015 - 2022 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_SCHEMA_COMPILE_NODE_H_ +#define LY_SCHEMA_COMPILE_NODE_H_ + +#include +#include + +#include "log.h" +#include "tree.h" +#include "tree_schema.h" + +struct ly_ctx; +struct ly_set; +struct lysc_ctx; + +/** + * @brief Compile information from the when statement by either standard compilation or by reusing + * another compiled when structure. + * + * @param[in] ctx Compile context. + * @param[in] when_p Parsed when structure. + * @param[in] inherited_flags Inherited flags from a schema-only statement. + * @param[in] parent Parent node, if any. + * @param[in] ctx_node Context node for the when statement. + * @param[in] node Compiled node to which to add the compiled when. + * @param[in,out] when_c Optional, pointer to the previously compiled @p when_p to be reused. Set to NULL + * for the first call. + * @return LY_ERR value. + */ +LY_ERR lys_compile_when(struct lysc_ctx *ctx, const struct lysp_when *when_p, uint16_t inherited_flags, + const struct lysc_node *parent, const struct lysc_node *ctx_node, struct lysc_node *node, struct lysc_when **when_c); + +/** + * @brief Compile information from the must statement + * + * @param[in] ctx Compile context. + * @param[in] must_p The parsed must statement structure. + * @param[in,out] must Prepared (empty) compiled must structure to fill. + * @return LY_ERR value. + */ +LY_ERR lys_compile_must(struct lysc_ctx *ctx, const struct lysp_restr *must_p, struct lysc_must *must); + +/** + * @brief Compile the parsed range restriction. + * + * @param[in] ctx Compile context. + * @param[in] range_p Parsed range structure to compile. + * @param[in] basetype Base YANG built-in type of the node with the range restriction. + * @param[in] length_restr Flag to distinguish between range and length restrictions. Only for logging. + * @param[in] frdigits The fraction-digits value in case of LY_TYPE_DEC64 basetype. + * @param[in] base_range Range restriction of the type from which the current type is derived. The current + * range restriction must be more restrictive than the base_range. + * @param[in,out] range Pointer to the created current range structure. + * @return LY_ERR value. + */ +LY_ERR lys_compile_type_range(struct lysc_ctx *ctx, const struct lysp_restr *range_p, LY_DATA_TYPE basetype, + ly_bool length_restr, uint8_t frdigits, struct lysc_range *base_range, struct lysc_range **range); + +/** + * @brief Checks pattern syntax. + * + * @param[in] ctx Context. + * @param[in] pattern Pattern to check. + * @param[in,out] code Compiled PCRE2 pattern. If NULL, the compiled information used to validate pattern are freed. + * @return LY_ERR value - LY_SUCCESS, LY_EMEM, LY_EVALID. + */ +LY_ERR lys_compile_type_pattern_check(struct ly_ctx *ctx, const char *pattern, pcre2_code **code); + +/** + * @brief Compile parsed pattern restriction in conjunction with the patterns from base type. + * + * @param[in] ctx Compile context. + * @param[in] patterns_p Array of parsed patterns from the current type to compile. + * @param[in] base_patterns Compiled patterns from the type from which the current type is derived. + * Patterns from the base type are inherited to have all the patterns that have to match at one place. + * @param[out] patterns Pointer to the storage for the patterns of the current type. + * @return LY_ERR LY_SUCCESS, LY_EMEM, LY_EVALID. + */ +LY_ERR lys_compile_type_patterns(struct lysc_ctx *ctx, const struct lysp_restr *patterns_p, + struct lysc_pattern **base_patterns, struct lysc_pattern ***patterns); + +/** + * @brief Compile parsed type's enum structures (for enumeration and bits types). + * + * @param[in] ctx Compile context. + * @param[in] enums_p Array of the parsed enum structures to compile. + * @param[in] basetype Base YANG built-in type from which the current type is derived. Only LY_TYPE_ENUM and LY_TYPE_BITS are expected. + * @param[in] base_enums Array of the compiled enums information from the (latest) base type to check if the current enums are compatible. + * @param[out] bitenums Newly created array of the compiled bitenums information for the current type. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +LY_ERR lys_compile_type_enums(struct lysc_ctx *ctx, const struct lysp_type_enum *enums_p, LY_DATA_TYPE basetype, + struct lysc_type_bitenum_item *base_enums, struct lysc_type_bitenum_item **bitenums); + +/** + * @brief Compile information about the leaf/leaf-list's type. + * + * @param[in] ctx Compile context. + * @param[in] context_pnode Schema node where the type/typedef is placed to correctly find the base types. + * @param[in] context_flags Flags of the context node or the referencing typedef to correctly check status of referencing and referenced objects. + * @param[in] context_name Name of the context node or referencing typedef for logging. + * @param[in] type_p Parsed type to compile. + * @param[out] type Newly created (or reused with increased refcount) type structure with the filled information about the type. + * @param[out] units Storage for inheriting units value from the typedefs the current type derives from. + * @param[out] dflt Default value for the type. + * @return LY_ERR value. + */ +LY_ERR lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t context_flags, + const char *context_name, const struct lysp_type *type_p, struct lysc_type **type, const char **units, + struct lysp_qname **dflt); + +/** + * @brief Connect the node into the siblings list and check its name uniqueness. Also, + * keep specific order of augments targetting the same node. + * + * @param[in] ctx Compile context + * @param[in] parent Parent node holding the children list, in case of node from a choice's case, + * the choice itself is expected instead of a specific case node. + * @param[in] node Schema node to connect into the list. + * @return LY_ERR value - LY_SUCCESS or LY_EEXIST. + * In case of LY_EEXIST, the node is actually kept in the tree, so do not free it directly. + */ +LY_ERR lys_compile_node_connect(struct lysc_ctx *ctx, struct lysc_node *parent, struct lysc_node *node); + +/** + * @brief Compile parsed action's input/output node information. + * + * @param[in] ctx Compile context + * @param[in] pnode Parsed inout node. + * @param[in,out] node Pre-prepared structure from lys_compile_node_() with filled generic node information + * is enriched with the inout-specific information. + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +LY_ERR lys_compile_node_action_inout(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *node); + +/** + * @brief Compile choice children. + * + * @param[in] ctx Compile context + * @param[in] child_p Parsed choice children nodes. + * @param[in] node Compiled choice node to compile and add children to. + * @param[in,out] child_set Optional set to add all the compiled nodes into (can be more in case of uses). + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +LY_ERR lys_compile_node_choice_child(struct lysc_ctx *ctx, struct lysp_node *child_p, struct lysc_node *node, + struct ly_set *child_set); + +/** + * @brief Set LYS_MAND_TRUE flag for the non-presence container parents. + * + * A non-presence container is mandatory in case it has at least one mandatory children. This function propagate + * the flag to such parents from a mandatory children. + * + * @param[in] parent A schema node to be examined if the mandatory child make it also mandatory. + * @param[in] add Flag to distinguish adding the mandatory flag (new mandatory children appeared) or removing the flag + * (mandatory children was removed). + */ +void lys_compile_mandatory_parents(struct lysc_node *parent, ly_bool add); + +/** + * @brief Validate grouping that was defined but not used in the schema itself. + * + * The grouping does not need to be compiled (and it is compiled here, but the result is forgotten immediately), + * but to have the complete result of the schema validity, even such groupings are supposed to be checked. + * + * @param[in] ctx Compile context. + * @param[in] pnode Parsed parent node of the grouping, NULL for top-level. + * @param[in] grp Parsed grouping node to check. + * @return LY_ERR value. + */ +LY_ERR lys_compile_grouping(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysp_node_grp *grp); + +/** + * @brief Compile parsed schema node information. + * + * @param[in] ctx Compile context + * @param[in] pnode Parsed schema node. + * @param[in] parent Compiled parent node where the current node is supposed to be connected. It is + * NULL for top-level nodes, in such a case the module where the node will be connected is taken from + * the compile context. + * @param[in] inherited_flags Inherited flags from a schema-only statement. + * @param[in,out] child_set Optional set to add all the compiled nodes into (can be more in case of uses). + * @return LY_ERR value - LY_SUCCESS or LY_EVALID. + */ +LY_ERR lys_compile_node(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc_node *parent, uint16_t inherited_flags, + struct ly_set *child_set); + +#endif /* LY_SCHEMA_COMPILE_NODE_H_ */ diff --git a/src/schema_features.c b/src/schema_features.c new file mode 100644 index 0000000..dca998e --- /dev/null +++ b/src/schema_features.c @@ -0,0 +1,714 @@ +/** + * @file schema_features.c + * @author Radek Krejci + * @brief Schema feature handling + * + * Copyright (c) 2015 - 2020 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 + +#include "schema_features.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "log.h" +#include "set.h" +#include "tree.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" + +#define IFF_RECORDS_IN_BYTE 4 +#define IFF_RECORD_BITS 2 +#define IFF_RECORD_MASK 0x3 + +uint8_t +lysc_iff_getop(uint8_t *list, size_t pos) +{ + uint8_t *item; + uint8_t mask = IFF_RECORD_MASK, result; + + item = &list[pos / IFF_RECORDS_IN_BYTE]; + result = (*item) & (mask << IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE)); + return result >> IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE); +} + +static LY_ERR +lysc_iffeature_value_(const struct lysc_iffeature *iff, size_t *index_e, size_t *index_f) +{ + uint8_t op; + LY_ERR a, b; + + op = lysc_iff_getop(iff->expr, *index_e); + (*index_e)++; + + switch (op) { + case LYS_IFF_F: + /* resolve feature */ + return (iff->features[(*index_f)++]->flags & LYS_FENABLED) ? LY_SUCCESS : LY_ENOT; + case LYS_IFF_NOT: + /* invert result */ + return lysc_iffeature_value_(iff, index_e, index_f) == LY_SUCCESS ? LY_ENOT : LY_SUCCESS; + case LYS_IFF_AND: + case LYS_IFF_OR: + a = lysc_iffeature_value_(iff, index_e, index_f); + b = lysc_iffeature_value_(iff, index_e, index_f); + if (op == LYS_IFF_AND) { + if ((a == LY_SUCCESS) && (b == LY_SUCCESS)) { + return LY_SUCCESS; + } else { + return LY_ENOT; + } + } else { /* LYS_IFF_OR */ + if ((a == LY_SUCCESS) || (b == LY_SUCCESS)) { + return LY_SUCCESS; + } else { + return LY_ENOT; + } + } + } + + return LY_ENOT; +} + +LIBYANG_API_DEF LY_ERR +lysc_iffeature_value(const struct lysc_iffeature *iff) +{ + size_t index_e = 0, index_f = 0; + + LY_CHECK_ARG_RET(NULL, iff, LY_EINVAL); + + if (iff->expr) { + return lysc_iffeature_value_(iff, &index_e, &index_f); + } + return LY_ENOT; +} + +LIBYANG_API_DEF LY_ERR +lys_identity_iffeature_value(const struct lysc_ident *ident) +{ + LY_ARRAY_COUNT_TYPE u, v; + ly_bool enabled; + const struct lysp_ident *idents_p, *found_ident = NULL; + struct lysp_include *includes; + + assert(ident); + + /* Search parsed identity in the module. */ + idents_p = ident->module->parsed->identities; + LY_ARRAY_FOR(idents_p, u) { + if (idents_p[u].name == ident->name) { + found_ident = &idents_p[u]; + break; + } + } + + if (!found_ident) { + /* It is not in the module, so it must be in some submodule. */ + includes = ident->module->parsed->includes; + LY_ARRAY_FOR(includes, u) { + idents_p = includes[u].submodule->identities; + LY_ARRAY_FOR(idents_p, v) { + if (idents_p[v].name == ident->name) { + found_ident = &idents_p[v]; + break; + } + } + } + } + + assert(found_ident); + + /* Evaluate its if-feature. */ + LY_CHECK_RET(lys_eval_iffeatures(ident->module->ctx, found_ident->iffeatures, &enabled)); + if (!enabled) { + return LY_ENOT; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF struct lysp_feature * +lysp_feature_next(const struct lysp_feature *last, const struct lysp_module *pmod, uint32_t *idx) +{ + struct lysp_feature *features; + + if (!*idx) { + /* module features */ + features = pmod->features; + } else if ((*idx - 1) < LY_ARRAY_COUNT(pmod->includes)) { + /* submodule features */ + features = pmod->includes[*idx - 1].submodule->features; + } else { + /* no more features */ + return NULL; + } + + /* get the next feature */ + if (features && (!last || (&features[LY_ARRAY_COUNT(features) - 1] != last))) { + return !last ? &features[0] : (struct lysp_feature *)last + 1; + } + + /* no more features in current (sub)module */ + ++(*idx); + return lysp_feature_next(NULL, pmod, idx); +} + +/** + * @brief Find a feature of the given name and referenced in the given module. + * + * @param[in] pmod Module where the feature was referenced (used to resolve prefix of the feature). + * @param[in] name Name of the feature including possible prefix. + * @param[in] len Length of the string representing the feature identifier in the name variable (mandatory!). + * @param[in] prefixed Whether the feature name can be prefixed. + * @return Pointer to the feature structure if found, NULL otherwise. + */ +static struct lysp_feature * +lysp_feature_find(const struct lysp_module *pmod, const char *name, size_t len, ly_bool prefixed) +{ + const struct lys_module *mod; + const char *ptr; + struct lysp_feature *f = NULL; + uint32_t idx = 0; + + assert(pmod); + + if (prefixed && (ptr = ly_strnchr(name, ':', len))) { + /* we have a prefixed feature */ + mod = ly_resolve_prefix(pmod->mod->ctx, name, ptr - name, LY_VALUE_SCHEMA, (void *)pmod); + LY_CHECK_RET(!mod, NULL); + + pmod = mod->parsed; + len = len - (ptr - name) - 1; + name = ptr + 1; + } + + /* feature without prefix, look in main module and all submodules */ + if (pmod->is_submod) { + pmod = pmod->mod->parsed; + } + + /* we have the correct module, get the feature */ + while ((f = lysp_feature_next(f, pmod, &idx))) { + if (!ly_strncmp(f->name, name, len)) { + return f; + } + } + + return NULL; +} + +LIBYANG_API_DEF LY_ERR +lys_feature_value(const struct lys_module *module, const char *feature) +{ + const struct lysp_feature *f; + + LY_CHECK_ARG_RET(NULL, module, module->parsed, feature, LY_EINVAL); + + /* search for the specified feature */ + f = lysp_feature_find(module->parsed, feature, strlen(feature), 0); + LY_CHECK_RET(!f, LY_ENOTFOUND); + + /* feature disabled */ + if (!(f->flags & LYS_FENABLED)) { + return LY_ENOT; + } + + /* feature enabled */ + return LY_SUCCESS; +} + +/** + * @brief Stack for processing if-feature expressions. + */ +struct iff_stack { + size_t size; /**< number of items in the stack */ + size_t index; /**< first empty item */ + uint8_t *stack; /**< stack - array of @ref ifftokens to create the if-feature expression in prefix format */ +}; +#define IFF_STACK_SIZE_STEP 4 + +/** + * @brief Add @ref ifftokens into the stack. + * @param[in] stack The if-feature stack to use. + * @param[in] value One of the @ref ifftokens to store in the stack. + * @return LY_EMEM in case of memory allocation error + * @return LY_ESUCCESS if the value successfully stored. + */ +static LY_ERR +iff_stack_push(struct iff_stack *stack, uint8_t value) +{ + if (stack->index == stack->size) { + stack->size += IFF_STACK_SIZE_STEP; + stack->stack = ly_realloc(stack->stack, stack->size * sizeof *stack->stack); + LY_CHECK_ERR_RET(!stack->stack, LOGMEM(NULL); stack->size = 0, LY_EMEM); + } + stack->stack[stack->index++] = value; + return LY_SUCCESS; +} + +/** + * @brief Get (and remove) the last item form the stack. + * @param[in] stack The if-feature stack to use. + * @return The value from the top of the stack. + */ +static uint8_t +iff_stack_pop(struct iff_stack *stack) +{ + assert(stack && stack->index); + + stack->index--; + return stack->stack[stack->index]; +} + +/** + * @brief Clean up the stack. + * @param[in] stack The if-feature stack to use. + */ +static void +iff_stack_clean(struct iff_stack *stack) +{ + stack->size = 0; + free(stack->stack); +} + +/** + * @brief Store the @ref ifftokens (@p op) on the given position in the 2bits array + * (libyang format of the if-feature expression). + * @param[in,out] list The 2bits array to modify. + * @param[in] op The operand (@ref ifftokens) to store. + * @param[in] pos Position (0-based) where to store the given @p op. + */ +static void +iff_setop(uint8_t *list, uint8_t op, size_t pos) +{ + uint8_t *item; + uint8_t mask = IFF_RECORD_MASK; + + assert(op <= IFF_RECORD_MASK); /* max 2 bits */ + + item = &list[pos / IFF_RECORDS_IN_BYTE]; + mask = mask << IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE); + *item = (*item) & ~mask; + *item = (*item) | (op << IFF_RECORD_BITS * (pos % IFF_RECORDS_IN_BYTE)); +} + +#define LYS_IFF_LP 0x04 /**< Additional, temporary, value of @ref ifftokens: ( */ +#define LYS_IFF_RP 0x08 /**< Additional, temporary, value of @ref ifftokens: ) */ + +static LY_ERR +lys_compile_iffeature(const struct ly_ctx *ctx, const struct lysp_qname *qname, struct lysc_iffeature *iff) +{ + LY_ERR rc = LY_SUCCESS; + const char *c = qname->str; + int64_t i, j; + int8_t op_len, last_not = 0, checkversion = 0; + LY_ARRAY_COUNT_TYPE f_size = 0, expr_size = 0, f_exp = 1; + uint8_t op; + struct iff_stack stack = {0, 0, NULL}; + struct lysp_feature *f; + + assert(c); + + /* pre-parse the expression to get sizes for arrays, also do some syntax checks of the expression */ + for (i = j = 0; c[i]; i++) { + if (c[i] == '(') { + j++; + checkversion = 1; + continue; + } else if (c[i] == ')') { + j--; + continue; + } else if (isspace(c[i])) { + checkversion = 1; + continue; + } + + if (!strncmp(&c[i], "not", op_len = ly_strlen_const("not")) || + !strncmp(&c[i], "and", op_len = ly_strlen_const("and")) || + !strncmp(&c[i], "or", op_len = ly_strlen_const("or"))) { + uint64_t spaces; + + for (spaces = 0; c[i + op_len + spaces] && isspace(c[i + op_len + spaces]); spaces++) {} + if (c[i + op_len + spaces] == '\0') { + LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - unexpected end of expression.", qname->str); + return LY_EVALID; + } else if (!isspace(c[i + op_len])) { + /* feature name starting with the not/and/or */ + last_not = 0; + f_size++; + } else if (c[i] == 'n') { /* not operation */ + if (last_not) { + /* double not */ + expr_size = expr_size - 2; + last_not = 0; + } else { + last_not = 1; + } + } else { /* and, or */ + if (f_exp != f_size) { + LOGVAL(ctx, LYVE_SYNTAX_YANG, + "Invalid value \"%s\" of if-feature - missing feature/expression before \"%.*s\" operation.", + qname->str, op_len, &c[i]); + return LY_EVALID; + } + f_exp++; + + /* not a not operation */ + last_not = 0; + } + i += op_len; + } else { + f_size++; + last_not = 0; + } + expr_size++; + + while (!isspace(c[i])) { + if (!c[i] || (c[i] == ')') || (c[i] == '(')) { + i--; + break; + } + i++; + } + } + if (j) { + /* not matching count of ( and ) */ + LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - non-matching opening and closing parentheses.", + qname->str); + return LY_EVALID; + } + if (f_exp != f_size) { + /* features do not match the needed arguments for the logical operations */ + LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - number of features in expression does not match " + "the required number of operands for the operations.", qname->str); + return LY_EVALID; + } + + if (checkversion || (expr_size > 1)) { + /* check that we have 1.1 module */ + if (qname->mod->version != LYS_VERSION_1_1) { + LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - YANG 1.1 expression in YANG 1.0 module.", + qname->str); + return LY_EVALID; + } + } + + /* allocate the memory */ + LY_ARRAY_CREATE_RET(ctx, iff->features, f_size, LY_EMEM); + iff->expr = calloc((j = (expr_size / IFF_RECORDS_IN_BYTE) + ((expr_size % IFF_RECORDS_IN_BYTE) ? 1 : 0)), sizeof *iff->expr); + stack.stack = malloc(expr_size * sizeof *stack.stack); + LY_CHECK_ERR_GOTO(!stack.stack || !iff->expr, LOGMEM(ctx); rc = LY_EMEM, error); + + stack.size = expr_size; + f_size--; expr_size--; /* used as indexes from now */ + + for (i--; i >= 0; i--) { + if (c[i] == ')') { + /* push it on stack */ + iff_stack_push(&stack, LYS_IFF_RP); + continue; + } else if (c[i] == '(') { + /* pop from the stack into result all operators until ) */ + while ((op = iff_stack_pop(&stack)) != LYS_IFF_RP) { + iff_setop(iff->expr, op, expr_size--); + } + continue; + } else if (isspace(c[i])) { + continue; + } + + /* end of operator or operand -> find beginning and get what is it */ + j = i + 1; + while (i >= 0 && !isspace(c[i]) && c[i] != '(') { + i--; + } + i++; /* go back by one step */ + + if (!strncmp(&c[i], "not", ly_strlen_const("not")) && isspace(c[i + ly_strlen_const("not")])) { + if (stack.index && (stack.stack[stack.index - 1] == LYS_IFF_NOT)) { + /* double not */ + iff_stack_pop(&stack); + } else { + /* not has the highest priority, so do not pop from the stack + * as in case of AND and OR */ + iff_stack_push(&stack, LYS_IFF_NOT); + } + } else if (!strncmp(&c[i], "and", ly_strlen_const("and")) && isspace(c[i + ly_strlen_const("and")])) { + /* as for OR - pop from the stack all operators with the same or higher + * priority and store them to the result, then push the AND to the stack */ + while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_AND) { + op = iff_stack_pop(&stack); + iff_setop(iff->expr, op, expr_size--); + } + iff_stack_push(&stack, LYS_IFF_AND); + } else if (!strncmp(&c[i], "or", 2) && isspace(c[i + 2])) { + while (stack.index && stack.stack[stack.index - 1] <= LYS_IFF_OR) { + op = iff_stack_pop(&stack); + iff_setop(iff->expr, op, expr_size--); + } + iff_stack_push(&stack, LYS_IFF_OR); + } else { + /* feature name, length is j - i */ + + /* add it to the expression */ + iff_setop(iff->expr, LYS_IFF_F, expr_size--); + + /* now get the link to the feature definition */ + f = lysp_feature_find(qname->mod, &c[i], j - i, 1); + if (!f) { + LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - unable to find feature \"%.*s\".", + qname->str, (int)(j - i), &c[i]); + rc = LY_EVALID; + goto error; + } + iff->features[f_size] = f; + LY_ARRAY_INCREMENT(iff->features); + f_size--; + } + } + while (stack.index) { + op = iff_stack_pop(&stack); + iff_setop(iff->expr, op, expr_size--); + } + + if (++expr_size || ++f_size) { + /* not all expected operators and operands found */ + LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - processing error.", qname->str); + rc = LY_EINT; + } else { + rc = LY_SUCCESS; + } + +error: + /* cleanup */ + iff_stack_clean(&stack); + + return rc; +} + +LY_ERR +lys_eval_iffeatures(const struct ly_ctx *ctx, const struct lysp_qname *iffeatures, ly_bool *enabled) +{ + LY_ERR ret; + LY_ARRAY_COUNT_TYPE u; + struct lysc_iffeature iff; + struct lysf_ctx fctx = {.ctx = (struct ly_ctx *)ctx}; + + /* enabled by default */ + *enabled = 1; + + if (!iffeatures) { + return LY_SUCCESS; + } + + /* evaluate all if-feature conditions or until an unsatisfied one is found */ + LY_ARRAY_FOR(iffeatures, u) { + memset(&iff, 0, sizeof iff); + LY_CHECK_RET(lys_compile_iffeature(ctx, &iffeatures[u], &iff)); + + ret = lysc_iffeature_value(&iff); + lysc_iffeature_free(&fctx, &iff); + if (ret == LY_ENOT) { + *enabled = 0; + break; + } else if (ret) { + return ret; + } + } + + return LY_SUCCESS; +} + +LY_ERR +lys_check_features(const struct lysp_module *pmod) +{ + LY_ERR r; + uint32_t i = 0; + struct lysp_feature *f = NULL; + + while ((f = lysp_feature_next(f, pmod, &i))) { + if (!(f->flags & LYS_FENABLED) || !f->iffeatures) { + /* disabled feature or no if-features to check */ + continue; + } + + assert(f->iffeatures_c); + r = lysc_iffeature_value(f->iffeatures_c); + if (r == LY_ENOT) { + LOGERR(pmod->mod->ctx, LY_EDENIED, "Feature \"%s\" cannot be enabled because its \"if-feature\" is not satisfied.", + f->name); + return LY_EDENIED; + } else if (r) { + return r; + } /* else if-feature satisfied */ + } + + return LY_SUCCESS; +} + +LY_ERR +lys_set_features(struct lysp_module *pmod, const char **features) +{ + uint32_t i = 0, j; + struct lysp_feature *f = 0; + ly_bool change = 0; + + if (!features) { + /* do not touch the features */ + + } else if (!features[0]) { + /* disable all the features */ + while ((f = lysp_feature_next(f, pmod, &i))) { + if (f->flags & LYS_FENABLED) { + f->flags &= ~LYS_FENABLED; + change = 1; + } + } + } else if (!strcmp(features[0], "*")) { + /* enable all the features */ + while ((f = lysp_feature_next(f, pmod, &i))) { + if (!(f->flags & LYS_FENABLED)) { + f->flags |= LYS_FENABLED; + change = 1; + } + } + } else { + /* check that all the features exist */ + for (j = 0; features[j]; ++j) { + if (!lysp_feature_find(pmod, features[j], strlen(features[j]), 0)) { + LOGERR(pmod->mod->ctx, LY_EINVAL, "Feature \"%s\" not found in module \"%s\".", features[j], pmod->mod->name); + return LY_EINVAL; + } + } + + /* enable specific features, disable the rest */ + while ((f = lysp_feature_next(f, pmod, &i))) { + for (j = 0; features[j]; ++j) { + if (!strcmp(f->name, features[j])) { + break; + } + } + + if (features[j] && !(f->flags & LYS_FENABLED)) { + /* enable */ + f->flags |= LYS_FENABLED; + change = 1; + } else if (!features[j] && (f->flags & LYS_FENABLED)) { + /* disable */ + f->flags &= ~LYS_FENABLED; + change = 1; + } + } + } + + if (!change) { + /* features already set correctly */ + return LY_EEXIST; + } + + return LY_SUCCESS; +} + +/** + * @brief Check circular dependency of features - feature MUST NOT reference itself (via their if-feature statement). + * + * The function works in the same way as lys_compile_identity_circular_check() with different structures and error messages. + * + * @param[in] ctx Compile context for logging. + * @param[in] feature The feature referenced in if-feature statement (its depfeatures list is being extended by the feature + * being currently processed). + * @param[in] depfeatures The list of depending features of the feature being currently processed (not the one provided as @p feature) + * @return LY_SUCCESS if everything is ok. + * @return LY_EVALID if the feature references indirectly itself. + */ +static LY_ERR +lys_compile_feature_circular_check(const struct ly_ctx *ctx, struct lysp_feature *feature, struct lysp_feature **depfeatures) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + struct ly_set recursion = {0}; + struct lysp_feature *drv; + + if (!depfeatures) { + return LY_SUCCESS; + } + + for (u = 0; u < LY_ARRAY_COUNT(depfeatures); ++u) { + if (feature == depfeatures[u]) { + LOGVAL(ctx, LYVE_REFERENCE, "Feature \"%s\" is indirectly referenced from itself.", feature->name); + ret = LY_EVALID; + goto cleanup; + } + ret = ly_set_add(&recursion, depfeatures[u], 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + + for (v = 0; v < recursion.count; ++v) { + drv = recursion.objs[v]; + for (u = 0; u < LY_ARRAY_COUNT(drv->depfeatures); ++u) { + if (feature == drv->depfeatures[u]) { + LOGVAL(ctx, LYVE_REFERENCE, "Feature \"%s\" is indirectly referenced from itself.", feature->name); + ret = LY_EVALID; + goto cleanup; + } + ly_set_add(&recursion, drv->depfeatures[u], 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + ly_set_erase(&recursion, NULL); + return ret; +} + +LY_ERR +lys_compile_feature_iffeatures(struct lysp_module *pmod) +{ + LY_ARRAY_COUNT_TYPE u, v; + struct lysp_feature *f = NULL, **df; + uint32_t idx = 0; + + while ((f = lysp_feature_next(f, pmod, &idx))) { + if (!f->iffeatures) { + continue; + } + + /* compile if-features */ + LY_ARRAY_CREATE_RET(pmod->mod->ctx, f->iffeatures_c, LY_ARRAY_COUNT(f->iffeatures), LY_EMEM); + LY_ARRAY_FOR(f->iffeatures, u) { + LY_ARRAY_INCREMENT(f->iffeatures_c); + LY_CHECK_RET(lys_compile_iffeature(pmod->mod->ctx, &(f->iffeatures)[u], &(f->iffeatures_c)[u])); + } + LY_ARRAY_FOR(f->iffeatures_c, u) { + LY_ARRAY_FOR(f->iffeatures_c[u].features, v) { + /* check for circular dependency - direct reference first,... */ + if (f == f->iffeatures_c[u].features[v]) { + LOGVAL(pmod->mod->ctx, LYVE_REFERENCE, "Feature \"%s\" is referenced from itself.", f->name); + return LY_EVALID; + } + /* ... and indirect circular reference */ + LY_CHECK_RET(lys_compile_feature_circular_check(pmod->mod->ctx, f->iffeatures_c[u].features[v], f->depfeatures)); + + /* add itself into the dependants list */ + LY_ARRAY_NEW_RET(pmod->mod->ctx, f->iffeatures_c[u].features[v]->depfeatures, df, LY_EMEM); + *df = f; + } + } + } + + return LY_SUCCESS; +} diff --git a/src/schema_features.h b/src/schema_features.h new file mode 100644 index 0000000..c73561f --- /dev/null +++ b/src/schema_features.h @@ -0,0 +1,67 @@ +/** + * @file schema_features.h + * @author Michal Vasko + * @brief Header for schema features. + * + * Copyright (c) 2015 - 2020 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_SCHEMA_FEATURES_H_ +#define LY_SCHEMA_FEATURES_H_ + +#include "log.h" + +struct ly_ctx; +struct lysp_module; +struct lysp_qname; + +/** + * @brief Evaluate if-features array. + * + * @param[in] ctx libyang context. + * @param[in] iffeatures Sized array of if-features to evaluate. + * @param[out] enabled Whether if-features evaluated to true or false. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +LY_ERR lys_eval_iffeatures(const struct ly_ctx *ctx, const struct lysp_qname *iffeatures, ly_bool *enabled); + +/** + * @brief Check whether all enabled features have their if-features satisfied. + * + * @param[in] pmod Parsed module features to check. + * @return LY_SUCCESS on success. + * @return LY_EDENIED if there was an enabled feature with disabled if-feature. + */ +LY_ERR lys_check_features(const struct lysp_module *pmod); + +/** + * @brief Set the specified features of a parsed module ignoring their own if-features. These are all checked before + * compiling the module(s). + * + * @param[in] pmod Parsed module to modify. + * @param[in] features Array of features ended with NULL to be enabled if the module is being implemented. + * The feature string '*' enables all and array of length 1 with only the terminating NULL explicitly disables all + * the features. In case the parameter is NULL, the features are untouched - left disabled in newly loaded module or + * with the current features settings in case the module is already present in the context. + * @return LY_SUCCESS on success. + * @return LY_EEXIST if the specified features were already set. + * @return LY_ERR on error. + */ +LY_ERR lys_set_features(struct lysp_module *pmod, const char **features); + +/** + * @brief Compile if-features of features in the provided module and all its submodules. + * + * @param[in] pmod Parsed module to process. + * @return LY_ERR value. + */ +LY_ERR lys_compile_feature_iffeatures(struct lysp_module *pmod); + +#endif /* LY_SCHEMA_FEATURES_H_ */ diff --git a/src/set.c b/src/set.c new file mode 100644 index 0000000..1b8bfa5 --- /dev/null +++ b/src/set.c @@ -0,0 +1,247 @@ +/** + * @file set.c + * @author Radek Krejci + * @brief Generic set routines implementations + * + * Copyright (c) 2015 - 2018 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "common.h" + +#include +#include +#include + +#include "log.h" +#include "set.h" + +LIBYANG_API_DEF LY_ERR +ly_set_new(struct ly_set **set_p) +{ + LY_CHECK_ARG_RET(NULL, set_p, LY_EINVAL); + + *set_p = calloc(1, sizeof **set_p); + LY_CHECK_ERR_RET(!(*set_p), LOGMEM(NULL), LY_EMEM); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF void +ly_set_clean(struct ly_set *set, void (*destructor)(void *obj)) +{ + uint32_t u; + + if (!set) { + return; + } + + if (destructor) { + for (u = 0; u < set->count; ++u) { + destructor(set->objs[u]); + } + } + set->count = 0; +} + +LIBYANG_API_DEF void +ly_set_erase(struct ly_set *set, void (*destructor)(void *obj)) +{ + if (!set) { + return; + } + + ly_set_clean(set, destructor); + + free(set->objs); + set->size = 0; + set->objs = NULL; +} + +LIBYANG_API_DEF void +ly_set_free(struct ly_set *set, void (*destructor)(void *obj)) +{ + if (!set) { + return; + } + + ly_set_erase(set, destructor); + + free(set); +} + +LIBYANG_API_DEF ly_bool +ly_set_contains(const struct ly_set *set, const void *object, uint32_t *index_p) +{ + LY_CHECK_ARG_RET(NULL, set, 0); + + for (uint32_t i = 0; i < set->count; i++) { + if (set->objs[i] == object) { + /* object found */ + if (index_p) { + *index_p = i; + } + return 1; + } + } + + /* object not found */ + return 0; +} + +LIBYANG_API_DEF LY_ERR +ly_set_dup(const struct ly_set *set, void *(*duplicator)(const void *obj), struct ly_set **newset_p) +{ + struct ly_set *newset; + uint32_t u; + + LY_CHECK_ARG_RET(NULL, set, newset_p, LY_EINVAL); + + newset = calloc(1, sizeof *newset); + LY_CHECK_ERR_RET(!newset, LOGMEM(NULL), LY_EMEM); + if (!set->count) { + *newset_p = newset; + return LY_SUCCESS; + } + + newset->count = set->count; + newset->size = set->count; /* optimize the size */ + newset->objs = malloc(newset->size * sizeof *(newset->objs)); + LY_CHECK_ERR_RET(!newset->objs, LOGMEM(NULL); free(newset), LY_EMEM); + if (duplicator) { + for (u = 0; u < set->count; ++u) { + newset->objs[u] = duplicator(set->objs[u]); + } + } else { + memcpy(newset->objs, set->objs, newset->size * sizeof *(newset->objs)); + } + + *newset_p = newset; + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_set_add(struct ly_set *set, const void *object, ly_bool list, uint32_t *index_p) +{ + void **new; + + LY_CHECK_ARG_RET(NULL, set, LY_EINVAL); + + if (!list) { + /* search for duplication */ + for (uint32_t i = 0; i < set->count; i++) { + if (set->objs[i] == object) { + /* already in set */ + if (index_p) { + *index_p = i; + } + return LY_SUCCESS; + } + } + } + + if (set->size == set->count) { +#define SET_SIZE_STEP 8 + new = realloc(set->objs, (set->size + SET_SIZE_STEP) * sizeof *(set->objs)); + LY_CHECK_ERR_RET(!new, LOGMEM(NULL), LY_EMEM); + set->size += SET_SIZE_STEP; + set->objs = new; +#undef SET_SIZE_STEP + } + + if (index_p) { + *index_p = set->count; + } + set->objs[set->count++] = (void *)object; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_set_merge(struct ly_set *trg, const struct ly_set *src, ly_bool list, void *(*duplicator)(const void *obj)) +{ + uint32_t u; + void *obj; + + LY_CHECK_ARG_RET(NULL, trg, LY_EINVAL); + + if (!src) { + /* nothing to do */ + return LY_SUCCESS; + } + + for (u = 0; u < src->count; ++u) { + if (duplicator) { + obj = duplicator(src->objs[u]); + } else { + obj = src->objs[u]; + } + LY_CHECK_RET(ly_set_add(trg, obj, list, NULL)); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_set_rm_index(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)) +{ + LY_CHECK_ARG_RET(NULL, set, LY_EINVAL); + LY_CHECK_ERR_RET(index >= set->count, LOGARG(NULL, index), LY_EINVAL); + + if (destructor) { + destructor(set->objs[index]); + } + if (index == set->count - 1) { + /* removing last item in set */ + set->objs[index] = NULL; + } else { + /* removing item somewhere in a middle, so put there the last item */ + set->objs[index] = set->objs[set->count - 1]; + set->objs[set->count - 1] = NULL; + } + set->count--; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_set_rm(struct ly_set *set, void *object, void (*destructor)(void *obj)) +{ + uint32_t i; + + LY_CHECK_ARG_RET(NULL, set, object, LY_EINVAL); + + /* get index */ + for (i = 0; i < set->count; i++) { + if (set->objs[i] == object) { + break; + } + } + LY_CHECK_ERR_RET((i == set->count), LOGARG(NULL, object), LY_EINVAL); /* object is not in set */ + + return ly_set_rm_index(set, i, destructor); +} + +LY_ERR +ly_set_rm_index_ordered(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)) +{ + if (destructor) { + destructor(set->objs[index]); + } + set->count--; + if (index == set->count) { + /* removing last item in set */ + set->objs[index] = NULL; + } else { + /* removing item somewhere in a middle, move following items */ + memmove(set->objs + index, set->objs + index + 1, (set->count - index) * sizeof *set->objs); + set->objs[set->count] = NULL; + } + + return LY_SUCCESS; +} diff --git a/src/set.h b/src/set.h new file mode 100644 index 0000000..3f79916 --- /dev/null +++ b/src/set.h @@ -0,0 +1,181 @@ +/** + * @file set.h + * @author Radek Krejci + * @brief Generic set structure and manipulation routines. + * + * Copyright (c) 2015 - 2018 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_SET_H_ +#define LY_SET_H_ + +#include + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup lyset Generic sets + * + * Structure and functions to hold and manipulate with sets of nodes from schema or data trees. + * + * @{ + */ + +/** + * @brief Structure to hold a set of (not necessary somehow connected) objects. Usually used for lyd_node, + * ::lysp_node or ::lysc_node objects, but it is not limited to them. Caller is supposed to not mix the type of objects + * added to the set and according to its knowledge about the set content, it can access objects via the members + * of the set union. + * + * Until ::ly_set_rm() or ::ly_set_rm_index() is used, the set keeps the order of the inserted items as they + * were added into the set, so the first added item is on array index 0. + * + * To free the structure, use ::ly_set_free() function, to manipulate with the structure, use other + * ly_set_* functions. + */ +struct ly_set { + uint32_t size; /**< allocated size of the set array */ + uint32_t count; /**< number of elements in (used size of) the set array */ + + union { + struct lyd_node **dnodes; /**< set array of data nodes */ + struct lysc_node **snodes; /**< set array of schema nodes */ + void **objs; /**< set array of generic object pointers */ + }; +}; + +/** + * @brief Create and initiate new ::ly_set structure. + * + * @param[out] set_p Pointer to store the created ::ly_set structure. + * @return LY_SUCCESS on success. + * @return LY_EINVAL in case of NULL @p set parameter. + * @return LY_EMEM in case of memory allocation failure. + */ +LIBYANG_API_DECL LY_ERR ly_set_new(struct ly_set **set_p); + +/** + * @brief Duplicate the existing set. + * + * @param[in] set Original set to duplicate + * @param[in] duplicator Optional pointer to function that duplicates the objects stored + * in the original set. If not provided, the new set points to the exact same objects as + * the original set. + * @param[out] newset_p Pointer to return the duplication of the original set. + * @return LY_SUCCESS in case the data were successfully duplicated. + * @return LY_EMEM in case of memory allocation failure. + * @return LY_EINVAL in case of invalid parameters. + */ +LIBYANG_API_DECL LY_ERR ly_set_dup(const struct ly_set *set, void *(*duplicator)(const void *obj), struct ly_set **newset_p); + +/** + * @brief Add an object into the set + * + * @param[in] set Set where the \p object will be added. + * @param[in] object Object to be added into the \p set; + * @param[in] list flag to handle set as a list (without checking for (ignoring) duplicit items) + * @param[out] index_p Optional pointer to return index of the added @p object. Usually it is the last index (::ly_set::count - 1), + * but in case the duplicities are checked and the object is already in the set, the @p object is not added and index of the + * already present object is returned. + * @return LY_SUCCESS in case of success + * @return LY_EINVAL in case of invalid input parameters. + * @return LY_EMEM in case of memory allocation failure. + */ +LIBYANG_API_DECL LY_ERR ly_set_add(struct ly_set *set, const void *object, ly_bool list, uint32_t *index_p); + +/** + * @brief Add all objects from \p src to \p trg. + * + * Since it is a set, the function checks for duplicities. + * + * @param[in] trg Target (result) set. + * @param[in] src Source set. + * @param[in] list flag to handle set as a list (without checking for (ignoring) duplicit items) + * @param[in] duplicator Optional pointer to function that duplicates the objects being added + * from \p src into \p trg set. If not provided, the \p trg set will point to the exact same + * objects as the \p src set. + * @return LY_SUCCESS in case of success + * @return LY_EINVAL in case of invalid input parameters. + * @return LY_EMEM in case of memory allocation failure. + */ +LIBYANG_API_DECL LY_ERR ly_set_merge(struct ly_set *trg, const struct ly_set *src, ly_bool list, void *(*duplicator)(const void *obj)); + +/** + * @brief Learn whether the set contains the specified object. + * + * @param[in] set Set to explore. + * @param[in] object Object to be found in the set. + * @param[out] index_p Optional pointer to return index of the searched @p object. + * @return Boolean value whether the @p object was found in the @p set. + */ +LIBYANG_API_DECL ly_bool ly_set_contains(const struct ly_set *set, const void *object, uint32_t *index_p); + +/** + * @brief Remove all objects from the set, but keep the set container for further use. + * + * @param[in] set Set to clean. + * @param[in] destructor Optional function to free the objects in the set. + */ +LIBYANG_API_DECL void ly_set_clean(struct ly_set *set, void (*destructor)(void *obj)); + +/** + * @brief Remove an object from the set. + * + * Note that after removing the object from a set, indexes of other objects in the set can change + * (the last object is placed instead of the removed object). + * + * @param[in] set Set from which the \p node will be removed. + * @param[in] object The object to be removed from the \p set. + * @param[in] destructor Optional function to free the objects being removed. + * @return LY_ERR return value. + */ +LIBYANG_API_DECL LY_ERR ly_set_rm(struct ly_set *set, void *object, void (*destructor)(void *obj)); + +/** + * @brief Remove an object on the specific set index. + * + * Note that after removing the object from a set, indexes of other nodes in the set can change + * (the last object is placed instead of the removed object). + * + * @param[in] set Set from which a node will be removed. + * @param[in] index Index of the object to remove in the \p set. + * @param[in] destructor Optional function to free the objects being removed. + * @return LY_ERR return value. + */ +LIBYANG_API_DECL LY_ERR ly_set_rm_index(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)); + +/** + * @brief Free the ::ly_set data. If the destructor is not provided, it frees only the set structure + * content, not the referred data. + * + * @param[in] set The set to be freed. + * @param[in] destructor Optional function to free the objects in the set. + */ +LIBYANG_API_DECL void ly_set_free(struct ly_set *set, void (*destructor)(void *obj)); + +/** + * @brief Alternative to the ::ly_set_free() for static ::ly_set objects - in contrast to ::ly_set_free() + * it does not free the provided ::ly_set object. + * + * @param[in] set The set to be erased. + * @param[in] destructor Optional function to free the objects in the set. + */ +LIBYANG_API_DECL void ly_set_erase(struct ly_set *set, void (*destructor)(void *obj)); + +/** @} lyset */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_SET_H_ */ diff --git a/src/tree.h b/src/tree.h new file mode 100644 index 0000000..b02b1e1 --- /dev/null +++ b/src/tree.h @@ -0,0 +1,250 @@ +/** + * @file tree.h + * @author Radek Krejci + * @brief libyang generic macros and functions to work with YANG schema or data trees. + * + * Copyright (c) 2019 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_TREE_H_ +#define LY_TREE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @page howtoXPath XPath Addressing + * + * Internally, XPath evaluation is performed on __when__ and __must__ conditions in the schema. For that almost + * a full [XPath 1.0](http://www.w3.org/TR/1999/REC-xpath-19991116/) evaluator was implemented. + * In YANG models you can also find paths identifying __augment__ targets, __leafref__ targets, and trivial paths in + * __choice default__ and __unique__ statements argument. The exact format of all those paths can be found in the + * relevant RFCs. Further will only be discussed paths that are used directly in libyang API functions. + * + * XPath + * ===== + * + * Generally, any xpath argument expects an expression similar to _when_ or _must_ as the same evaluator is used. As for + * the format of any prefixes, the standardized JSON ([RFC 7951](https://tools.ietf.org/html/rfc7951#section-6.11)) + * was used. Summarized, xpath follows these conventions: + * - full XPath can be used, but only data nodes (node sets) will always be returned, + * - as per the specification, prefixes are actually __module names__, + * - also in the specification, for _absolute_ paths, the first (leftmost) node _MUST_ have a prefix, + * - for _relative_ paths, you specify the __context node__, which then acts as a parent for the first node in the path, + * - nodes always inherit their module (prefix) from their __parent node__ so whenever a node is from a different + * module than its parent, it _MUST_ have a prefix, + * - nodes from the same module as their __parent__ _MUST NOT_ have a prefix, + * - note that non-data nodes/schema-only node (choice, case, uses, input, output) are skipped and _MUST_ not be + * included in the path. + * + * Functions List + * -------------- + * - ::lyd_find_xpath() + * - ::lys_find_xpath() + * + * Path + * ==== + * + * The term path is used when a simplified (subset of) XPath is expected. Path is always a valid XPath but not + * the other way around. In short, paths only identify a specific (set of) nodes based on their ancestors in the + * schema. Predicates are allowed the same as for an [instance-identifier](https://tools.ietf.org/html/rfc7950#section-9.13). + * Specifically, key values of a list, leaf-list value, or position of lists without keys can be used. + * + * Examples + * -------- + * + * - get __list__ instance with __key1__ of value __1__ and __key2__ of value __2__ (this can return more __list__ instances if there are more keys than __key1__ and __key2__) + * + * /module-name:container/list[key1='1'][key2='2'] + * + * - get __leaf-list__ instance with the value __val__ + * + * /module-name:container/leaf-list[.='val'] + * + * - get __3rd list-without-keys__ instance with no keys defined + * + * /module-name:container/list-without-keys[3] + * + * - get __aug-list__ with __aug-list-key__, which was added to __module-name__ from an augment module __augment-module__ + * + * /module-name:container/container2/augment-module:aug-cont/aug-list[aug-list-key='value'] + * + * Functions List + * -------------- + * - ::lyd_new_path() + * - ::lyd_new_path2() + * - ::lyd_path() + * - ::lyd_find_path() + * - ::lys_find_path() + * + */ + +/** + * @defgroup trees Trees + * + * Generic macros, functions, etc. to work with both [schema](@ref schematree) and [data](@ref datatree) trees. + * + * @{ + */ + +/** + * @brief Type (i.e. size) of the [sized array](@ref sizedarrays)'s size counter. + * + * To print the value via a print format, use LY_PRI_ARRAY_COUNT_TYPE specifier. + */ +#define LY_ARRAY_COUNT_TYPE uint64_t + +/** + * @brief Printing format specifier macro for LY_ARRAY_SIZE_TYPE values. + */ +#define LY_PRI_ARRAY_COUNT_TYPE PRIu64 + +/** + * @brief Macro selector for other LY_ARRAY_* macros, do not use directly! + */ +#define LY_ARRAY_SELECT(_1, _2, NAME, ...) NAME + +/** + * @brief Helper macro to go through sized-arrays with a pointer iterator. + * + * Use with opening curly bracket (`{`). + * + * @param[in] ARRAY Array to go through + * @param[in] TYPE Type of the records in the ARRAY + * @param[out] ITER Iterating pointer to the item being processed in each loop + */ +#define LY_ARRAY_FOR_ITER(ARRAY, TYPE, ITER) \ + for (ITER = ARRAY; \ + (ARRAY) && ((char *)ITER - (char *)ARRAY)/(sizeof(TYPE)) < (*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)); \ + ITER = (TYPE*)ITER + 1) + +/** + * @brief Helper macro to go through sized-arrays with a numeric iterator. + * + * Use with opening curly bracket (`{`). + * + * The item on the current INDEX in the ARRAY can be accessed in a standard C way as ARRAY[INDEX]. + * + * @param[in] ARRAY Array to go through + * @param[out] INDEX Variable for the iterating index of the item being processed in each loop + */ +#define LY_ARRAY_FOR_INDEX(ARRAY, INDEX) \ + for (INDEX = 0; \ + INDEX < LY_ARRAY_COUNT(ARRAY); \ + ++INDEX) + +/** + * @brief Get the number of records in the ARRAY. + */ +#define LY_ARRAY_COUNT(ARRAY) (ARRAY ? (*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)) : 0) + +/** + * @brief Sized-array iterator (for-loop). + * + * Use with opening curly bracket (`{`). + * + * There are 2 variants: + * + * LY_ARRAY_FOR(ARRAY, TYPE, ITER) + * + * Where ARRAY is a sized-array to go through, TYPE is the type of the items in the ARRAY and ITER is a pointer variable + * providing the items of the ARRAY in the loops. This functionality is provided by LY_ARRAY_FOR_ITER macro + * + * LY_ARRAY_FOR(ARRAY, INDEX) + * + * The ARRAY is again a sized-array to go through, the INDEX is a variable (LY_ARRAY_COUNT_TYPE) for storing iterating ARRAY's index + * to access the items of ARRAY in the loops. This functionality is provided by LY_ARRAY_FOR_INDEX macro. + */ +#define LY_ARRAY_FOR(ARRAY, ...) LY_ARRAY_SELECT(__VA_ARGS__, LY_ARRAY_FOR_ITER, LY_ARRAY_FOR_INDEX, LY_UNDEF)(ARRAY, __VA_ARGS__) + +/** + * @brief Macro to iterate via all sibling elements without affecting the list itself + * + * Works for all types of nodes despite it is data or schema tree, but all the + * parameters must be pointers to the same type. + * + * Use with opening curly bracket (`{`). All parameters must be of the same type. + * + * @param START Pointer to the starting element. + * @param ELEM Iterator. + */ +#define LY_LIST_FOR(START, ELEM) \ + for ((ELEM) = (START); \ + (ELEM); \ + (ELEM) = (ELEM)->next) + +/** + * @brief Macro to iterate via all sibling elements allowing to modify the list itself (e.g. removing elements) + * + * Use with opening curly bracket (`{`). All parameters must be of the same type. + * + * @param START Pointer to the starting element. + * @param NEXT Temporary storage to allow removing of the current iterator content. + * @param ELEM Iterator. + */ +#define LY_LIST_FOR_SAFE(START, NEXT, ELEM) \ + for ((ELEM) = (START); \ + (ELEM) ? (NEXT = (ELEM)->next, 1) : 0; \ + (ELEM) = (NEXT)) + +/** + * @brief YANG built-in types + */ +typedef enum { + LY_TYPE_UNKNOWN = 0, /**< Unknown type */ + LY_TYPE_BINARY, /**< Any binary data ([RFC 6020 sec 9.8](http://tools.ietf.org/html/rfc6020#section-9.8)) */ + LY_TYPE_UINT8, /**< 8-bit unsigned integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_UINT16, /**< 16-bit unsigned integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_UINT32, /**< 32-bit unsigned integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_UINT64, /**< 64-bit unsigned integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_STRING, /**< Human-readable string ([RFC 6020 sec 9.4](http://tools.ietf.org/html/rfc6020#section-9.4)) */ + LY_TYPE_BITS, /**< A set of bits or flags ([RFC 6020 sec 9.7](http://tools.ietf.org/html/rfc6020#section-9.7)) */ + LY_TYPE_BOOL, /**< "true" or "false" ([RFC 6020 sec 9.5](http://tools.ietf.org/html/rfc6020#section-9.5)) */ + LY_TYPE_DEC64, /**< 64-bit signed decimal number ([RFC 6020 sec 9.3](http://tools.ietf.org/html/rfc6020#section-9.3))*/ + LY_TYPE_EMPTY, /**< A leaf that does not have any value ([RFC 6020 sec 9.11](http://tools.ietf.org/html/rfc6020#section-9.11)) */ + LY_TYPE_ENUM, /**< Enumerated strings ([RFC 6020 sec 9.6](http://tools.ietf.org/html/rfc6020#section-9.6)) */ + LY_TYPE_IDENT, /**< A reference to an abstract identity ([RFC 6020 sec 9.10](http://tools.ietf.org/html/rfc6020#section-9.10)) */ + LY_TYPE_INST, /**< References a data tree node ([RFC 6020 sec 9.13](http://tools.ietf.org/html/rfc6020#section-9.13)) */ + LY_TYPE_LEAFREF, /**< A reference to a leaf instance ([RFC 6020 sec 9.9](http://tools.ietf.org/html/rfc6020#section-9.9))*/ + LY_TYPE_UNION, /**< Choice of member types ([RFC 6020 sec 9.12](http://tools.ietf.org/html/rfc6020#section-9.12)) */ + LY_TYPE_INT8, /**< 8-bit signed integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_INT16, /**< 16-bit signed integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_INT32, /**< 32-bit signed integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ + LY_TYPE_INT64 /**< 64-bit signed integer ([RFC 6020 sec 9.2](http://tools.ietf.org/html/rfc6020#section-9.2)) */ +} LY_DATA_TYPE; +#define LY_DATA_TYPE_COUNT 20 /**< Number of different types */ + +/** + * @brief Stringfield YANG built-in data types + */ +extern const char *ly_data_type2str[LY_DATA_TYPE_COUNT]; + +/** + * @brief All kinds of supported value formats and prefix mappings to modules. + */ +typedef enum { + LY_VALUE_CANON, /**< canonical value, prefix mapping is type-specific */ + LY_VALUE_SCHEMA, /**< YANG schema value, prefixes map to YANG import prefixes */ + 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_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; + +/** @} trees */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_TREE_H_ */ diff --git a/src/tree_data.c b/src/tree_data.c new file mode 100644 index 0000000..d6a04ff --- /dev/null +++ b/src/tree_data.c @@ -0,0 +1,2943 @@ +/** + * @file tree_data.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Data tree functions + * + * Copyright (c) 2015 - 2022 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 + +#include "tree_data.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "diff.h" +#include "hash_table.h" +#include "in.h" +#include "in_internal.h" +#include "log.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "path.h" +#include "plugins.h" +#include "plugins_exts/metadata.h" +#include "plugins_internal.h" +#include "plugins_types.h" +#include "set.h" +#include "tree.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "validation.h" +#include "xml.h" +#include "xpath.h" + +static LYD_FORMAT +lyd_parse_get_format(const struct ly_in *in, LYD_FORMAT format) +{ + if (!format && (in->type == LY_IN_FILEPATH)) { + /* unknown format - try to detect it from filename's suffix */ + const char *path = in->method.fpath.filepath; + size_t len = strlen(path); + + /* ignore trailing whitespaces */ + for ( ; len > 0 && isspace(path[len - 1]); len--) {} + + if ((len >= LY_XML_SUFFIX_LEN + 1) && + !strncmp(&path[len - LY_XML_SUFFIX_LEN], LY_XML_SUFFIX, LY_XML_SUFFIX_LEN)) { + format = LYD_XML; + } else if ((len >= LY_JSON_SUFFIX_LEN + 1) && + !strncmp(&path[len - LY_JSON_SUFFIX_LEN], LY_JSON_SUFFIX, LY_JSON_SUFFIX_LEN)) { + format = LYD_JSON; + } 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 still unknown */ + } + + return 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] 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 handle to read the input from. + * @param[in] format Expected format of the data in @p in. + * @param[in] parse_opts Options for parser. + * @param[in] val_opts Options for validation. + * @param[out] op Optional pointer to the parsed operation, if any. + * @return LY_ERR value. + */ +static LY_ERR +lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_in *in, LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op) +{ + LY_ERR rc = LY_SUCCESS; + struct lyd_ctx *lydctx = NULL; + struct ly_set parsed = {0}; + struct lyd_node *first; + uint32_t i, int_opts = 0; + ly_bool subtree_sibling = 0; + + assert(ctx && (parent || first_p)); + + format = lyd_parse_get_format(in, format); + if (first_p) { + *first_p = NULL; + } + + /* remember input position */ + in->func_start = in->current; + + /* set internal options */ + if (!(parse_opts & LYD_PARSE_SUBTREE)) { + int_opts = LYD_INTOPT_WITH_SIBLINGS; + } + + /* parse the data */ + switch (format) { + case LYD_XML: + rc = lyd_parse_xml(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + &subtree_sibling, &lydctx); + break; + case LYD_JSON: + rc = lyd_parse_json(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + &subtree_sibling, &lydctx); + break; + case LYD_LYB: + rc = lyd_parse_lyb(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + &subtree_sibling, &lydctx); + break; + case LYD_UNKNOWN: + LOGARG(ctx, format); + rc = LY_EINVAL; + break; + } + LY_CHECK_GOTO(rc, cleanup); + + if (parent) { + /* get first top-level sibling */ + for (first = parent; first->parent; first = lyd_parent(first)) {} + first = lyd_first_sibling(first); + first_p = &first; + } + + if (!(parse_opts & LYD_PARSE_ONLY)) { + /* validate data */ + rc = lyd_validate(first_p, NULL, ctx, val_opts, 0, &lydctx->node_when, &lydctx->node_types, &lydctx->meta_types, + &lydctx->ext_node, &lydctx->ext_val, NULL); + LY_CHECK_GOTO(rc, cleanup); + } + + /* set the operation node */ + if (op) { + *op = lydctx->op_node; + } + +cleanup: + if (lydctx) { + lydctx->free(lydctx); + } + if (rc) { + if (parent) { + /* free all the parsed subtrees */ + for (i = 0; i < parsed.count; ++i) { + lyd_free_tree(parsed.dnodes[i]); + } + } else { + /* free everything */ + lyd_free_all(*first_p); + *first_p = NULL; + } + } else if (subtree_sibling) { + rc = LY_ENOT; + } + ly_set_erase(&parsed, NULL); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_parse_ext_data(const struct lysc_ext_instance *ext, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree) +{ + const struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + + LY_CHECK_ARG_RET(ctx, ext, in, parent || tree, LY_EINVAL); + LY_CHECK_ARG_RET(ctx, !(parse_options & ~LYD_PARSE_OPTS_MASK), LY_EINVAL); + LY_CHECK_ARG_RET(ctx, !(validate_options & ~LYD_VALIDATE_OPTS_MASK), LY_EINVAL); + + return lyd_parse(ctx, ext, parent, tree, in, format, parse_options, validate_options, NULL); +} + +LIBYANG_API_DEF LY_ERR +lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, + uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree) +{ + LY_CHECK_ARG_RET(ctx, ctx, in, parent || tree, LY_EINVAL); + LY_CHECK_ARG_RET(ctx, !(parse_options & ~LYD_PARSE_OPTS_MASK), LY_EINVAL); + LY_CHECK_ARG_RET(ctx, !(validate_options & ~LYD_VALIDATE_OPTS_MASK), LY_EINVAL); + + return lyd_parse(ctx, NULL, parent, tree, in, format, parse_options, validate_options, NULL); +} + +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) +{ + LY_ERR ret; + struct ly_in *in; + + LY_CHECK_RET(ly_in_new_memory(data, &in)); + 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_fd(const struct ly_ctx *ctx, int fd, 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_fd(fd, &in)); + 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_path(const struct ly_ctx *ctx, const char *path, 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_filepath(path, 0, &in)); + ret = lyd_parse_data(ctx, NULL, in, format, parse_options, validate_options, tree); + + ly_in_free(in, 0); + return ret; +} + +/** + * @brief Parse YANG data into an operation data tree, in case the extension instance is specified, keep the searching + * for schema nodes locked inside the extension instance. + * + * At least one of @p parent, @p tree, or @p op must always be set. + * + * Specific @p data_type values have different parameter meaning as follows: + * - ::LYD_TYPE_RPC_NETCONF: + * - @p parent - must be NULL, the whole RPC is expected; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the RPC/action data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_NOTIF_NETCONF: + * - @p parent - must be NULL, the whole notification is expected; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the notification data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_REPLY_NETCONF: + * - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node; + * - @p format - must be ::LYD_XML, NETCONF supports only this format; + * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be NULL, the reply is appended to the RPC; + * Note that there are 3 kinds of NETCONF replies - ok, error, and data. Only data reply appends any nodes to the RPC. + * + * @param[in] ctx libyang context. + * @param[in] ext Extension instance providing the specific schema tree to match with the data being parsed. + * @param[in] parent Optional parent to connect the parsed nodes to. + * @param[in] in Input handle to read the input from. + * @param[in] format Expected format of the data in @p in. + * @param[in] data_type Expected operation to parse (@ref datatype). + * @param[out] tree Optional full parsed data tree. If @p parent is set, set to NULL. + * @param[out] op Optional parsed operation node. + * @return LY_ERR value. + * @return LY_ENOT if @p data_type is a NETCONF message and the root XML element is not the expected one. + */ +static LY_ERR +lyd_parse_op_(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + struct ly_in *in, LYD_FORMAT format, enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op) +{ + LY_ERR rc = LY_SUCCESS; + struct lyd_ctx *lydctx = NULL; + struct ly_set parsed = {0}; + struct lyd_node *first = NULL, *envp = NULL; + uint32_t i, parse_opts, val_opts, int_opts = 0; + + if (!ctx) { + ctx = LYD_CTX(parent); + } + if (tree) { + *tree = NULL; + } + if (op) { + *op = NULL; + } + + format = lyd_parse_get_format(in, format); + + /* remember input position */ + in->func_start = in->current; + + /* set parse and validation opts */ + parse_opts = LYD_PARSE_ONLY | LYD_PARSE_STRICT; + val_opts = 0; + + switch (data_type) { + + /* special XML NETCONF data types */ + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + LY_CHECK_ARG_RET(ctx, format == LYD_XML, !parent, tree, op, LY_EINVAL); + /* fallthrough */ + case LYD_TYPE_REPLY_NETCONF: + if (data_type == LYD_TYPE_REPLY_NETCONF) { + LY_CHECK_ARG_RET(ctx, format == LYD_XML, parent, parent->schema->nodetype & (LYS_RPC | LYS_ACTION), tree, !op, + LY_EINVAL); + } + + /* parse the NETCONF message */ + rc = lyd_parse_xml_netconf(ctx, ext, parent, &first, in, parse_opts, val_opts, data_type, &envp, &parsed, &lydctx); + if (rc) { + if (envp) { + /* special situation when the envelopes were parsed successfully */ + *tree = envp; + } + goto cleanup; + } + + /* set out params correctly */ + if (envp) { + /* special out param meaning */ + *tree = envp; + } else { + *tree = parent ? NULL : first; + } + if (op) { + *op = lydctx->op_node; + } + goto cleanup; + + /* set internal opts */ + case LYD_TYPE_RPC_YANG: + int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NO_SIBLINGS; + break; + case LYD_TYPE_NOTIF_YANG: + int_opts = LYD_INTOPT_NOTIF | LYD_INTOPT_NO_SIBLINGS; + break; + case LYD_TYPE_REPLY_YANG: + int_opts = LYD_INTOPT_REPLY | LYD_INTOPT_NO_SIBLINGS; + break; + case LYD_TYPE_DATA_YANG: + LOGINT(ctx); + rc = LY_EINT; + goto cleanup; + } + + /* parse the data */ + switch (format) { + case LYD_XML: + rc = lyd_parse_xml(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); + break; + case LYD_JSON: + rc = lyd_parse_json(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); + break; + case LYD_LYB: + rc = lyd_parse_lyb(ctx, ext, parent, &first, in, parse_opts, val_opts, int_opts, &parsed, NULL, &lydctx); + break; + case LYD_UNKNOWN: + LOGARG(ctx, format); + rc = LY_EINVAL; + break; + } + LY_CHECK_GOTO(rc, cleanup); + + /* set out params correctly */ + if (tree) { + *tree = parent ? NULL : first; + } + if (op) { + *op = lydctx->op_node; + } + +cleanup: + if (lydctx) { + lydctx->free(lydctx); + } + if (rc) { + /* free all the parsed nodes */ + if (parsed.count) { + i = parsed.count; + do { + --i; + lyd_free_tree(parsed.dnodes[i]); + } while (i); + } + if (tree && ((format != LYD_XML) || !envp)) { + *tree = NULL; + } + if (op) { + *op = NULL; + } + } + ly_set_erase(&parsed, NULL); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, + enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op) +{ + LY_CHECK_ARG_RET(ctx, ctx || parent, in, data_type, parent || tree || op, LY_EINVAL); + + return lyd_parse_op_(ctx, NULL, parent, in, format, data_type, tree, op); +} + +LIBYANG_API_DEF LY_ERR +lyd_parse_ext_op(const struct lysc_ext_instance *ext, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, + enum lyd_type data_type, struct lyd_node **tree, struct lyd_node **op) +{ + const struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + + LY_CHECK_ARG_RET(ctx, ext, in, data_type, parent || tree || op, LY_EINVAL); + + return lyd_parse_op_(ctx, ext, parent, in, format, data_type, tree, op); +} + +struct lyd_node * +lyd_insert_get_next_anchor(const struct lyd_node *first_sibling, const struct lyd_node *new_node) +{ + const struct lysc_node *schema, *sparent; + struct lyd_node *match = NULL; + ly_bool found; + uint32_t getnext_opts; + + assert(new_node); + + if (!first_sibling || !new_node->schema || (LYD_CTX(first_sibling) != LYD_CTX(new_node))) { + /* insert at the end, no next anchor */ + return NULL; + } + + getnext_opts = 0; + if (new_node->schema->flags & LYS_IS_OUTPUT) { + getnext_opts = LYS_GETNEXT_OUTPUT; + } + + if (first_sibling->parent && first_sibling->parent->schema && first_sibling->parent->children_ht) { + /* find the anchor using hashes */ + sparent = first_sibling->parent->schema; + schema = lys_getnext(new_node->schema, sparent, NULL, getnext_opts); + while (schema) { + /* keep trying to find the first existing instance of the closest following schema sibling, + * otherwise return NULL - inserting at the end */ + if (!lyd_find_sibling_schema(first_sibling, schema, &match)) { + break; + } + + schema = lys_getnext(schema, sparent, NULL, getnext_opts); + } + } else { + /* find the anchor without hashes */ + match = (struct lyd_node *)first_sibling; + sparent = lysc_data_parent(new_node->schema); + if (!sparent) { + /* we are in top-level, skip all the data from preceding modules */ + LY_LIST_FOR(match, match) { + if (!match->schema || (strcmp(lyd_owner_module(match)->name, lyd_owner_module(new_node)->name) >= 0)) { + break; + } + } + } + + /* get the first schema sibling */ + schema = lys_getnext(NULL, sparent, new_node->schema->module->compiled, getnext_opts); + + found = 0; + LY_LIST_FOR(match, match) { + if (!match->schema || (lyd_owner_module(match) != lyd_owner_module(new_node))) { + /* we have found an opaque node, which must be at the end, so use it OR + * modules do not match, so we must have traversed all the data from new_node module (if any), + * we have found the first node of the next module, that is what we want */ + break; + } + + /* skip schema nodes until we find the instantiated one */ + while (!found) { + if (new_node->schema == schema) { + /* we have found the schema of the new node, continue search to find the first + * data node with a different schema (after our schema) */ + found = 1; + break; + } + if (match->schema == schema) { + /* current node (match) is a data node still before the new node, continue search in data */ + break; + } + schema = lys_getnext(schema, sparent, new_node->schema->module->compiled, getnext_opts); + assert(schema); + } + + if (found && (match->schema != new_node->schema)) { + /* find the next node after we have found our node schema data instance */ + break; + } + } + } + + return match; +} + +void +lyd_insert_after_node(struct lyd_node *sibling, struct lyd_node *node) +{ + struct lyd_node_inner *par; + + assert(!node->next && (node->prev == node)); + + node->next = sibling->next; + node->prev = sibling; + sibling->next = node; + if (node->next) { + /* sibling had a succeeding node */ + node->next->prev = node; + } else { + /* sibling was last, find first sibling and change its prev */ + if (sibling->parent) { + sibling = sibling->parent->child; + } else { + for ( ; sibling->prev->next != node; sibling = sibling->prev) {} + } + sibling->prev = node; + } + node->parent = sibling->parent; + + for (par = node->parent; par; par = par->parent) { + if ((par->flags & LYD_DEFAULT) && !(node->flags & LYD_DEFAULT)) { + /* remove default flags from NP containers */ + par->flags &= ~LYD_DEFAULT; + } + } +} + +void +lyd_insert_before_node(struct lyd_node *sibling, struct lyd_node *node) +{ + struct lyd_node_inner *par; + + assert(!node->next && (node->prev == node)); + + node->next = sibling; + /* covers situation of sibling being first */ + node->prev = sibling->prev; + sibling->prev = node; + if (node->prev->next) { + /* sibling had a preceding node */ + node->prev->next = node; + } else if (sibling->parent) { + /* sibling was first and we must also change parent child pointer */ + sibling->parent->child = node; + } + node->parent = sibling->parent; + + for (par = node->parent; par; par = par->parent) { + if ((par->flags & LYD_DEFAULT) && !(node->flags & LYD_DEFAULT)) { + /* remove default flags from NP containers */ + par->flags &= ~LYD_DEFAULT; + } + } +} + +/** + * @brief Insert node as the first and only child of a parent. + * + * Handles inserting into NP containers and key-less lists. + * + * @param[in] parent Parent to insert into. + * @param[in] node Node to insert. + */ +static void +lyd_insert_only_child(struct lyd_node *parent, struct lyd_node *node) +{ + struct lyd_node_inner *par; + + assert(parent && !lyd_child(parent) && !node->next && (node->prev == node)); + assert(!parent->schema || (parent->schema->nodetype & LYD_NODE_INNER)); + + par = (struct lyd_node_inner *)parent; + + par->child = node; + node->parent = par; + + for ( ; par; par = par->parent) { + if ((par->flags & LYD_DEFAULT) && !(node->flags & LYD_DEFAULT)) { + /* remove default flags from NP containers */ + par->flags &= ~LYD_DEFAULT; + } + } +} + +/** + * @brief Learn whether a list instance has all the keys. + * + * @param[in] list List instance to check. + * @return non-zero if all the keys were found, + * @return 0 otherwise. + */ +static int +lyd_insert_has_keys(const struct lyd_node *list) +{ + const struct lyd_node *key; + const struct lysc_node *skey = NULL; + + assert(list->schema->nodetype == LYS_LIST); + key = lyd_child(list); + while ((skey = lys_getnext(skey, list->schema, NULL, 0)) && (skey->flags & LYS_KEY)) { + if (!key || (key->schema != skey)) { + /* key missing */ + return 0; + } + + key = key->next; + } + + /* all keys found */ + return 1; +} + +void +lyd_insert_node(struct lyd_node *parent, struct lyd_node **first_sibling_p, struct lyd_node *node, ly_bool last) +{ + struct lyd_node *anchor, *first_sibling; + + /* inserting list without its keys is not supported */ + assert((parent || first_sibling_p) && node && (node->hash || !node->schema)); + assert(!parent || !parent->schema || + (parent->schema->nodetype & (LYS_CONTAINER | LYS_LIST | LYS_RPC | LYS_ACTION | LYS_NOTIF))); + + if (!parent && first_sibling_p && (*first_sibling_p) && (*first_sibling_p)->parent) { + parent = lyd_parent(*first_sibling_p); + } + + /* get first sibling */ + first_sibling = parent ? lyd_child(parent) : *first_sibling_p; + + if (last || (first_sibling && (first_sibling->flags & LYD_EXT))) { + /* no next anchor */ + anchor = NULL; + } else { + /* find the anchor, our next node, so we can insert before it */ + anchor = lyd_insert_get_next_anchor(first_sibling, node); + } + + if (anchor) { + /* insert before the anchor */ + lyd_insert_before_node(anchor, node); + if (!parent && (*first_sibling_p == anchor)) { + /* move first sibling */ + *first_sibling_p = node; + } + } else if (first_sibling) { + /* insert as the last node */ + lyd_insert_after_node(first_sibling->prev, node); + } else if (parent) { + /* insert as the only child */ + lyd_insert_only_child(parent, node); + } else { + /* insert as the only sibling */ + *first_sibling_p = node; + } + + /* insert into parent HT */ + lyd_insert_hash(node); + + /* finish hashes for our parent, if needed and possible */ + if (node->schema && (node->schema->flags & LYS_KEY) && parent && lyd_insert_has_keys(parent)) { + lyd_hash(parent); + + /* now we can insert even the list into its parent HT */ + lyd_insert_hash(parent); + } +} + +/** + * @brief Check schema place of a node to be inserted. + * + * @param[in] parent Schema node of the parent data node. + * @param[in] sibling Schema node of a sibling data node. + * @param[in] schema Schema node if the data node to be inserted. + * @return LY_SUCCESS on success. + * @return LY_EINVAL if the place is invalid. + */ +static LY_ERR +lyd_insert_check_schema(const struct lysc_node *parent, const struct lysc_node *sibling, const struct lysc_node *schema) +{ + const struct lysc_node *par2; + + assert(!parent || !(parent->nodetype & (LYS_CASE | LYS_CHOICE))); + assert(!sibling || !(sibling->nodetype & (LYS_CASE | LYS_CHOICE))); + assert(!schema || !(schema->nodetype & (LYS_CASE | LYS_CHOICE))); + + if (!schema || (!parent && !sibling)) { + /* opaque nodes can be inserted wherever */ + return LY_SUCCESS; + } + + if (!parent) { + parent = lysc_data_parent(sibling); + } + + /* find schema parent */ + par2 = lysc_data_parent(schema); + + if (parent) { + /* inner node */ + if (par2 != parent) { + LOGERR(schema->module->ctx, LY_EINVAL, "Cannot insert, parent of \"%s\" is not \"%s\".", schema->name, + parent->name); + return LY_EINVAL; + } + } else { + /* top-level node */ + if (par2) { + LOGERR(schema->module->ctx, LY_EINVAL, "Cannot insert, node \"%s\" is not top-level.", schema->name); + return LY_EINVAL; + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_insert_child(struct lyd_node *parent, struct lyd_node *node) +{ + struct lyd_node *iter; + + LY_CHECK_ARG_RET(NULL, parent, node, !parent->schema || (parent->schema->nodetype & LYD_NODE_INNER), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(LYD_CTX(parent), LYD_CTX(node), LY_EINVAL); + + LY_CHECK_RET(lyd_insert_check_schema(parent->schema, NULL, node->schema)); + + if (node->schema && (node->schema->flags & LYS_KEY)) { + LOGERR(LYD_CTX(parent), LY_EINVAL, "Cannot insert key \"%s\".", node->schema->name); + return LY_EINVAL; + } + + if (node->parent || node->prev->next) { + lyd_unlink_tree(node); + } + + while (node) { + iter = node->next; + lyd_unlink_tree(node); + lyd_insert_node(parent, NULL, node, 0); + node = iter; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyplg_ext_insert(struct lyd_node *parent, struct lyd_node *first) +{ + struct lyd_node *iter; + + LY_CHECK_ARG_RET(NULL, parent, first, !first->parent, !first->prev->next, + !parent->schema || (parent->schema->nodetype & LYD_NODE_INNER), LY_EINVAL); + + if (first->schema && (first->schema->flags & LYS_KEY)) { + LOGERR(LYD_CTX(parent), LY_EINVAL, "Cannot insert key \"%s\".", first->schema->name); + return LY_EINVAL; + } + + while (first) { + iter = first->next; + lyd_unlink_tree(first); + lyd_insert_node(parent, NULL, first, 1); + first = iter; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_insert_sibling(struct lyd_node *sibling, struct lyd_node *node, struct lyd_node **first) +{ + struct lyd_node *iter; + + LY_CHECK_ARG_RET(NULL, node, LY_EINVAL); + + if (sibling) { + LY_CHECK_RET(lyd_insert_check_schema(NULL, sibling->schema, node->schema)); + } + + if (sibling == node) { + /* we need to keep the connection to siblings so we can insert into them */ + sibling = sibling->prev; + } + + if (node->parent || node->prev->next) { + lyd_unlink_tree(node); + } + + while (node) { + if (lysc_is_key(node->schema)) { + LOGERR(LYD_CTX(node), LY_EINVAL, "Cannot insert key \"%s\".", node->schema->name); + return LY_EINVAL; + } + + iter = node->next; + lyd_unlink_tree(node); + lyd_insert_node(NULL, &sibling, node, 0); + node = iter; + } + + if (first) { + /* find the first sibling */ + *first = sibling; + while ((*first)->prev->next) { + *first = (*first)->prev; + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_insert_before(struct lyd_node *sibling, struct lyd_node *node) +{ + LY_CHECK_ARG_RET(NULL, sibling, node, sibling != node, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(LYD_CTX(sibling), LYD_CTX(node), LY_EINVAL); + + LY_CHECK_RET(lyd_insert_check_schema(NULL, sibling->schema, node->schema)); + + if (node->schema && (!(node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) || !(node->schema->flags & LYS_ORDBY_USER))) { + LOGERR(LYD_CTX(sibling), LY_EINVAL, "Can be used only for user-ordered nodes."); + return LY_EINVAL; + } + if (node->schema && sibling->schema && (node->schema != sibling->schema)) { + LOGERR(LYD_CTX(sibling), LY_EINVAL, "Cannot insert before a different schema node instance."); + return LY_EINVAL; + } + + lyd_unlink_tree(node); + lyd_insert_before_node(sibling, node); + lyd_insert_hash(node); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_insert_after(struct lyd_node *sibling, struct lyd_node *node) +{ + LY_CHECK_ARG_RET(NULL, sibling, node, sibling != node, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(LYD_CTX(sibling), LYD_CTX(node), LY_EINVAL); + + LY_CHECK_RET(lyd_insert_check_schema(NULL, sibling->schema, node->schema)); + + if (node->schema && (!(node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) || !(node->schema->flags & LYS_ORDBY_USER))) { + LOGERR(LYD_CTX(sibling), LY_EINVAL, "Can be used only for user-ordered nodes."); + return LY_EINVAL; + } + if (node->schema && sibling->schema && (node->schema != sibling->schema)) { + LOGERR(LYD_CTX(sibling), LY_EINVAL, "Cannot insert after a different schema node instance."); + return LY_EINVAL; + } + + lyd_unlink_tree(node); + lyd_insert_after_node(sibling, node); + lyd_insert_hash(node); + + return LY_SUCCESS; +} + +LIBYANG_API_DEF void +lyd_unlink_siblings(struct lyd_node *node) +{ + struct lyd_node *next, *elem, *first = NULL; + + LY_LIST_FOR_SAFE(node, next, elem) { + lyd_unlink_tree(elem); + lyd_insert_node(NULL, &first, elem, 1); + } +} + +LIBYANG_API_DEF void +lyd_unlink_tree(struct lyd_node *node) +{ + struct lyd_node *iter; + + if (!node) { + return; + } + + /* update hashes while still linked into the tree */ + lyd_unlink_hash(node); + + /* unlink from siblings */ + if (node->prev->next) { + node->prev->next = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } else { + /* unlinking the last node */ + if (node->parent) { + iter = node->parent->child; + } else { + iter = node->prev; + while (iter->prev != node) { + iter = iter->prev; + } + } + /* update the "last" pointer from the first node */ + iter->prev = node->prev; + } + + /* unlink from parent */ + if (node->parent) { + if (node->parent->child == node) { + /* the node is the first child */ + node->parent->child = node->next; + } + + /* check for NP container whether its last non-default node is not being unlinked */ + lyd_cont_set_dflt(lyd_parent(node)); + + node->parent = NULL; + } + + node->next = NULL; + node->prev = node; +} + +void +lyd_insert_meta(struct lyd_node *parent, struct lyd_meta *meta, ly_bool clear_dflt) +{ + struct lyd_meta *last, *iter; + + assert(parent); + + if (!meta) { + return; + } + + for (iter = meta; iter; iter = iter->next) { + iter->parent = parent; + } + + /* insert as the last attribute */ + if (parent->meta) { + for (last = parent->meta; last->next; last = last->next) {} + last->next = meta; + } else { + parent->meta = meta; + } + + /* remove default flags from NP containers */ + while (clear_dflt && parent && (parent->schema->nodetype == LYS_CONTAINER) && (parent->flags & LYD_DEFAULT)) { + parent->flags &= ~LYD_DEFAULT; + parent = lyd_parent(parent); + } +} + +LY_ERR +lyd_create_meta(struct lyd_node *parent, struct lyd_meta **meta, const struct lys_module *mod, const char *name, + size_t name_len, const char *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, + void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, ly_bool clear_dflt, ly_bool *incomplete) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_ext_instance *ant = NULL; + const struct lysc_type *ant_type; + struct lyd_meta *mt, *last; + LY_ARRAY_COUNT_TYPE u; + + assert((parent || meta) && mod); + + LY_ARRAY_FOR(mod->compiled->exts, u) { + if (!strncmp(mod->compiled->exts[u].def->plugin->id, "ly2 metadata", 12) && + !ly_strncmp(mod->compiled->exts[u].argument, name, name_len)) { + /* we have the annotation definition */ + ant = &mod->compiled->exts[u]; + break; + } + } + if (!ant) { + /* attribute is not defined as a metadata annotation (RFC 7952) */ + LOGVAL(mod->ctx, LYVE_REFERENCE, "Annotation definition for attribute \"%s:%.*s\" not found.", + mod->name, (int)name_len, name); + ret = LY_EINVAL; + goto cleanup; + } + + mt = calloc(1, sizeof *mt); + LY_CHECK_ERR_GOTO(!mt, LOGMEM(mod->ctx); ret = LY_EMEM, cleanup); + mt->parent = parent; + mt->annotation = ant; + lyplg_ext_get_storage(ant, LY_STMT_TYPE, sizeof ant_type, (const void **)&ant_type); + ret = lyd_value_store(mod->ctx, &mt->value, ant_type, value, value_len, dynamic, format, prefix_data, hints, + ctx_node, incomplete); + LY_CHECK_ERR_GOTO(ret, free(mt), cleanup); + ret = lydict_insert(mod->ctx, name, name_len, &mt->name); + LY_CHECK_ERR_GOTO(ret, free(mt), cleanup); + + /* insert as the last attribute */ + if (parent) { + lyd_insert_meta(parent, mt, clear_dflt); + } else if (*meta) { + for (last = *meta; last->next; last = last->next) {} + last->next = mt; + } + + if (meta) { + *meta = mt; + } + +cleanup: + return ret; +} + +void +lyd_insert_attr(struct lyd_node *parent, struct lyd_attr *attr) +{ + struct lyd_attr *last, *iter; + struct lyd_node_opaq *opaq; + + assert(parent && !parent->schema); + + if (!attr) { + return; + } + + opaq = (struct lyd_node_opaq *)parent; + for (iter = attr; iter; iter = iter->next) { + iter->parent = opaq; + } + + /* insert as the last attribute */ + if (opaq->attr) { + for (last = opaq->attr; last->next; last = last->next) {} + last->next = attr; + } else { + opaq->attr = attr; + } +} + +LY_ERR +lyd_create_attr(struct lyd_node *parent, struct lyd_attr **attr, const struct ly_ctx *ctx, const char *name, size_t name_len, + const char *prefix, size_t prefix_len, const char *module_key, size_t module_key_len, const char *value, + size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *val_prefix_data, uint32_t hints) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_attr *at, *last; + + assert(ctx && (parent || attr) && (!parent || !parent->schema)); + assert(name && name_len && format); + + if (!value_len && (!dynamic || !*dynamic)) { + value = ""; + } + + at = calloc(1, sizeof *at); + LY_CHECK_ERR_RET(!at, LOGMEM(ctx); ly_free_prefix_data(format, val_prefix_data), LY_EMEM); + + LY_CHECK_GOTO(ret = lydict_insert(ctx, name, name_len, &at->name.name), finish); + if (prefix_len) { + LY_CHECK_GOTO(ret = lydict_insert(ctx, prefix, prefix_len, &at->name.prefix), finish); + } + if (module_key_len) { + LY_CHECK_GOTO(ret = lydict_insert(ctx, module_key, module_key_len, &at->name.module_ns), finish); + } + + if (dynamic && *dynamic) { + ret = lydict_insert_zc(ctx, (char *)value, &at->value); + LY_CHECK_GOTO(ret, finish); + *dynamic = 0; + } else { + LY_CHECK_GOTO(ret = lydict_insert(ctx, value, value_len, &at->value), finish); + } + at->format = format; + at->val_prefix_data = val_prefix_data; + at->hints = hints; + + /* insert as the last attribute */ + if (parent) { + lyd_insert_attr(parent, at); + } else if (*attr) { + for (last = *attr; last->next; last = last->next) {} + last->next = at; + } + +finish: + if (ret) { + lyd_free_attr_single(ctx, at); + } else if (attr) { + *attr = at; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF const struct lyd_node_term * +lyd_target(const struct ly_path *path, const struct lyd_node *tree) +{ + struct lyd_node *target; + + if (ly_path_eval(path, tree, &target)) { + return NULL; + } + + return (struct lyd_node_term *)target; +} + +/** + * @brief Check the equality of the two schemas from different contexts. + * + * @param schema1 of first node. + * @param schema2 of second node. + * @return 1 if the schemas are equal otherwise 0. + */ +static ly_bool +lyd_compare_schema_equal(const struct lysc_node *schema1, const struct lysc_node *schema2) +{ + if (!schema1 && !schema2) { + return 1; + } else if (!schema1 || !schema2) { + return 0; + } + + assert(schema1->module->ctx != schema2->module->ctx); + + if (schema1->nodetype != schema2->nodetype) { + return 0; + } + + if (strcmp(schema1->name, schema2->name)) { + return 0; + } + + if (strcmp(schema1->module->name, schema2->module->name)) { + return 0; + } + + if (schema1->module->revision || schema2->module->revision) { + if (!schema1->module->revision || !schema2->module->revision) { + return 0; + } + if (strcmp(schema1->module->revision, schema2->module->revision)) { + return 0; + } + } + + return 1; +} + +/** + * @brief Check the equality of the schemas for all parent nodes. + * + * Both nodes must be from different contexts. + * + * @param node1 Data of first node. + * @param node2 Data of second node. + * @return 1 if the all related parental schemas are equal otherwise 0. + */ +static ly_bool +lyd_compare_schema_parents_equal(const struct lyd_node *node1, const struct lyd_node *node2) +{ + const struct lysc_node *parent1, *parent2; + + assert(node1 && node2); + + for (parent1 = node1->schema->parent, parent2 = node2->schema->parent; + parent1 && parent2; + parent1 = parent1->parent, parent2 = parent2->parent) { + if (!lyd_compare_schema_equal(parent1, parent2)) { + return 0; + } + } + + if (parent1 || parent2) { + return 0; + } + + return 1; +} + +/** + * @brief Compare 2 nodes values including opaque node values. + * + * @param[in] node1 First node to compare. + * @param[in] node2 Second node to compare. + * @return LY_SUCCESS if equal. + * @return LY_ENOT if not equal. + * @return LY_ERR on error. + */ +static LY_ERR +lyd_compare_single_value(const struct lyd_node *node1, const struct lyd_node *node2) +{ + const struct lyd_node_opaq *opaq1 = NULL, *opaq2 = NULL; + const char *val1, *val2, *col; + const struct lys_module *mod; + char *val_dyn = NULL; + LY_ERR rc = LY_SUCCESS; + + if (!node1->schema) { + opaq1 = (struct lyd_node_opaq *)node1; + } + if (!node2->schema) { + opaq2 = (struct lyd_node_opaq *)node2; + } + + if (opaq1 && opaq2 && (opaq1->format == LY_VALUE_XML) && (opaq2->format == LY_VALUE_XML)) { + /* opaque XML and opaque XML node */ + if (lyxml_value_compare(LYD_CTX(node1), opaq1->value, opaq1->val_prefix_data, LYD_CTX(node2), opaq2->value, + opaq2->val_prefix_data)) { + return LY_ENOT; + } + return LY_SUCCESS; + } + + /* get their values */ + if (opaq1 && ((opaq1->format == LY_VALUE_XML) || (opaq1->format == LY_VALUE_STR_NS)) && (col = strchr(opaq1->value, ':'))) { + /* XML value with a prefix, try to transform it into a JSON (canonical) value */ + mod = ly_resolve_prefix(LYD_CTX(node1), opaq1->value, col - opaq1->value, opaq1->format, opaq1->val_prefix_data); + if (!mod) { + /* unable to compare */ + return LY_ENOT; + } + + if (asprintf(&val_dyn, "%s%s", mod->name, col) == -1) { + LOGMEM(LYD_CTX(node1)); + return LY_EMEM; + } + val1 = val_dyn; + } else { + val1 = lyd_get_value(node1); + } + if (opaq2 && ((opaq2->format == LY_VALUE_XML) || (opaq2->format == LY_VALUE_STR_NS)) && (col = strchr(opaq2->value, ':'))) { + mod = ly_resolve_prefix(LYD_CTX(node2), opaq2->value, col - opaq2->value, opaq2->format, opaq2->val_prefix_data); + if (!mod) { + return LY_ENOT; + } + + assert(!val_dyn); + if (asprintf(&val_dyn, "%s%s", mod->name, col) == -1) { + LOGMEM(LYD_CTX(node2)); + return LY_EMEM; + } + val2 = val_dyn; + } else { + val2 = lyd_get_value(node2); + } + + /* compare values */ + if (strcmp(val1, val2)) { + rc = LY_ENOT; + } + + free(val_dyn); + return rc; +} + +/** + * @brief Internal implementation of @ref lyd_compare_single. + * @copydoc lyd_compare_single + * @param[in] parental_schemas_checked Flag used for optimization. + * When this function is called for the first time, the flag must be set to 0. + * The @ref lyd_compare_schema_parents_equal should be called only once during + * recursive calls, and this is accomplished by setting to 1 in the lyd_compare_single_ body. + */ +static LY_ERR +lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options, + ly_bool parental_schemas_checked) +{ + const struct lyd_node *iter1, *iter2; + struct lyd_node_any *any1, *any2; + int len1, len2; + LY_ERR r; + + if (!node1 || !node2) { + if (node1 == node2) { + return LY_SUCCESS; + } else { + return LY_ENOT; + } + } + + if (LYD_CTX(node1) == LYD_CTX(node2)) { + /* same contexts */ + if (options & LYD_COMPARE_OPAQ) { + if (lyd_node_schema(node1) != lyd_node_schema(node2)) { + return LY_ENOT; + } + } else { + if (node1->schema != node2->schema) { + return LY_ENOT; + } + } + } else { + /* different contexts */ + if (!lyd_compare_schema_equal(node1->schema, node2->schema)) { + return LY_ENOT; + } + if (!parental_schemas_checked) { + if (!lyd_compare_schema_parents_equal(node1, node2)) { + return LY_ENOT; + } + parental_schemas_checked = 1; + } + } + + if (!(options & LYD_COMPARE_OPAQ) && (node1->hash != node2->hash)) { + return LY_ENOT; + } + /* equal hashes do not mean equal nodes, they can be just in collision so the nodes must be checked explicitly */ + + if (!node1->schema || !node2->schema) { + if (!(options & LYD_COMPARE_OPAQ) && ((node1->schema && !node2->schema) || (!node1->schema && node2->schema))) { + return LY_ENOT; + } + if ((r = lyd_compare_single_value(node1, node2))) { + return r; + } + + if (options & LYD_COMPARE_FULL_RECURSION) { + iter1 = lyd_child(node1); + iter2 = lyd_child(node2); + goto all_children_compare; + } + return LY_SUCCESS; + } else { + switch (node1->schema->nodetype) { + case LYS_LEAF: + case LYS_LEAFLIST: + if (options & LYD_COMPARE_DEFAULTS) { + if ((node1->flags & LYD_DEFAULT) != (node2->flags & LYD_DEFAULT)) { + return LY_ENOT; + } + } + if ((r = lyd_compare_single_value(node1, node2))) { + return r; + } + + return LY_SUCCESS; + case LYS_CONTAINER: + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + if (options & LYD_COMPARE_DEFAULTS) { + if ((node1->flags & LYD_DEFAULT) != (node2->flags & LYD_DEFAULT)) { + return LY_ENOT; + } + } + if (options & LYD_COMPARE_FULL_RECURSION) { + iter1 = lyd_child(node1); + iter2 = lyd_child(node2); + goto all_children_compare; + } + return LY_SUCCESS; + case LYS_LIST: + iter1 = lyd_child(node1); + iter2 = lyd_child(node2); + + if (!(node1->schema->flags & LYS_KEYLESS) && !(options & LYD_COMPARE_FULL_RECURSION)) { + /* lists with keys, their equivalence is based on their keys */ + for (const struct lysc_node *key = lysc_node_child(node1->schema); + key && (key->flags & LYS_KEY); + key = key->next) { + if (lyd_compare_single_(iter1, iter2, options, parental_schemas_checked)) { + return LY_ENOT; + } + iter1 = iter1->next; + iter2 = iter2->next; + } + } else { + /* lists without keys, their equivalence is based on equivalence of all the children (both direct and indirect) */ + +all_children_compare: + if (!iter1 && !iter2) { + /* no children, nothing to compare */ + return LY_SUCCESS; + } + + for ( ; iter1 && iter2; iter1 = iter1->next, iter2 = iter2->next) { + if (lyd_compare_single_(iter1, iter2, options | LYD_COMPARE_FULL_RECURSION, parental_schemas_checked)) { + return LY_ENOT; + } + } + if (iter1 || iter2) { + return LY_ENOT; + } + } + return LY_SUCCESS; + case LYS_ANYXML: + case LYS_ANYDATA: + any1 = (struct lyd_node_any *)node1; + any2 = (struct lyd_node_any *)node2; + + if (any1->value_type != any2->value_type) { + return LY_ENOT; + } + switch (any1->value_type) { + case LYD_ANYDATA_DATATREE: + iter1 = any1->value.tree; + iter2 = any2->value.tree; + goto all_children_compare; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + if ((!any1->value.str && any2->value.str) || (any1->value.str && !any2->value.str)) { + return LY_ENOT; + } else if (!any1->value.str && !any2->value.str) { + return LY_SUCCESS; + } + len1 = strlen(any1->value.str); + len2 = strlen(any2->value.str); + if ((len1 != len2) || strcmp(any1->value.str, any2->value.str)) { + return LY_ENOT; + } + return LY_SUCCESS; + case LYD_ANYDATA_LYB: + len1 = lyd_lyb_data_length(any1->value.mem); + len2 = lyd_lyb_data_length(any2->value.mem); + if ((len1 == -1) || (len2 == -1) || (len1 != len2) || memcmp(any1->value.mem, any2->value.mem, len1)) { + return LY_ENOT; + } + return LY_SUCCESS; + } + } + } + + LOGINT(LYD_CTX(node1)); + return LY_EINT; +} + +LIBYANG_API_DEF LY_ERR +lyd_compare_single(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) +{ + return lyd_compare_single_(node1, node2, options, 0); +} + +LIBYANG_API_DEF LY_ERR +lyd_compare_siblings(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) +{ + for ( ; node1 && node2; node1 = node1->next, node2 = node2->next) { + LY_CHECK_RET(lyd_compare_single(node1, node2, options)); + } + + if (node1 == node2) { + return LY_SUCCESS; + } + return LY_ENOT; +} + +LIBYANG_API_DEF LY_ERR +lyd_compare_meta(const struct lyd_meta *meta1, const struct lyd_meta *meta2) +{ + if (!meta1 || !meta2) { + if (meta1 == meta2) { + return LY_SUCCESS; + } else { + return LY_ENOT; + } + } + + if ((meta1->annotation->module->ctx != meta2->annotation->module->ctx) || (meta1->annotation != meta2->annotation)) { + return LY_ENOT; + } + + return meta1->value.realtype->plugin->compare(&meta1->value, &meta2->value); +} + +/** + * @brief Create a copy of the attribute. + * + * @param[in] attr Attribute to copy. + * @param[in] node Opaque where to append the new attribute. + * @param[out] dup Optional created attribute copy. + * @return LY_ERR value. + */ +static LY_ERR +lyd_dup_attr_single(const struct lyd_attr *attr, struct lyd_node *node, struct lyd_attr **dup) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_attr *a, *last; + struct lyd_node_opaq *opaq = (struct lyd_node_opaq *)node; + + LY_CHECK_ARG_RET(NULL, attr, node, !node->schema, LY_EINVAL); + + /* create a copy */ + a = calloc(1, sizeof *attr); + LY_CHECK_ERR_RET(!a, LOGMEM(LYD_CTX(node)), LY_EMEM); + + LY_CHECK_GOTO(ret = lydict_insert(LYD_CTX(node), attr->name.name, 0, &a->name.name), finish); + LY_CHECK_GOTO(ret = lydict_insert(LYD_CTX(node), attr->name.prefix, 0, &a->name.prefix), finish); + LY_CHECK_GOTO(ret = lydict_insert(LYD_CTX(node), attr->name.module_ns, 0, &a->name.module_ns), finish); + LY_CHECK_GOTO(ret = lydict_insert(LYD_CTX(node), attr->value, 0, &a->value), finish); + a->hints = attr->hints; + a->format = attr->format; + if (attr->val_prefix_data) { + ret = ly_dup_prefix_data(LYD_CTX(node), attr->format, attr->val_prefix_data, &a->val_prefix_data); + LY_CHECK_GOTO(ret, finish); + } + + /* insert as the last attribute */ + a->parent = opaq; + if (opaq->attr) { + for (last = opaq->attr; last->next; last = last->next) {} + last->next = a; + } else { + opaq->attr = a; + } + +finish: + if (ret) { + lyd_free_attr_single(LYD_CTX(node), a); + } else if (dup) { + *dup = a; + } + return LY_SUCCESS; +} + +/** + * @brief Find @p schema equivalent in @p trg_ctx. + * + * @param[in] schema Schema node to find. + * @param[in] trg_ctx Target context to search in. + * @param[in] parent Data parent of @p schema, if any. + * @param[in] log Whether to log directly. + * @param[out] trg_schema Found schema from @p trg_ctx to use. + * @return LY_RRR value. + */ +static LY_ERR +lyd_find_schema_ctx(const struct lysc_node *schema, const struct ly_ctx *trg_ctx, const struct lyd_node *parent, + ly_bool log, const struct lysc_node **trg_schema) +{ + const struct lysc_node *src_parent = NULL, *trg_parent = NULL, *sp, *tp; + const struct lys_module *trg_mod = NULL; + char *path; + + if (!schema) { + /* opaque node */ + *trg_schema = NULL; + return LY_SUCCESS; + } + + if (lysc_data_parent(schema) && parent && parent->schema) { + /* start from schema parent */ + trg_parent = parent->schema; + src_parent = lysc_data_parent(schema); + } + + do { + /* find the next parent */ + sp = schema; + while (lysc_data_parent(sp) != src_parent) { + sp = lysc_data_parent(sp); + } + src_parent = sp; + + if (!src_parent->parent) { + /* find the module first */ + trg_mod = ly_ctx_get_module_implemented(trg_ctx, src_parent->module->name); + if (!trg_mod) { + if (log) { + LOGERR(trg_ctx, LY_ENOTFOUND, "Module \"%s\" not present/implemented in the target context.", + src_parent->module->name); + } + return LY_ENOTFOUND; + } + } + + /* find the next parent */ + assert(trg_parent || trg_mod); + tp = NULL; + while ((tp = lys_getnext(tp, trg_parent, trg_mod ? trg_mod->compiled : NULL, 0))) { + if (!strcmp(tp->name, src_parent->name) && !strcmp(tp->module->name, src_parent->module->name)) { + break; + } + } + if (!tp) { + /* schema node not found */ + if (log) { + path = lysc_path(src_parent, LYSC_PATH_LOG, NULL, 0); + LOGERR(trg_ctx, LY_ENOTFOUND, "Schema node \"%s\" not found in the target context.", path); + free(path); + } + return LY_ENOTFOUND; + } + + trg_parent = tp; + } while (schema != src_parent); + + /* success */ + *trg_schema = trg_parent; + return LY_SUCCESS; +} + +/** + * @brief Duplicate a single node and connect it into @p parent (if present) or last of @p first siblings. + * + * Ignores ::LYD_DUP_WITH_PARENTS and ::LYD_DUP_WITH_SIBLINGS which are supposed to be handled by lyd_dup(). + * + * @param[in] node Node to duplicate. + * @param[in] trg_ctx Target context for duplicated nodes. + * @param[in] parent Parent to insert into, NULL for top-level sibling. + * @param[in] insert_last Whether the duplicated node can be inserted as the last child of @p parent. Set for + * recursive duplication as an optimization. + * @param[in,out] first First sibling, NULL if no top-level sibling exist yet. Can be also NULL if @p parent is set. + * @param[in] options Bitmask of options flags, see @ref dupoptions. + * @param[out] dup_p Pointer where the created duplicated node is placed (besides connecting it to @p parent / @p first). + * @return LY_ERR value. + */ +static LY_ERR +lyd_dup_r(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent, ly_bool insert_last, + struct lyd_node **first, uint32_t options, struct lyd_node **dup_p) +{ + LY_ERR ret; + struct lyd_node *dup = NULL; + struct lyd_meta *meta; + struct lyd_attr *attr; + struct lyd_node_any *any; + const struct lysc_type *type; + const char *val_can; + + LY_CHECK_ARG_RET(NULL, node, LY_EINVAL); + + if (node->flags & LYD_EXT) { + if (options & LYD_DUP_NO_EXT) { + /* no not duplicate this subtree */ + return LY_SUCCESS; + } + + /* we need to use the same context */ + trg_ctx = LYD_CTX(node); + } + + if (!node->schema) { + dup = calloc(1, sizeof(struct lyd_node_opaq)); + ((struct lyd_node_opaq *)dup)->ctx = trg_ctx; + } else { + switch (node->schema->nodetype) { + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + case LYS_CONTAINER: + case LYS_LIST: + dup = calloc(1, sizeof(struct lyd_node_inner)); + break; + case LYS_LEAF: + case LYS_LEAFLIST: + dup = calloc(1, sizeof(struct lyd_node_term)); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + dup = calloc(1, sizeof(struct lyd_node_any)); + break; + default: + LOGINT(trg_ctx); + ret = LY_EINT; + goto error; + } + } + LY_CHECK_ERR_GOTO(!dup, LOGMEM(trg_ctx); ret = LY_EMEM, error); + + if (options & LYD_DUP_WITH_FLAGS) { + dup->flags = node->flags; + } else { + dup->flags = (node->flags & (LYD_DEFAULT | LYD_EXT)) | LYD_NEW; + } + if (trg_ctx == LYD_CTX(node)) { + dup->schema = node->schema; + } else { + ret = lyd_find_schema_ctx(node->schema, trg_ctx, parent, 1, &dup->schema); + if (ret) { + /* has no schema but is not an opaque node */ + free(dup); + dup = NULL; + goto error; + } + } + dup->prev = dup; + + /* duplicate metadata/attributes */ + if (!(options & LYD_DUP_NO_META)) { + if (!node->schema) { + LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, attr) { + LY_CHECK_GOTO(ret = lyd_dup_attr_single(attr, dup, NULL), error); + } + } else { + LY_LIST_FOR(node->meta, meta) { + LY_CHECK_GOTO(ret = lyd_dup_meta_single(meta, dup, NULL), error); + } + } + } + + /* nodetype-specific work */ + if (!dup->schema) { + struct lyd_node_opaq *opaq = (struct lyd_node_opaq *)dup; + struct lyd_node_opaq *orig = (struct lyd_node_opaq *)node; + struct lyd_node *child; + + if (options & LYD_DUP_RECURSIVE) { + /* duplicate all the children */ + LY_LIST_FOR(orig->child, child) { + LY_CHECK_GOTO(ret = lyd_dup_r(child, trg_ctx, dup, 1, NULL, options, NULL), error); + } + } + LY_CHECK_GOTO(ret = lydict_insert(trg_ctx, orig->name.name, 0, &opaq->name.name), error); + LY_CHECK_GOTO(ret = lydict_insert(trg_ctx, orig->name.prefix, 0, &opaq->name.prefix), error); + LY_CHECK_GOTO(ret = lydict_insert(trg_ctx, orig->name.module_ns, 0, &opaq->name.module_ns), error); + LY_CHECK_GOTO(ret = lydict_insert(trg_ctx, orig->value, 0, &opaq->value), error); + opaq->hints = orig->hints; + opaq->format = orig->format; + if (orig->val_prefix_data) { + ret = ly_dup_prefix_data(trg_ctx, opaq->format, orig->val_prefix_data, &opaq->val_prefix_data); + LY_CHECK_GOTO(ret, error); + } + } else if (dup->schema->nodetype & LYD_NODE_TERM) { + struct lyd_node_term *term = (struct lyd_node_term *)dup; + struct lyd_node_term *orig = (struct lyd_node_term *)node; + + term->hash = orig->hash; + if (trg_ctx == LYD_CTX(node)) { + ret = orig->value.realtype->plugin->duplicate(trg_ctx, &orig->value, &term->value); + LY_CHECK_ERR_GOTO(ret, LOGERR(trg_ctx, ret, "Value duplication failed."), error); + } else { + /* store canonical value in the target context */ + val_can = lyd_get_value(node); + type = ((struct lysc_node_leaf *)term->schema)->type; + ret = lyd_value_store(trg_ctx, &term->value, type, val_can, strlen(val_can), NULL, LY_VALUE_CANON, NULL, + LYD_HINT_DATA, term->schema, NULL); + LY_CHECK_GOTO(ret, error); + } + } else if (dup->schema->nodetype & LYD_NODE_INNER) { + struct lyd_node_inner *orig = (struct lyd_node_inner *)node; + struct lyd_node *child; + + if (options & LYD_DUP_RECURSIVE) { + /* duplicate all the children */ + LY_LIST_FOR(orig->child, child) { + LY_CHECK_GOTO(ret = lyd_dup_r(child, trg_ctx, dup, 1, NULL, options, NULL), error); + } + } else if ((dup->schema->nodetype == LYS_LIST) && !(dup->schema->flags & LYS_KEYLESS)) { + /* always duplicate keys of a list */ + for (child = orig->child; child && lysc_is_key(child->schema); child = child->next) { + LY_CHECK_GOTO(ret = lyd_dup_r(child, trg_ctx, dup, 1, NULL, options, NULL), error); + } + } + lyd_hash(dup); + } else if (dup->schema->nodetype & LYD_NODE_ANY) { + dup->hash = node->hash; + any = (struct lyd_node_any *)node; + LY_CHECK_GOTO(ret = lyd_any_copy_value(dup, &any->value, any->value_type), error); + } + + /* insert */ + lyd_insert_node(parent, first, dup, insert_last); + + if (dup_p) { + *dup_p = dup; + } + return LY_SUCCESS; + +error: + lyd_free_tree(dup); + return ret; +} + +/** + * @brief Get a parent node to connect duplicated subtree to. + * + * @param[in] node Node (subtree) to duplicate. + * @param[in] trg_ctx Target context for duplicated nodes. + * @param[in] parent Initial parent to connect to. + * @param[in] options Bitmask of options flags, see @ref dupoptions. + * @param[out] dup_parent First duplicated parent node, if any. + * @param[out] local_parent Correct parent to directly connect duplicated @p node to. + * @return LY_ERR value. + */ +static LY_ERR +lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, const struct lyd_node_inner *parent, + uint32_t options, struct lyd_node **dup_parent, struct lyd_node_inner **local_parent) +{ + const struct lyd_node_inner *orig_parent, *iter; + ly_bool repeat = 1, ext_parent = 0; + + *dup_parent = NULL; + *local_parent = NULL; + + if (node->flags & LYD_EXT) { + ext_parent = 1; + } + for (orig_parent = node->parent; repeat && orig_parent; orig_parent = orig_parent->parent) { + if (ext_parent) { + /* use the standard context */ + trg_ctx = LYD_CTX(orig_parent); + } + if (parent && (parent->schema == orig_parent->schema)) { + /* stop creating parents, connect what we have into the provided parent */ + iter = parent; + repeat = 0; + } else { + iter = NULL; + LY_CHECK_RET(lyd_dup_r((struct lyd_node *)orig_parent, trg_ctx, NULL, 0, (struct lyd_node **)&iter, options, + (struct lyd_node **)&iter)); + } + if (!*local_parent) { + *local_parent = (struct lyd_node_inner *)iter; + } + if (iter->child) { + /* 1) list - add after keys + * 2) provided parent with some children */ + iter->child->prev->next = *dup_parent; + if (*dup_parent) { + (*dup_parent)->prev = iter->child->prev; + iter->child->prev = *dup_parent; + } + } else { + ((struct lyd_node_inner *)iter)->child = *dup_parent; + } + if (*dup_parent) { + (*dup_parent)->parent = (struct lyd_node_inner *)iter; + } + *dup_parent = (struct lyd_node *)iter; + if (orig_parent->flags & LYD_EXT) { + ext_parent = 1; + } + } + + if (repeat && parent) { + /* given parent and created parents chain actually do not interconnect */ + LOGERR(trg_ctx, LY_EINVAL, + "Invalid argument parent (%s()) - does not interconnect with the created node's parents chain.", __func__); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +static LY_ERR +lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node_inner *parent, uint32_t options, + ly_bool nosiblings, struct lyd_node **dup) +{ + LY_ERR rc; + const struct lyd_node *orig; /* original node to be duplicated */ + struct lyd_node *first = NULL; /* the first duplicated node, this is returned */ + struct lyd_node *top = NULL; /* the most higher created node */ + struct lyd_node_inner *local_parent = NULL; /* the direct parent node for the duplicated node(s) */ + + assert(node && trg_ctx); + + if (options & LYD_DUP_WITH_PARENTS) { + LY_CHECK_GOTO(rc = lyd_dup_get_local_parent(node, trg_ctx, parent, options & (LYD_DUP_WITH_FLAGS | LYD_DUP_NO_META), + &top, &local_parent), error); + } else { + local_parent = parent; + } + + LY_LIST_FOR(node, orig) { + if (lysc_is_key(orig->schema)) { + if (local_parent) { + /* the key must already exist in the parent */ + rc = lyd_find_sibling_schema(local_parent->child, orig->schema, first ? NULL : &first); + LY_CHECK_ERR_GOTO(rc, LOGINT(trg_ctx), error); + } else { + assert(!(options & LYD_DUP_WITH_PARENTS)); + /* duplicating a single key, okay, I suppose... */ + rc = lyd_dup_r(orig, trg_ctx, NULL, 0, &first, options, first ? NULL : &first); + LY_CHECK_GOTO(rc, error); + } + } else { + /* if there is no local parent, it will be inserted into first */ + rc = lyd_dup_r(orig, trg_ctx, local_parent ? &local_parent->node : NULL, 0, &first, options, + first ? NULL : &first); + LY_CHECK_GOTO(rc, error); + } + if (nosiblings) { + break; + } + } + + if (dup) { + *dup = first; + } + return LY_SUCCESS; + +error: + if (top) { + lyd_free_tree(top); + } else { + lyd_free_siblings(first); + } + return rc; +} + +/** + * @brief Check the context of node and parent when duplicating nodes. + * + * @param[in] node Node to duplicate. + * @param[in] parent Parent of the duplicated node(s). + * @return LY_ERR value. + */ +static LY_ERR +lyd_dup_ctx_check(const struct lyd_node *node, const struct lyd_node_inner *parent) +{ + const struct lyd_node *iter; + + if (!node || !parent) { + return LY_SUCCESS; + } + + if ((LYD_CTX(node) != LYD_CTX(parent))) { + /* try to find top-level ext data parent */ + for (iter = node; iter && !(iter->flags & LYD_EXT); iter = lyd_parent(iter)) {} + + if (!iter || !lyd_parent(iter) || (LYD_CTX(lyd_parent(iter)) != LYD_CTX(parent))) { + LOGERR(NULL, LY_EINVAL, "Different contexts used in node duplication."); + return LY_EINVAL; + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_dup_single(const struct lyd_node *node, struct lyd_node_inner *parent, uint32_t options, struct lyd_node **dup) +{ + LY_CHECK_ARG_RET(NULL, node, LY_EINVAL); + LY_CHECK_RET(lyd_dup_ctx_check(node, parent)); + + return lyd_dup(node, LYD_CTX(node), parent, options, 1, dup); +} + +LIBYANG_API_DEF LY_ERR +lyd_dup_single_to_ctx(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node_inner *parent, + uint32_t options, struct lyd_node **dup) +{ + LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL); + + return lyd_dup(node, trg_ctx, parent, options, 1, dup); +} + +LIBYANG_API_DEF LY_ERR +lyd_dup_siblings(const struct lyd_node *node, struct lyd_node_inner *parent, uint32_t options, struct lyd_node **dup) +{ + LY_CHECK_ARG_RET(NULL, node, LY_EINVAL); + LY_CHECK_RET(lyd_dup_ctx_check(node, parent)); + + return lyd_dup(node, LYD_CTX(node), parent, options, 0, dup); +} + +LIBYANG_API_DEF LY_ERR +lyd_dup_siblings_to_ctx(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node_inner *parent, + uint32_t options, struct lyd_node **dup) +{ + LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL); + + return lyd_dup(node, trg_ctx, parent, options, 0, dup); +} + +LIBYANG_API_DEF LY_ERR +lyd_dup_meta_single(const struct lyd_meta *meta, struct lyd_node *node, struct lyd_meta **dup) +{ + LY_ERR ret = LY_SUCCESS; + const struct ly_ctx *ctx; + struct lyd_meta *mt, *last; + + LY_CHECK_ARG_RET(NULL, meta, node, LY_EINVAL); + + /* log to node context but value must always use the annotation context */ + ctx = meta->annotation->module->ctx; + + /* create a copy */ + mt = calloc(1, sizeof *mt); + LY_CHECK_ERR_RET(!mt, LOGMEM(LYD_CTX(node)), LY_EMEM); + mt->annotation = meta->annotation; + ret = meta->value.realtype->plugin->duplicate(ctx, &meta->value, &mt->value); + LY_CHECK_ERR_GOTO(ret, LOGERR(LYD_CTX(node), LY_EINT, "Value duplication failed."), finish); + LY_CHECK_GOTO(ret = lydict_insert(ctx, meta->name, 0, &mt->name), finish); + + /* insert as the last attribute */ + mt->parent = node; + if (node->meta) { + for (last = node->meta; last->next; last = last->next) {} + last->next = mt; + } else { + node->meta = mt; + } + +finish: + if (ret) { + lyd_free_meta_single(mt); + } else if (dup) { + *dup = mt; + } + return LY_SUCCESS; +} + +/** + * @brief Merge a source sibling into target siblings. + * + * @param[in,out] first_trg First target sibling, is updated if top-level. + * @param[in] parent_trg Target parent. + * @param[in,out] sibling_src Source sibling to merge, set to NULL if spent. + * @param[in] merge_cb Optional merge callback. + * @param[in] cb_data Arbitrary callback data. + * @param[in] options Merge options. + * @param[in,out] dup_inst Duplicate instance cache for all @p first_trg siblings. + * @return LY_ERR value. + */ +static LY_ERR +lyd_merge_sibling_r(struct lyd_node **first_trg, struct lyd_node *parent_trg, const struct lyd_node **sibling_src_p, + lyd_merge_cb merge_cb, void *cb_data, uint16_t options, struct lyd_dup_inst **dup_inst) +{ + const struct lyd_node *child_src, *tmp, *sibling_src; + struct lyd_node *match_trg, *dup_src, *elem; + struct lyd_node_opaq *opaq_trg, *opaq_src; + struct lysc_type *type; + struct lyd_dup_inst *child_dup_inst = NULL; + LY_ERR ret; + ly_bool first_inst = 0; + + sibling_src = *sibling_src_p; + if (!sibling_src->schema) { + /* try to find the same opaque node */ + lyd_find_sibling_opaq_next(*first_trg, LYD_NAME(sibling_src), &match_trg); + } else if (sibling_src->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { + /* try to find the exact instance */ + lyd_find_sibling_first(*first_trg, sibling_src, &match_trg); + } else { + /* try to simply find the node, there cannot be more instances */ + lyd_find_sibling_val(*first_trg, sibling_src->schema, NULL, 0, &match_trg); + } + + if (match_trg) { + /* update match as needed */ + LY_CHECK_RET(lyd_dup_inst_next(&match_trg, *first_trg, dup_inst)); + } else { + /* first instance of this node */ + first_inst = 1; + } + + if (match_trg) { + /* call callback */ + if (merge_cb) { + LY_CHECK_RET(merge_cb(match_trg, sibling_src, cb_data)); + } + + /* node found, make sure even value matches for all node types */ + if (!match_trg->schema) { + if (lyd_compare_single(sibling_src, match_trg, 0)) { + /* update value */ + opaq_trg = (struct lyd_node_opaq *)match_trg; + opaq_src = (struct lyd_node_opaq *)sibling_src; + + lydict_remove(LYD_CTX(opaq_trg), opaq_trg->value); + lydict_insert(LYD_CTX(opaq_trg), opaq_src->value, 0, &opaq_trg->value); + opaq_trg->hints = opaq_src->hints; + + ly_free_prefix_data(opaq_trg->format, opaq_trg->val_prefix_data); + opaq_trg->format = opaq_src->format; + ly_dup_prefix_data(LYD_CTX(opaq_trg), opaq_src->format, opaq_src->val_prefix_data, + &opaq_trg->val_prefix_data); + } + } else if ((match_trg->schema->nodetype == LYS_LEAF) && + lyd_compare_single(sibling_src, match_trg, LYD_COMPARE_DEFAULTS)) { + /* since they are different, they cannot both be default */ + assert(!(sibling_src->flags & LYD_DEFAULT) || !(match_trg->flags & LYD_DEFAULT)); + + /* update value (or only LYD_DEFAULT flag) only if flag set or the source node is not default */ + if ((options & LYD_MERGE_DEFAULTS) || !(sibling_src->flags & LYD_DEFAULT)) { + type = ((struct lysc_node_leaf *)match_trg->schema)->type; + type->plugin->free(LYD_CTX(match_trg), &((struct lyd_node_term *)match_trg)->value); + LY_CHECK_RET(type->plugin->duplicate(LYD_CTX(match_trg), &((struct lyd_node_term *)sibling_src)->value, + &((struct lyd_node_term *)match_trg)->value)); + + /* copy flags and add LYD_NEW */ + match_trg->flags = sibling_src->flags | ((options & LYD_MERGE_WITH_FLAGS) ? 0 : LYD_NEW); + } + } else if ((match_trg->schema->nodetype & LYS_ANYDATA) && lyd_compare_single(sibling_src, match_trg, 0)) { + /* update value */ + LY_CHECK_RET(lyd_any_copy_value(match_trg, &((struct lyd_node_any *)sibling_src)->value, + ((struct lyd_node_any *)sibling_src)->value_type)); + + /* copy flags and add LYD_NEW */ + match_trg->flags = sibling_src->flags | ((options & LYD_MERGE_WITH_FLAGS) ? 0 : LYD_NEW); + } + + /* check descendants, recursively */ + ret = LY_SUCCESS; + LY_LIST_FOR_SAFE(lyd_child_no_keys(sibling_src), tmp, child_src) { + ret = lyd_merge_sibling_r(lyd_node_child_p(match_trg), match_trg, &child_src, merge_cb, cb_data, options, + &child_dup_inst); + if (ret) { + break; + } + } + lyd_dup_inst_free(child_dup_inst); + LY_CHECK_RET(ret); + } else { + /* node not found, merge it */ + if (options & LYD_MERGE_DESTRUCT) { + dup_src = (struct lyd_node *)sibling_src; + lyd_unlink_tree(dup_src); + /* spend it */ + *sibling_src_p = NULL; + } else { + LY_CHECK_RET(lyd_dup_single(sibling_src, NULL, LYD_DUP_RECURSIVE | LYD_DUP_WITH_FLAGS, &dup_src)); + } + + if (!(options & LYD_MERGE_WITH_FLAGS)) { + /* set LYD_NEW for all the new nodes, required for validation */ + LYD_TREE_DFS_BEGIN(dup_src, elem) { + elem->flags |= LYD_NEW; + LYD_TREE_DFS_END(dup_src, elem); + } + } + + /* insert */ + lyd_insert_node(parent_trg, first_trg, dup_src, 0); + + if (first_inst) { + /* remember not to find this instance next time */ + LY_CHECK_RET(lyd_dup_inst_next(&dup_src, *first_trg, dup_inst)); + } + + /* call callback, no source node */ + if (merge_cb) { + LY_CHECK_RET(merge_cb(dup_src, NULL, cb_data)); + } + } + + return LY_SUCCESS; +} + +static LY_ERR +lyd_merge(struct lyd_node **target, const struct lyd_node *source, const struct lys_module *mod, + lyd_merge_cb merge_cb, void *cb_data, uint16_t options, ly_bool nosiblings) +{ + const struct lyd_node *sibling_src, *tmp; + struct lyd_dup_inst *dup_inst = NULL; + ly_bool first; + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(NULL, target, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(*target ? LYD_CTX(*target) : NULL, source ? LYD_CTX(source) : NULL, mod ? mod->ctx : NULL, + LY_EINVAL); + + if (!source) { + /* nothing to merge */ + return LY_SUCCESS; + } + + if ((*target && lysc_data_parent((*target)->schema)) || lysc_data_parent(source->schema)) { + LOGERR(LYD_CTX(source), LY_EINVAL, "Invalid arguments - can merge only 2 top-level subtrees (%s()).", __func__); + return LY_EINVAL; + } + + LY_LIST_FOR_SAFE(source, tmp, sibling_src) { + if (mod && (lyd_owner_module(sibling_src) != mod)) { + /* skip data nodes from different modules */ + continue; + } + + first = (sibling_src == source) ? 1 : 0; + ret = lyd_merge_sibling_r(target, NULL, &sibling_src, merge_cb, cb_data, options, &dup_inst); + if (ret) { + break; + } + if (first && !sibling_src) { + /* source was spent (unlinked), move to the next node */ + source = tmp; + } + + if (nosiblings) { + break; + } + } + + if (options & LYD_MERGE_DESTRUCT) { + /* free any leftover source data that were not merged */ + lyd_free_siblings((struct lyd_node *)source); + } + + lyd_dup_inst_free(dup_inst); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_merge_tree(struct lyd_node **target, const struct lyd_node *source, uint16_t options) +{ + return lyd_merge(target, source, NULL, NULL, NULL, options, 1); +} + +LIBYANG_API_DEF LY_ERR +lyd_merge_siblings(struct lyd_node **target, const struct lyd_node *source, uint16_t options) +{ + return lyd_merge(target, source, NULL, NULL, NULL, options, 0); +} + +LIBYANG_API_DEF LY_ERR +lyd_merge_module(struct lyd_node **target, const struct lyd_node *source, const struct lys_module *mod, + lyd_merge_cb merge_cb, void *cb_data, uint16_t options) +{ + return lyd_merge(target, source, mod, merge_cb, cb_data, options, 0); +} + +static LY_ERR +lyd_path_str_enlarge(char **buffer, size_t *buflen, size_t reqlen, ly_bool is_static) +{ + /* ending \0 */ + ++reqlen; + + if (reqlen > *buflen) { + if (is_static) { + return LY_EINCOMPLETE; + } + + *buffer = ly_realloc(*buffer, reqlen * sizeof **buffer); + if (!*buffer) { + return LY_EMEM; + } + + *buflen = reqlen; + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_path_list_predicate(const struct lyd_node *node, char **buffer, size_t *buflen, size_t *bufused, ly_bool is_static) +{ + const struct lyd_node *key; + size_t len; + const char *val; + char quot; + + for (key = lyd_child(node); key && key->schema && (key->schema->flags & LYS_KEY); key = key->next) { + val = lyd_get_value(key); + len = 1 + strlen(key->schema->name) + 2 + strlen(val) + 2; + LY_CHECK_RET(lyd_path_str_enlarge(buffer, buflen, *bufused + len, is_static)); + + quot = '\''; + if (strchr(val, '\'')) { + quot = '"'; + } + *bufused += sprintf(*buffer + *bufused, "[%s=%c%s%c]", key->schema->name, quot, val, quot); + } + + return LY_SUCCESS; +} + +/** + * @brief Append leaf-list value predicate to path. + * + * @param[in] node Node to print. + * @param[in,out] buffer Buffer to print to. + * @param[in,out] buflen Current buffer length. + * @param[in,out] bufused Current number of characters used in @p buffer. + * @param[in] is_static Whether buffer is static or can be reallocated. + * @return LY_ERR + */ +static LY_ERR +lyd_path_leaflist_predicate(const struct lyd_node *node, char **buffer, size_t *buflen, size_t *bufused, ly_bool is_static) +{ + size_t len; + const char *val; + char quot; + + val = lyd_get_value(node); + len = 4 + strlen(val) + 2; /* "[.='" + val + "']" */ + LY_CHECK_RET(lyd_path_str_enlarge(buffer, buflen, *bufused + len, is_static)); + + quot = '\''; + if (strchr(val, '\'')) { + quot = '"'; + } + *bufused += sprintf(*buffer + *bufused, "[.=%c%s%c]", quot, val, quot); + + return LY_SUCCESS; +} + +/** + * @brief Append node position (relative to its other instances) predicate to path. + * + * @param[in] node Node to print. + * @param[in,out] buffer Buffer to print to. + * @param[in,out] buflen Current buffer length. + * @param[in,out] bufused Current number of characters used in @p buffer. + * @param[in] is_static Whether buffer is static or can be reallocated. + * @return LY_ERR + */ +static LY_ERR +lyd_path_position_predicate(const struct lyd_node *node, char **buffer, size_t *buflen, size_t *bufused, ly_bool is_static) +{ + size_t len; + uint32_t pos; + char *val = NULL; + LY_ERR rc; + + pos = lyd_list_pos(node); + if (asprintf(&val, "%" PRIu32, pos) == -1) { + return LY_EMEM; + } + + len = 1 + strlen(val) + 1; + rc = lyd_path_str_enlarge(buffer, buflen, *bufused + len, is_static); + if (rc != LY_SUCCESS) { + goto cleanup; + } + + *bufused += sprintf(*buffer + *bufused, "[%s]", val); + +cleanup: + free(val); + return rc; +} + +LIBYANG_API_DEF char * +lyd_path(const struct lyd_node *node, LYD_PATH_TYPE pathtype, char *buffer, size_t buflen) +{ + ly_bool is_static = 0; + uint32_t i, depth; + size_t bufused = 0, len; + const struct lyd_node *iter, *parent; + const struct lys_module *mod, *prev_mod; + LY_ERR rc = LY_SUCCESS; + + LY_CHECK_ARG_RET(NULL, node, NULL); + if (buffer) { + LY_CHECK_ARG_RET(LYD_CTX(node), buflen > 1, NULL); + is_static = 1; + } else { + buflen = 0; + } + + switch (pathtype) { + case LYD_PATH_STD: + case LYD_PATH_STD_NO_LAST_PRED: + depth = 1; + for (iter = node; iter->parent; iter = lyd_parent(iter)) { + ++depth; + } + + goto iter_print; + while (depth) { + /* find the right node */ + for (iter = node, i = 1; i < depth; iter = lyd_parent(iter), ++i) {} +iter_print: + /* get the module */ + mod = iter->schema ? iter->schema->module : lyd_owner_module(iter); + parent = lyd_parent(iter); + prev_mod = (parent && parent->schema) ? parent->schema->module : lyd_owner_module(parent); + if (prev_mod == mod) { + mod = NULL; + } + + /* realloc string */ + len = 1 + (mod ? strlen(mod->name) + 1 : 0) + (iter->schema ? strlen(iter->schema->name) : + strlen(((struct lyd_node_opaq *)iter)->name.name)); + rc = lyd_path_str_enlarge(&buffer, &buflen, bufused + len, is_static); + if (rc != LY_SUCCESS) { + break; + } + + /* print next node */ + bufused += sprintf(buffer + bufused, "/%s%s%s", mod ? mod->name : "", mod ? ":" : "", LYD_NAME(iter)); + + /* do not always print the last (first) predicate */ + if (iter->schema && ((depth > 1) || (pathtype == LYD_PATH_STD))) { + switch (iter->schema->nodetype) { + case LYS_LIST: + if (iter->schema->flags & LYS_KEYLESS) { + /* print its position */ + rc = lyd_path_position_predicate(iter, &buffer, &buflen, &bufused, is_static); + } else { + /* print all list keys in predicates */ + rc = lyd_path_list_predicate(iter, &buffer, &buflen, &bufused, is_static); + } + break; + case LYS_LEAFLIST: + if (iter->schema->flags & LYS_CONFIG_W) { + /* print leaf-list value */ + rc = lyd_path_leaflist_predicate(iter, &buffer, &buflen, &bufused, is_static); + } else { + /* print its position */ + rc = lyd_path_position_predicate(iter, &buffer, &buflen, &bufused, is_static); + } + break; + default: + /* nothing to print more */ + break; + } + } + if (rc != LY_SUCCESS) { + break; + } + + --depth; + } + break; + } + + return buffer; +} + +char * +lyd_path_set(const struct ly_set *dnodes, LYD_PATH_TYPE pathtype) +{ + uint32_t depth; + size_t bufused = 0, buflen = 0, len; + char *buffer = NULL; + const struct lyd_node *iter, *parent; + const struct lys_module *mod, *prev_mod; + LY_ERR rc = LY_SUCCESS; + + switch (pathtype) { + case LYD_PATH_STD: + case LYD_PATH_STD_NO_LAST_PRED: + for (depth = 1; depth <= dnodes->count; ++depth) { + /* current node */ + iter = dnodes->dnodes[depth - 1]; + mod = iter->schema ? iter->schema->module : lyd_owner_module(iter); + + /* parent */ + parent = (depth > 1) ? dnodes->dnodes[depth - 2] : NULL; + assert(!parent || !iter->schema || !parent->schema || (lysc_data_parent(iter->schema) == parent->schema) || + (!lysc_data_parent(iter->schema) && (LYD_CTX(iter) != LYD_CTX(parent)))); + + /* get module to print, if any */ + prev_mod = (parent && parent->schema) ? parent->schema->module : lyd_owner_module(parent); + if (prev_mod == mod) { + mod = NULL; + } + + /* realloc string */ + len = 1 + (mod ? strlen(mod->name) + 1 : 0) + (iter->schema ? strlen(iter->schema->name) : + strlen(((struct lyd_node_opaq *)iter)->name.name)); + if ((rc = lyd_path_str_enlarge(&buffer, &buflen, bufused + len, 0))) { + break; + } + + /* print next node */ + bufused += sprintf(buffer + bufused, "/%s%s%s", mod ? mod->name : "", mod ? ":" : "", LYD_NAME(iter)); + + /* do not always print the last (first) predicate */ + if (iter->schema && ((depth > 1) || (pathtype == LYD_PATH_STD))) { + switch (iter->schema->nodetype) { + case LYS_LIST: + if (iter->schema->flags & LYS_KEYLESS) { + /* print its position */ + rc = lyd_path_position_predicate(iter, &buffer, &buflen, &bufused, 0); + } else { + /* print all list keys in predicates */ + rc = lyd_path_list_predicate(iter, &buffer, &buflen, &bufused, 0); + } + break; + case LYS_LEAFLIST: + if (iter->schema->flags & LYS_CONFIG_W) { + /* print leaf-list value */ + rc = lyd_path_leaflist_predicate(iter, &buffer, &buflen, &bufused, 0); + } else { + /* print its position */ + rc = lyd_path_position_predicate(iter, &buffer, &buflen, &bufused, 0); + } + break; + default: + /* nothing to print more */ + break; + } + } + if (rc) { + break; + } + } + break; + } + + return buffer; +} + +LIBYANG_API_DEF struct lyd_meta * +lyd_find_meta(const struct lyd_meta *first, const struct lys_module *module, const char *name) +{ + struct lyd_meta *ret = NULL; + const struct ly_ctx *ctx; + const char *prefix, *tmp; + char *str; + size_t pref_len, name_len; + + LY_CHECK_ARG_RET(NULL, module || strchr(name, ':'), name, NULL); + LY_CHECK_CTX_EQUAL_RET(first ? first->annotation->module->ctx : NULL, module ? module->ctx : NULL, NULL); + + if (!first) { + return NULL; + } + + ctx = first->annotation->module->ctx; + + /* parse the name */ + tmp = name; + if (ly_parse_nodeid(&tmp, &prefix, &pref_len, &name, &name_len) || tmp[0]) { + LOGERR(ctx, LY_EINVAL, "Metadata name \"%s\" is not valid.", name); + return NULL; + } + + /* find the module */ + if (prefix) { + str = strndup(prefix, pref_len); + module = ly_ctx_get_module_latest(ctx, str); + free(str); + LY_CHECK_ERR_RET(!module, LOGERR(ctx, LY_EINVAL, "Module \"%.*s\" not found.", (int)pref_len, prefix), NULL); + } + + /* find the metadata */ + LY_LIST_FOR(first, first) { + if ((first->annotation->module == module) && !strcmp(first->name, name)) { + ret = (struct lyd_meta *)first; + break; + } + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_sibling_first(const struct lyd_node *siblings, const struct lyd_node *target, struct lyd_node **match) +{ + struct lyd_node **match_p, *iter, *dup = NULL; + struct lyd_node_inner *parent; + ly_bool found; + + LY_CHECK_ARG_RET(NULL, target, LY_EINVAL); + if (!siblings) { + /* no data */ + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + if (LYD_CTX(siblings) != LYD_CTX(target)) { + /* create a duplicate in this context */ + LY_CHECK_RET(lyd_dup_single_to_ctx(target, LYD_CTX(siblings), NULL, 0, &dup)); + target = dup; + } + + if ((siblings->schema && target->schema && (lysc_data_parent(siblings->schema) != lysc_data_parent(target->schema)))) { + /* schema mismatch */ + lyd_free_tree(dup); + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + /* get first sibling */ + siblings = lyd_first_sibling(siblings); + + parent = siblings->parent; + if (parent && parent->schema && parent->children_ht) { + assert(target->hash); + + if (lysc_is_dup_inst_list(target->schema)) { + /* we must search the instances from beginning to find the first matching one */ + found = 0; + LYD_LIST_FOR_INST(siblings, target->schema, iter) { + if (!lyd_compare_single(target, iter, 0)) { + found = 1; + break; + } + } + if (found) { + siblings = iter; + } else { + siblings = NULL; + } + } else { + /* find by hash */ + if (!lyht_find(parent->children_ht, &target, target->hash, (void **)&match_p)) { + siblings = *match_p; + } else { + /* not found */ + siblings = NULL; + } + } + } else { + /* no children hash table */ + for ( ; siblings; siblings = siblings->next) { + if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) { + break; + } + } + } + + lyd_free_tree(dup); + if (!siblings) { + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + if (match) { + *match = (struct lyd_node *)siblings; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_sibling_val(const struct lyd_node *siblings, const struct lysc_node *schema, const char *key_or_value, + size_t val_len, struct lyd_node **match) +{ + LY_ERR rc; + struct lyd_node *target = NULL; + const struct lyd_node *parent; + + LY_CHECK_ARG_RET(NULL, schema, !(schema->nodetype & (LYS_CHOICE | LYS_CASE)), LY_EINVAL); + if (!siblings) { + /* no data */ + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + if ((LYD_CTX(siblings) != schema->module->ctx)) { + /* parent of ext nodes is useless */ + parent = (siblings->flags & LYD_EXT) ? NULL : lyd_parent(siblings); + if (lyd_find_schema_ctx(schema, LYD_CTX(siblings), parent, 0, &schema)) { + /* no schema node in siblings so certainly no data node either */ + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + } + + if (siblings->schema && (lysc_data_parent(siblings->schema) != lysc_data_parent(schema))) { + /* schema mismatch */ + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + if (key_or_value && !val_len) { + val_len = strlen(key_or_value); + } + + if ((schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && key_or_value) { + /* create a data node and find the instance */ + if (schema->nodetype == LYS_LEAFLIST) { + /* target used attributes: schema, hash, value */ + rc = lyd_create_term(schema, key_or_value, val_len, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, &target); + LY_CHECK_RET(rc); + } else { + /* target used attributes: schema, hash, child (all keys) */ + LY_CHECK_RET(lyd_create_list2(schema, key_or_value, val_len, &target)); + } + + /* find it */ + rc = lyd_find_sibling_first(siblings, target, match); + } else { + /* find the first schema node instance */ + rc = lyd_find_sibling_schema(siblings, schema, match); + } + + lyd_free_tree(target); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_sibling_dup_inst_set(const struct lyd_node *siblings, const struct lyd_node *target, struct ly_set **set) +{ + struct lyd_node **match_p, *first, *iter; + struct lyd_node_inner *parent; + + LY_CHECK_ARG_RET(NULL, target, set, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(siblings ? LYD_CTX(siblings) : NULL, LYD_CTX(target), LY_EINVAL); + + LY_CHECK_RET(ly_set_new(set)); + + if (!siblings || (siblings->schema && (lysc_data_parent(siblings->schema) != lysc_data_parent(target->schema)))) { + /* no data or schema mismatch */ + return LY_ENOTFOUND; + } + + /* get first sibling */ + siblings = lyd_first_sibling(siblings); + + parent = siblings->parent; + if (parent && parent->schema && parent->children_ht) { + assert(target->hash); + + /* find the first instance */ + lyd_find_sibling_first(siblings, target, &first); + if (first) { + /* add it so that it is the first in the set */ + if (ly_set_add(*set, first, 1, NULL)) { + goto error; + } + + /* find by hash */ + if (!lyht_find(parent->children_ht, &target, target->hash, (void **)&match_p)) { + iter = *match_p; + } else { + /* not found */ + iter = NULL; + } + while (iter) { + /* add all found nodes into the set */ + if ((iter != first) && !lyd_compare_single(iter, target, 0) && ly_set_add(*set, iter, 1, NULL)) { + goto error; + } + + /* find next instance */ + if (lyht_find_next(parent->children_ht, &iter, iter->hash, (void **)&match_p)) { + iter = NULL; + } else { + iter = *match_p; + } + } + } + } else { + /* no children hash table */ + LY_LIST_FOR(siblings, siblings) { + if (!lyd_compare_single(target, siblings, LYD_COMPARE_OPAQ)) { + ly_set_add(*set, (void *)siblings, 1, NULL); + } + } + } + + if (!(*set)->count) { + return LY_ENOTFOUND; + } + return LY_SUCCESS; + +error: + ly_set_free(*set, NULL); + *set = NULL; + return LY_EMEM; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_sibling_opaq_next(const struct lyd_node *first, const char *name, struct lyd_node **match) +{ + LY_CHECK_ARG_RET(NULL, name, LY_EINVAL); + + for ( ; first; first = first->next) { + if (!first->schema && !strcmp(LYD_NAME(first), name)) { + break; + } + } + + if (match) { + *match = (struct lyd_node *)first; + } + return first ? LY_SUCCESS : LY_ENOTFOUND; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, LY_VALUE_FORMAT format, + void *prefix_data, const struct lyxp_var *vars, struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_set xp_set = {0}; + struct lyxp_expr *exp = NULL; + uint32_t i; + + LY_CHECK_ARG_RET(NULL, tree, xpath, format, set, LY_EINVAL); + + *set = NULL; + + /* parse expression */ + ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(tree), xpath, 0, 1, &exp); + LY_CHECK_GOTO(ret, cleanup); + + /* evaluate expression */ + ret = lyxp_eval(LYD_CTX(tree), exp, NULL, format, prefix_data, ctx_node, ctx_node, tree, vars, &xp_set, + LYXP_IGNORE_WHEN); + LY_CHECK_GOTO(ret, cleanup); + + if (xp_set.type != LYXP_SET_NODE_SET) { + LOGERR(LYD_CTX(tree), LY_EINVAL, "XPath \"%s\" result is not a node set.", xpath); + ret = LY_EINVAL; + goto cleanup; + } + + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); + + /* transform into ly_set, allocate memory for all the elements once (even though not all items must be + * elements but most likely will be) */ + (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); + LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(LYD_CTX(tree)); ret = LY_EMEM, cleanup); + (*set)->size = xp_set.used; + + for (i = 0; i < xp_set.used; ++i) { + if (xp_set.val.nodes[i].type == LYXP_NODE_ELEM) { + ret = ly_set_add(*set, xp_set.val.nodes[i].node, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&xp_set); + lyxp_expr_free((struct ly_ctx *)LYD_CTX(tree), exp); + if (ret) { + ly_set_free(*set, NULL); + *set = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_xpath3(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, + const struct lyxp_var *vars, struct ly_set **set) +{ + LY_CHECK_ARG_RET(NULL, tree, xpath, set, LY_EINVAL); + + return lyd_find_xpath4(ctx_node, tree, xpath, LY_VALUE_JSON, NULL, vars, set); +} + +LIBYANG_API_DEF LY_ERR +lyd_find_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, struct ly_set **set) +{ + LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL); + + return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, vars, set); +} + +LIBYANG_API_DEF LY_ERR +lyd_find_xpath(const struct lyd_node *ctx_node, const char *xpath, struct ly_set **set) +{ + LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL); + + return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, NULL, set); +} + +LIBYANG_API_DEF LY_ERR +lyd_eval_xpath3(const struct lyd_node *ctx_node, const struct lys_module *cur_mod, const char *xpath, + LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_set xp_set = {0}; + struct lyxp_expr *exp = NULL; + + LY_CHECK_ARG_RET(NULL, ctx_node, xpath, result, LY_EINVAL); + + /* compile expression */ + ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(ctx_node), xpath, 0, 1, &exp); + LY_CHECK_GOTO(ret, cleanup); + + /* evaluate expression */ + ret = lyxp_eval(LYD_CTX(ctx_node), exp, cur_mod, format, prefix_data, ctx_node, ctx_node, ctx_node, vars, &xp_set, + LYXP_IGNORE_WHEN); + LY_CHECK_GOTO(ret, cleanup); + + /* transform into boolean */ + ret = lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN); + LY_CHECK_GOTO(ret, cleanup); + + /* set result */ + *result = xp_set.val.bln; + +cleanup: + lyxp_set_free_content(&xp_set); + lyxp_expr_free((struct ly_ctx *)LYD_CTX(ctx_node), exp); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_eval_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, ly_bool *result) +{ + return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, vars, result); +} + +LIBYANG_API_DEF LY_ERR +lyd_eval_xpath(const struct lyd_node *ctx_node, const char *xpath, ly_bool *result) +{ + return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, NULL, result); +} + +LIBYANG_API_DEF LY_ERR +lyd_find_path(const struct lyd_node *ctx_node, const char *path, ly_bool output, struct lyd_node **match) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *expr = NULL; + struct ly_path *lypath = NULL; + + LY_CHECK_ARG_RET(NULL, ctx_node, ctx_node->schema, path, LY_EINVAL); + + /* parse the path */ + ret = ly_path_parse(LYD_CTX(ctx_node), ctx_node->schema, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, + LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_SIMPLE, &expr); + LY_CHECK_GOTO(ret, cleanup); + + /* compile the path */ + ret = ly_path_compile(LYD_CTX(ctx_node), NULL, ctx_node->schema, NULL, expr, + output ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_SINGLE, 0, LY_VALUE_JSON, NULL, &lypath); + LY_CHECK_GOTO(ret, cleanup); + + /* evaluate the path */ + ret = ly_path_eval_partial(lypath, ctx_node, NULL, match); + +cleanup: + lyxp_expr_free(LYD_CTX(ctx_node), expr); + ly_path_free(LYD_CTX(ctx_node), lypath); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_find_target(const struct ly_path *path, const struct lyd_node *tree, struct lyd_node **match) +{ + LY_ERR ret; + struct lyd_node *m; + + LY_CHECK_ARG_RET(NULL, path, LY_EINVAL); + + ret = ly_path_eval(path, tree, &m); + if (ret) { + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + if (match) { + *match = m; + } + return LY_SUCCESS; +} diff --git a/src/tree_data.h b/src/tree_data.h new file mode 100644 index 0000000..ff98f60 --- /dev/null +++ b/src/tree_data.h @@ -0,0 +1,2601 @@ +/** + * @file tree_data.h + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang representation of YANG data trees. + * + * Copyright (c) 2015 - 2022 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_TREE_DATA_H_ +#define LY_TREE_DATA_H_ + +#ifdef _WIN32 +# include +# include +#else +# include +# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__) +# include +# include +# endif +#endif +#include +#include +#include + +#include "config.h" +#include "log.h" +#include "tree.h" +#include "tree_schema.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_ctx; +struct ly_path; +struct ly_set; +struct lyd_node; +struct lyd_node_opaq; +struct lyd_node_term; +struct timespec; +struct lyxp_var; + +/** + * @page howtoData Data Instances + * + * All the nodes in data tree comes are based on ::lyd_node structure. According to the content of the ::lyd_node.schema + * it can be cast to several other structures. + * + * In case the ::lyd_node.schema pointer is NULL, the node is actually __opaq__ and can be safely cast to ::lyd_node_opaq. + * The opaq node represent an unknown node which wasn't mapped to any [(compiled) schema](@ref howtoSchema) node in the + * context. Such a node can appear in several places in the data tree. + * - As a part of the tree structure, but only in the case the ::LYD_PARSE_OPAQ option was used when input data were + * [parsed](@ref howtoDataParsers), because unknown data instances are ignored by default. The same way, the opaq nodes can + * appear as a node's attributes. + * - As a representation of YANG anydata/anyxml content. + * - As envelopes of standard data tree instances (RPCs, actions or Notifications). + * + * In case the data node has its definition in a [compiled schema tree](@ref howtoSchema), the structure of the data node is + * actually one of the followings according to the schema node's nodetype (::lysc_node.nodetype). + * - ::lyd_node_inner - represents data nodes corresponding to schema nodes matching ::LYD_NODE_INNER nodetypes. They provide + * structure of the tree by having children nodes. + * - ::lyd_node_term - represents data nodes corresponding to schema nodes matching ::LYD_NODE_TERM nodetypes. The terminal + * nodes provide values of the particular configuration/status information. The values are represented as ::lyd_value + * structure with string representation of the value (retrieved by ::lyd_get_value() and ::lyd_get_meta_value()) and + * the type specific data stored in the structure's union according to the real type of the value (::lyd_value.realtype). + * The string representation provides canonical representation of the value in case the type has the canonical + * representation specified. Otherwise, it is the original value or, in case the value can contain prefixes, the JSON + * format is used to make the value unambiguous. + * - ::lyd_node_any - represents data nodes corresponding to schema nodes matching ::LYD_NODE_ANY nodetypes. + * + * Despite all the aforementioned structures and their members are available as part of the libyang API and callers can use + * it to navigate through the data tree structure or to obtain various information, we recommend to use the following macros + * and functions. + * - ::lyd_child() (or ::lyd_child_no_keys()) and ::lyd_parent() to get the node's child/parent node. + * - ::LYD_CTX to get libyang context from a data node. + * - ::lyd_get_value()/::lyd_get_meta_value() to get canonical string value from a terminal node/metadata instance. + * - ::LYD_TREE_DFS_BEGIN and ::LYD_TREE_DFS_END to traverse the data tree (depth-first). + * - ::LY_LIST_FOR and ::LY_ARRAY_FOR as described on @ref howtoStructures page. + * + * Instead of going through the data tree on your own, a specific data node can be also located using a wide set of + * \b lyd_find_*() functions. + * + * More information about specific operations with data instances can be found on the following pages: + * - @subpage howtoDataParsers + * - @subpage howtoDataValidation + * - @subpage howtoDataWD + * - @subpage howtoDataManipulation + * - @subpage howtoDataPrinters + * - @subpage howtoDataLYB + * + * \note API for this group of functions is described in the [Data Instances module](@ref datatree). + * + * Functions List (not assigned to above subsections) + * -------------------------------------------------- + * - ::lyd_child() + * - ::lyd_child_no_keys() + * - ::lyd_parent() + * - ::lyd_owner_module() + * - ::lyd_get_value() + * - ::lyd_get_meta_value() + * - ::lyd_find_xpath() + * - ::lyd_find_path() + * - ::lyd_find_target() + * - ::lyd_find_sibling_val() + * - ::lyd_find_sibling_first() + * - ::lyd_find_sibling_opaq_next() + * - ::lyd_find_meta() + * + * - ::lyd_path() + * - ::lyd_target() + * + * - ::lyd_lyb_data_length() + * + * + * @section howtoDataMetadata Metadata Support + * + * YANG Metadata annotations are defined in [RFC 7952](https://tools.ietf.org/html/rfc7952) as YANG extension (and libyang + * [implements them as internal extension plugin](@ref howtoPluginsExtensions)). In practice, it allows to have XML + * attributes (there is also a special encoding for JSON) in YANG modeled data. libyang does not allow to have any XML + * attribute without the appropriate annotation definition describing the data as it is done e.g. for leafs. When an + * attribute without a matching annotation definition is found in the input data, it is: + * - silently dropped (with warning) or + * - an error is reported in case the ::LYD_PARSE_STRICT parser option is provided to the + * [parser function](@ref howtoDataParsers) or + * - stored into a generic ::lyd_attr structure without a connection with any YANG module in case the ::LYD_PARSE_OPAQ + * parser options is provided to the [parser function](@ref howtoDataParsers). + * + * There are some XML attributes, described by [YANG](https://tools.ietf.org/html/rfc7950) and + * [NETCONF](https://tools.ietf.org/html/rfc6241) specifications, which are not defined as annotations, but libyang + * implements them this way. In case of attributes in the YANG namespace (`insert`, `value` and `key` attributes + * for the NETCONF edit-config operation), they are defined in special libyang's internal module `yang`, which is + * available in each context and the content of this schema can be printed via + * [schema printers](@ref howtoSchemaPrinters). + * + * In case of the attributes described in [NETCONF specification](https://tools.ietf.org/html/rfc6241), the libyang's + * annotations structures are hidden and cannot be printed despite, internally, they are part of the `ietf-netconf`'s + * schema structure. Therefore, these attributes are available only when the `ietf-netconf` schema is loaded in the + * context. The definitions of these annotations are as follows: + * + * md:annotation operation { + * type enumeration { + * enum merge; + * enum replace; + * enum create; + * enum delete; + * enum remove; + * } + * } + * + * md:annotation type { + * type enumeration { + * enum subtree; + * enum xpath { + * if-feature "nc:xpath"; + * } + * } + * } + * + * md:annotation select { + * type string; + * } + * + * Note, that, following the specification, + * - the `type` and `select` XML attributes are supposed to be unqualified (without namespace) and that + * - the `select`'s content is XPath and it is internally transformed by libyang into the format where the + * XML namespace prefixes are replaced by the YANG module names. + * + * + * @section howtoDataYangdata yang-data Support + * + * [RFC 8040](https://tools.ietf.org/html/rfc8040) defines ietf-restconf module, which includes yang-data extension. Despite + * the definition in the RESTCONF YANG module, the yang-data concept is quite generic and used even in modules without a + * connection to RESTCONF protocol. The extension allows to define a separated YANG trees usable separately from any + * datastore. + * + * libyang implements support for yang-data internally as an [extension plugin](@ref howtoPluginsExtensions). To ease the + * use of yang-data with libyang, there are several generic functions, which are usable for yang-data: + * + * - ::lyd_parse_ext_data() + * - ::lyd_parse_ext_op() + * + * - ::lys_getnext_ext() + * + * - ::lyd_new_ext_inner() + * - ::lyd_new_ext_list() + * - ::lyd_new_ext_term() + * - ::lyd_new_ext_any() + * - ::lyd_new_ext_path() + * + * @section howtoDataMountpoint mount-point Support + * + * [RFC 8528](https://tools.ietf.org/html/rfc8528) defines mount-point extension in ietf-yang-schema-mount YANG module. + * This extension is supported out-of-the-box but to be able to parse data in a mount point, additional run-time data + * need to be provided by a callback: + * + * - ::ly_ctx_set_ext_data_clb() + * + * The mounted data can be parsed directly from data files or created manually using the standard functions. However, + * note that the mounted data use **their own context** created as needed. For *inline* data this means that any new + * request for a mount-point schema node results in a new context creation because it is impossible to determine + * whether any existing context can be used. Also, all these contexts created for the mounted data are **never** + * freed automatically except when the parent context is being freed. So, to avoid redundant context creation, it is + * always advised to use *shared-schema* for mount-points. + * + * In case it is not possible and *inline* mount point must be defined, it is still possible to avoid creating + * additional contexts. When the top-level node right under a schema node with a mount-point is created, always use + * this node for creation of any descendants. So, when using ::lyd_new_path(), use the node as `parent` and specify + * relative `path`. + */ + +/** + * @page howtoDataManipulation Manipulating Data + * + * There are many functions to create or modify an existing data tree. You can add new nodes, reconnect nodes from + * one tree to another (or e.g. from one list instance to another) or remove nodes. The functions doesn't allow you + * to put a node to a wrong place (by checking the YANG module structure), but not all validation checks can be made directly + * (or you have to make a valid change by multiple tree modifications) when the tree is being changed. Therefore, + * the [validation process](@ref howtoDataValidation) is expected to be invoked after changing the data tree to make sure + * that the changed data tree is valid. + * + * When inserting a node into data tree (no matter if the node already exists, via ::lyd_insert_child() and + * ::lyd_insert_sibling(), or a new node is being created), the node is automatically inserted to the place respecting the + * nodes order from the YANG schema. So the node is not inserted to the end or beginning of the siblings list, but after the + * existing instance of the closest preceding sibling node from the schema. In case the node is opaq (it is not connected + * with any schema node), it is placed to the end of the sibling node in the order they are inserted in. The only situation + * when it is possible to influence the order of the nodes is the order of user-ordered list/leaf-list instances. In such + * a case the ::lyd_insert_after() or ::lyd_insert_before() can be used. + * + * Creating data is generally possible in two ways, they can be combined. You can add nodes one-by-one based on + * the node name and/or its parent (::lyd_new_inner(), ::lyd_new_term(), ::lyd_new_any(), ::lyd_new_list(), ::lyd_new_list2() + * and ::lyd_new_opaq()) or address the nodes using a [simple XPath addressing](@ref howtoXPath) (::lyd_new_path() and + * ::lyd_new_path2()). The latter enables to create a whole path of nodes, requires less information + * about the modified data, and is generally simpler to use. Actually the third way is duplicating the existing data using + * ::lyd_dup_single(), ::lyd_dup_siblings() and ::lyd_dup_meta_single(). + * + * Note, that in case the node is defined in an extension instance, the functions mentioned above do not work until you + * provide parent where the new node is supposed to be inserted. The reason is that all the functions searches for the + * top-level nodes directly inside modules. To create a top-level node defined in an extension instance, use + * ::lyd_new_ext_inner(), ::lyd_new_ext_term(), ::lyd_new_ext_any(), ::lyd_new_ext_list() and ::lyd_new_ext_path() + * functions. + * + * The [metadata](@ref howtoDataMetadata) (and attributes in opaq nodes) can be created with ::lyd_new_meta() + * and ::lyd_new_attr(). + * + * Changing value of a terminal node (leaf, leaf-list) is possible with ::lyd_change_term(). Similarly, the metadata value + * can be changed with ::lyd_change_meta(). Before changing the value, it might be useful to compare the node's value + * with a string value (::lyd_value_compare()) or verify that the new string value is correct for the specific data node + * (::lyd_value_validate()). + * + * Working with two existing subtrees can also be performed two ways. Usually, you would use lyd_insert*() functions. + * They are generally meant for simple inserts of a node into a data tree. For more complicated inserts and when + * merging 2 trees use ::lyd_merge_tree() or ::lyd_merge_siblings(). It offers additional options and is basically a more + * powerful insert. + * + * Besides merging, libyang is also capable to provide information about differences between two data trees. For this purpose, + * ::lyd_diff_tree() and ::lyd_diff_siblings() generates annotated data trees which can be, in addition, used to change one + * data tree to another one using ::lyd_diff_apply_all(), ::lyd_diff_apply_module() and ::lyd_diff_reverse_all(). Multiple + * diff data trees can be also put together for further work using ::lyd_diff_merge_all(), ::lyd_diff_merge_module() and + * ::lyd_diff_merge_tree() functions. To just check equivalence of the data nodes, ::lyd_compare_single(), + * ::lyd_compare_siblings() and ::lyd_compare_meta() can be used. + * + * To remove a node or subtree from a data tree, use ::lyd_unlink_tree() and then free the unwanted data using + * ::lyd_free_all() (or other \b lyd_free_*() functions). + * + * Also remember, that when you are creating/inserting a node, all the objects in that operation must belong to the + * same context. + * + * Modifying the single data tree in multiple threads is not safe. + * + * Functions List + * -------------- + * - ::lyd_new_inner() + * - ::lyd_new_term() + * - ::lyd_new_term_bin() + * - ::lyd_new_term_canon() + * - ::lyd_new_list() + * - ::lyd_new_list_bin() + * - ::lyd_new_list_canon() + * - ::lyd_new_list2() + * - ::lyd_new_any() + * - ::lyd_new_opaq() + * - ::lyd_new_opaq2() + * - ::lyd_new_attr() + * - ::lyd_new_attr2() + * - ::lyd_new_meta() + * - ::lyd_new_path() + * - ::lyd_new_path2() + * + * - ::lyd_new_ext_inner() + * - ::lyd_new_ext_term() + * - ::lyd_new_ext_list() + * - ::lyd_new_ext_any() + * - ::lyd_new_ext_path() + * + * - ::lyd_dup_single() + * - ::lyd_dup_siblings() + * - ::lyd_dup_meta_single() + * + * - ::lyd_insert_child() + * - ::lyd_insert_sibling() + * - ::lyd_insert_after() + * - ::lyd_insert_before() + * + * - ::lyd_value_compare() + * - ::lyd_value_validate() + * + * - ::lyd_change_term() + * - ::lyd_change_term_bin() + * - ::lyd_change_term_canon() + * - ::lyd_change_meta() + * + * - ::lyd_compare_single() + * - ::lyd_compare_siblings() + * - ::lyd_compare_meta() + * - ::lyd_diff_tree() + * - ::lyd_diff_siblings() + * - ::lyd_diff_apply_all() + * - ::lyd_diff_apply_module() + * - ::lyd_diff_reverse_all() + * - ::lyd_diff_merge_all() + * - ::lyd_diff_merge_module() + * - ::lyd_diff_merge_tree() + * + * - ::lyd_merge_tree() + * - ::lyd_merge_siblings() + * - ::lyd_merge_module() + * + * - ::lyd_unlink_tree() + * + * - ::lyd_free_all() + * - ::lyd_free_siblings() + * - ::lyd_free_tree() + * - ::lyd_free_meta_single() + * - ::lyd_free_meta_siblings() + * - ::lyd_free_attr_single() + * - ::lyd_free_attr_siblings() + * + * - ::lyd_any_value_str() + * - ::lyd_any_copy_value() + */ + +/** + * @page howtoDataWD Default Values + * + * libyang provides support for work with default values as defined in [RFC 6243](https://tools.ietf.org/html/rfc6243). + * However, libyang context do not contains the *ietf-netconf-with-defaults* module on its own and caller is supposed to + * add this YANG module to enable full support of the *with-defaults* features described below. Without presence of the + * mentioned module in the context, the default nodes are still present and handled in the data trees, but the metadata + * providing the information about the default values cannot be used. It means that when parsing data, the default nodes + * marked with the metadata as implicit default nodes are handled as explicit data and when printing data tree, the expected + * nodes are printed without the ietf-netconf-with-defaults metadata. + * + * The RFC document defines 4 modes for handling default nodes in a data tree, libyang adds the fifth mode and use them + * via @ref dataprinterflags when printing data trees. + * - \b explicit - Only the explicitly set configuration data. But in the case of status data, missing default + * data are added into the tree. In libyang, this mode is represented by ::LYD_PRINT_WD_EXPLICIT option. + * This is the default with-defaults mode of the printer. The data nodes do not contain any additional + * metadata information. + * - \b trim - Data nodes containing the default value are removed. This mode is applied with ::LYD_PRINT_WD_TRIM option. + * - \b report-all - This mode provides all the default data nodes despite they were explicitly present in source data or + * they were added by libyang's [validation process](@ref howtoDataValidation). This mode is activated by + * ::LYD_PRINT_WD_ALL option. + * - \b report-all-tagged - In this case, all the data nodes (implicit as well the explicit) containing the default value + * are printed and tagged (see the note below). Printers accept ::LYD_PRINT_WD_ALL_TAG option for this mode. + * - \b report-implicit-tagged - The last mode is similar to the previous one, except only the implicitly added nodes + * are tagged. This is the libyang's extension and it is activated by ::LYD_PRINT_WD_IMPL_TAG option. + * + * Internally, libyang adds the default nodes into the data tree as part of the [validation process](@ref howtoDataValidation). + * When [parsing data](@ref howtoDataParsers) from an input source, adding default nodes can be avoided only by avoiding + * the whole [validation process](@ref howtoDataValidation). In case the ietf-netconf-with-defaults module is present in the + * context, the [parser process](@ref howtoDataParsers) also supports to recognize the implicit default nodes marked with the + * appropriate metadata. + * + * Note, that in a modified data tree (via e.g. \b lyd_insert_*() or \b lyd_free_*() functions), some of the default nodes + * can be missing or they can be present by mistake. Such a data tree is again corrected during the next run of the + * [validation process](@ref howtoDataValidation) or manualy using \b lyd_new_implicit_*() functions. + * + * The implicit (default) nodes, created by libyang, are marked with the ::LYD_DEFAULT flag in ::lyd_node.flags member + * Note, that besides leafs and leaf-lists, the flag can appear also in containers, where it means that the container + * holds only a default node(s) or it is implicitly added empty container (according to YANG 1.1 spec, all such containers are part of + * the accessible data tree). When printing data trees, the presence of empty containers (despite they were added + * explicitly or implicitly as part of accessible data tree) depends on ::LYD_PRINT_KEEPEMPTYCONT option. + * + * To get know if the particular leaf or leaf-list node contains default value (despite implicit or explicit), you can + * use ::lyd_is_default() function. + * + * Functions List + * -------------- + * - ::lyd_is_default() + * - ::lyd_new_implicit_all() + * - ::lyd_new_implicit_module() + * - ::lyd_new_implicit_tree() + */ + +/** + * @page howtoDataLYB LYB Binary Format + * + * LYB (LibYang Binary) is a proprietary libyang binary data and file format. Its primary purpose is efficient + * serialization (printing) and deserialization (parsing). With this goal in mind, every term node value is stored + * in its new binary format specification according to its type. Following is the format for all types with explicit + * support out-of-the-box (meaning that have a special type plugin). Any derived types inherit the format of its + * closest type with explicit support (up to a built-in type). + * + * @section howtoDataLYBTypes Format of specific data type values + */ + +/** + * @ingroup trees + * @defgroup datatree Data Tree + * @{ + * + * Data structures and functions to manipulate and access instance data tree. + */ + +/* *INDENT-OFF* */ + +/** + * @brief Macro to iterate via all elements in a data tree. This is the opening part + * to the #LYD_TREE_DFS_END - they always have to be used together. + * + * The function follows deep-first search algorithm: + *
+ *     1
+ *    / \
+ *   2   4
+ *  /   / \
+ * 3   5   6
+ * 
+ * + * Use the same parameters for #LYD_TREE_DFS_BEGIN and #LYD_TREE_DFS_END. While + * START can be any of the lyd_node* types, ELEM variable must be a pointer to + * the generic struct lyd_node. + * + * To skip a particular subtree, instead of the continue statement, set LYD_TREE_DFS_continue + * variable to non-zero value. + * + * Use with opening curly bracket '{' after the macro. + * + * @param START Pointer to the starting element processed first. + * @param ELEM Iterator intended for use in the block. + */ +#define LYD_TREE_DFS_BEGIN(START, ELEM) \ + { ly_bool LYD_TREE_DFS_continue = 0; struct lyd_node *LYD_TREE_DFS_next; \ + for ((ELEM) = (LYD_TREE_DFS_next) = (struct lyd_node *)(START); \ + (ELEM); \ + (ELEM) = (LYD_TREE_DFS_next), LYD_TREE_DFS_continue = 0) + +/** + * @brief Macro to iterate via all elements in a tree. This is the closing part + * to the #LYD_TREE_DFS_BEGIN - they always have to be used together. + * + * Use the same parameters for #LYD_TREE_DFS_BEGIN and #LYD_TREE_DFS_END. While + * START can be any of the lyd_node* types, ELEM variable must be a pointer + * to the generic struct lyd_node. + * + * Use with closing curly bracket '}' after the macro. + * + * @param START Pointer to the starting element processed first. + * @param ELEM Iterator intended for use in the block. + */ + +#define LYD_TREE_DFS_END(START, ELEM) \ + /* select element for the next run - children first */ \ + if (LYD_TREE_DFS_continue) { \ + (LYD_TREE_DFS_next) = NULL; \ + } else { \ + (LYD_TREE_DFS_next) = lyd_child(ELEM); \ + }\ + if (!(LYD_TREE_DFS_next)) { \ + /* no children */ \ + if ((ELEM) == (struct lyd_node *)(START)) { \ + /* we are done, (START) has no children */ \ + break; \ + } \ + /* try siblings */ \ + (LYD_TREE_DFS_next) = (ELEM)->next; \ + } \ + while (!(LYD_TREE_DFS_next)) { \ + /* parent is already processed, go to its sibling */ \ + (ELEM) = (struct lyd_node *)(ELEM)->parent; \ + /* no siblings, go back through parents */ \ + if ((ELEM)->parent == (START)->parent) { \ + /* we are done, no next element to process */ \ + break; \ + } \ + (LYD_TREE_DFS_next) = (ELEM)->next; \ + } } + +/** + * @brief Macro to iterate via all schema node data instances in data siblings. + * + * @param START Pointer to the starting sibling. Even if it is not first, all the siblings are searched. + * @param SCHEMA Schema node of the searched instances. + * @param ELEM Iterator. + */ +#define LYD_LIST_FOR_INST(START, SCHEMA, ELEM) \ + for (lyd_find_sibling_val(START, SCHEMA, NULL, 0, &(ELEM)); \ + (ELEM) && ((ELEM)->schema == (SCHEMA)); \ + (ELEM) = (ELEM)->next) + +/** + * @brief Macro to iterate via all schema node data instances in data siblings allowing to modify the list itself. + * + * @param START Pointer to the starting sibling. Even if it is not first, all the siblings are searched. + * @param SCHEMA Schema node of the searched instances. + * @param NEXT Temporary storage to allow removing of the current iterator content. + * @param ELEM Iterator. + */ +#define LYD_LIST_FOR_INST_SAFE(START, SCHEMA, NEXT, ELEM) \ + for ((NEXT) = (ELEM) = NULL, lyd_find_sibling_val(START, SCHEMA, NULL, 0, &(ELEM)); \ + (ELEM) && ((ELEM)->schema == (SCHEMA)) ? ((NEXT) = (ELEM)->next, 1) : 0; \ + (ELEM) = (NEXT)) + +/* *INDENT-ON* */ + +/** + * @brief Macro to get context from a data tree node. + */ +#define LYD_CTX(node) ((node)->schema ? (node)->schema->module->ctx : ((const struct lyd_node_opaq *)(node))->ctx) + +/** + * @brief Data input/output formats supported by libyang [parser](@ref howtoDataParsers) and + * [printer](@ref howtoDataPrinters) functions. + */ +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_FORMAT; + +/** + * @brief List of possible value types stored in ::lyd_node_any. + */ +typedef enum { + LYD_ANYDATA_DATATREE, /**< Value is a pointer to ::lyd_node structure (first sibling). When provided as input parameter, the pointer + is directly connected into the anydata node without duplication, caller is supposed to not manipulate + with the data after a successful call (including calling ::lyd_free_all() on the provided data) */ + LYD_ANYDATA_STRING, /**< Value is a generic string without any knowledge about its format (e.g. anyxml value in JSON encoded + as string). XML sensitive characters (such as & or \>) are automatically 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_LYB /**< Value is a memory chunk with the serialized data tree in LYB format. */ +} LYD_ANYDATA_VALUETYPE; + +/** @} */ + +/** + * @brief YANG data representation + */ +struct lyd_value { + const char *_canonical; /**< Should never be accessed directly, instead ::lyd_get_value() and ::lyd_get_meta_value() + should be used. Serves as a cache for the canonical value or the JSON + representation if no canonical value is defined. */ + const struct lysc_type *realtype; /**< pointer to the real type of the data stored in the value structure. This type can differ from the type + in the schema node of the data node since the type's store plugin can use other types/plugins for + storing data. Speaking about built-in types, this is the case of leafref which stores data as its + target type. In contrast, union type also uses its subtype's callbacks, but inside an internal data + stored in subvalue member of ::lyd_value structure, so here is the pointer to the union type. + In general, this type is used to get free callback for this lyd_value structure, so it must reflect + the type used to store data directly in the same lyd_value instance. */ + + union { + int8_t boolean; /**< 0 as false, 1 as true */ + int64_t dec64; /**< decimal64: value = dec64 / 10^fraction-digits */ + int8_t int8; /**< 8-bit signed integer */ + int16_t int16; /**< 16-bit signed integer */ + int32_t int32; /**< 32-bit signed integer */ + int64_t int64; /**< 64-bit signed integer */ + uint8_t uint8; /**< 8-bit unsigned integer */ + uint16_t uint16; /**< 16-bit unsigned integer */ + uint32_t uint32; /**< 32-bit unsigned integer */ + uint64_t uint64; /**< 64-bit unsigned integer */ + struct lysc_type_bitenum_item *enum_item; /**< pointer to the definition of the enumeration value */ + struct lysc_ident *ident; /**< pointer to the schema definition of the identityref value */ + struct ly_path *target; /**< Instance-identifier target path, use ::lyd_find_target() to evaluate + it on data. */ + struct lyd_value_union *subvalue; /** Union value with some metadata. */ + + void *dyn_mem; /**< pointer to generic data type value stored in dynamic memory */ + uint8_t fixed_mem[LYD_VALUE_FIXED_MEM_SIZE]; /**< fixed-size buffer for a generic data type value */ + }; /**< The union is just a list of shorthands to possible values stored by a type's plugin. libyang itself uses the ::lyd_value.realtype + plugin's callbacks to work with the data.*/ +}; + +/** + * @brief Get the value in format specific to the type. + * + * Should be used for any types that do not have their specific representation in the ::lyd_value union. + * + * @param[in] value Pointer to the value structure to read from (struct ::lyd_value *). + * @param[out] type_val Pointer to the type-specific value structure. + */ +#define LYD_VALUE_GET(value, type_val) \ + ((sizeof *(type_val) > LYD_VALUE_FIXED_MEM_SIZE) \ + ? ((type_val) = (((value)->dyn_mem))) \ + : ((type_val) = ((void *)((value)->fixed_mem)))) + +/** + * @brief Special lyd_value structure for built-in union values. + * + * Represents data with multiple types (union). The ::lyd_value_union.value contains representation according to + * one of the union's types. The ::lyd_value_union.prefix_data provides (possible) mappings from prefixes in + * the original value to YANG modules. These prefixes are necessary to parse original value to the union's subtypes. + */ +struct lyd_value_union { + struct lyd_value value; /**< representation of the value according to the selected union's subtype + (stored as ::lyd_value.realtype here) */ + void *original; /**< Original value. */ + size_t orig_len; /**< Original value length. */ + uint32_t hints; /**< [Value hints](@ref lydvalhints) from the parser */ + LY_VALUE_FORMAT format; /**< Prefix format of the value. However, this information is also used to decide + whether a value is valid for the specific format or not on later validations + (instance-identifier in XML looks different than in JSON). */ + void *prefix_data; /**< Format-specific data for prefix resolution (see ly_resolve_prefix()) */ + const struct lysc_node *ctx_node; /**< Context schema node. */ +}; + +/** + * @brief Special lyd_value structure for built-in bits values. + */ +struct lyd_value_bits { + char *bitmap; /**< bitmap of size ::lyplg_type_bits_bitmap_size(), if its value is + cast to an integer type of the corresponding size, can be used + directly as a bitmap */ + struct lysc_type_bitenum_item **items; /**< list of set pointers to the specification of the set + bits ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief Special lyd_value structure for built-in binary values. + */ +struct lyd_value_binary { + void *data; /**< pointer to the binary value */ + size_t size; /**< size of @p data value in bytes */ +}; + +/** + * @brief Special lyd_value structure for ietf-inet-types ipv4-address-no-zone values. + */ +struct lyd_value_ipv4_address_no_zone { + struct in_addr addr; /**< IPv4 address in binary */ +}; + +/** + * @brief Special lyd_value structure for ietf-inet-types ipv4-address values. + */ +struct lyd_value_ipv4_address { + struct in_addr addr; /**< IPv4 address in binary */ + const char *zone; /**< Optional address zone */ +}; + +/** + * @brief Special lyd_value structure for ietf-inet-types ipv4-prefix values. + */ +struct lyd_value_ipv4_prefix { + struct in_addr addr; /**< IPv4 host address in binary */ + uint8_t prefix; /**< prefix length (0 - 32) */ +}; + +/** + * @brief Special lyd_value structure for ietf-inet-types ipv6-address-no-zone values. + */ +struct lyd_value_ipv6_address_no_zone { + struct in6_addr addr; /**< IPv6 address in binary */ +}; + +/** + * @brief Special lyd_value structure for ietf-inet-types ipv6-address values. + */ +struct lyd_value_ipv6_address { + struct in6_addr addr; /**< IPv6 address in binary */ + const char *zone; /**< Optional address zone */ +}; + +/** + * @brief Special lyd_value structure for ietf-inet-types ipv6-prefix values. + */ +struct lyd_value_ipv6_prefix { + struct in6_addr addr; /**< IPv6 host address in binary */ + uint8_t prefix; /**< prefix length (0 - 128) */ +}; + +/** + * @brief Special lyd_value structure for ietf-yang-types date-and-time values. + */ +struct lyd_value_date_and_time { + time_t time; /**< UNIX timestamp */ + char *fractions_s; /**< Optional fractions of a second */ + ly_bool unknown_tz; /**< Whether the value is in the special -00:00 timezone. */ +}; + +/** + * @brief Special lyd_value structure for ietf-yang-types xpath1.0 values. + */ +struct lyd_value_xpath10 { + struct lyxp_expr *exp; + const struct ly_ctx *ctx; + void *prefix_data; + LY_VALUE_FORMAT format; +}; + +/** + * @brief Generic prefix and namespace mapping, meaning depends on the format. + * + * The union is used as a reference to the data's module and according to the format, it can be used as a key for + * ::ly_ctx_get_module_implemented_ns() or ::ly_ctx_get_module_implemented(). While the module reference is always present, + * the prefix member can be omitted in case it is not present in the source data as a reference to the default module/namespace. + */ +struct ly_opaq_name { + const char *name; /**< node name, without prefix if any was defined */ + const char *prefix; /**< identifier used in the qualified name as the prefix, can be NULL */ + + union { + const char *module_ns; /**< format ::LY_VALUE_XML - XML namespace of the node element */ + const char *module_name; /**< format ::LY_VALUE_JSON - (inherited) name of the module of the element */ + }; +}; + +/** + * @brief Generic attribute structure. + */ +struct lyd_attr { + struct lyd_node_opaq *parent; /**< data node where the attribute is placed */ + struct lyd_attr *next; /**< pointer to the next attribute */ + struct ly_opaq_name name; /**< attribute name with module information */ + const char *value; /**< attribute value */ + uint32_t hints; /**< additional information about from the data source, see the [hints list](@ref lydhints) */ + LY_VALUE_FORMAT format; /**< format of the attribute and any prefixes, ::LY_VALUE_XML or ::LY_VALUE_JSON */ + void *val_prefix_data; /**< format-specific prefix data */ +}; + +#define LYD_NODE_INNER (LYS_CONTAINER|LYS_LIST|LYS_RPC|LYS_ACTION|LYS_NOTIF) /**< Schema nodetype mask for lyd_node_inner */ +#define LYD_NODE_TERM (LYS_LEAF|LYS_LEAFLIST) /**< Schema nodetype mask for lyd_node_term */ +#define LYD_NODE_ANY (LYS_ANYDATA) /**< Schema nodetype mask for lyd_node_any */ + +/** + * @ingroup datatree + * @defgroup dnodeflags Data node flags + * @{ + * + * Various flags of data nodes. + * + * 1 - container 5 - anydata/anyxml + * 2 - list 6 - rpc/action + * 3 - leaf 7 - notification + * 4 - leaflist + * + * bit name 1 2 3 4 5 6 7 + * ---------------------+-+-+-+-+-+-+-+ + * 1 LYD_DEFAULT |x| |x|x| | | | + * +-+-+-+-+-+-+-+ + * 2 LYD_WHEN_TRUE |x|x|x|x|x| | | + * +-+-+-+-+-+-+-+ + * 3 LYD_NEW |x|x|x|x|x|x|x| + * +-+-+-+-+-+-+-+ + * 4 LYD_EXT |x|x|x|x|x|x|x| + * ---------------------+-+-+-+-+-+-+-+ + * + */ + +#define LYD_DEFAULT 0x01 /**< default (implicit) node */ +#define LYD_WHEN_TRUE 0x02 /**< all when conditions of this node were evaluated to true */ +#define LYD_NEW 0x04 /**< node was created after the last validation, is needed for the next validation */ +#define LYD_EXT 0x08 /**< node is the first sibling parsed as extension instance data */ + +/** @} */ + +/** + * @brief Generic structure for a data node. + */ +struct lyd_node { + uint32_t hash; /**< hash of this particular node (module name + schema name + key string values if list or + hashes of all nodes of subtree in case of keyless list). Note that while hash can be + used to get know that nodes are not equal, it cannot be used to decide that the + nodes are equal due to possible collisions. */ + uint32_t flags; /**< [data node flags](@ref dnodeflags) */ + const struct lysc_node *schema; /**< pointer to the schema definition of this node */ + struct lyd_node_inner *parent; /**< pointer to the parent node, NULL in case of root node */ + struct lyd_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + struct lyd_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + struct lyd_meta *meta; /**< pointer to the list of metadata of this node */ + void *priv; /**< private user data, not used by libyang */ +}; + +/** + * @brief Data node structure for the inner data tree nodes - containers, lists, RPCs, actions and Notifications. + */ +struct lyd_node_inner { + union { + struct lyd_node node; /**< implicit cast for the members compatible with ::lyd_node */ + + struct { + uint32_t hash; /**< hash of this particular node (module name + schema name + key string + values if list or hashes of all nodes of subtree in case of keyless + list). Note that while hash can be used to get know that nodes are + not equal, it cannot be used to decide that the nodes are equal due + to possible collisions. */ + uint32_t flags; /**< [data node flags](@ref dnodeflags) */ + const struct lysc_node *schema; /**< pointer to the schema definition of this node */ + struct lyd_node_inner *parent; /**< pointer to the parent node, NULL in case of root node */ + struct lyd_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + struct lyd_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + struct lyd_meta *meta; /**< pointer to the list of metadata of this node */ + void *priv; /**< private user data, not used by libyang */ + }; + }; /**< common part corresponding to ::lyd_node */ + + struct lyd_node *child; /**< pointer to the first child node. */ + struct hash_table *children_ht; /**< hash table with all the direct children (except keys for a list, lists without keys) */ + +#define LYD_HT_MIN_ITEMS 4 /**< minimal number of children to create ::lyd_node_inner.children_ht hash table. */ +}; + +/** + * @brief Data node structure for the terminal data tree nodes - leaves and leaf-lists. + */ +struct lyd_node_term { + union { + struct lyd_node node; /**< implicit cast for the members compatible with ::lyd_node */ + + struct { + uint32_t hash; /**< hash of this particular node (module name + schema name + key string + values if list or hashes of all nodes of subtree in case of keyless + list). Note that while hash can be used to get know that nodes are + not equal, it cannot be used to decide that the nodes are equal due + to possible collisions. */ + uint32_t flags; /**< [data node flags](@ref dnodeflags) */ + const struct lysc_node *schema; /**< pointer to the schema definition of this node */ + struct lyd_node_inner *parent; /**< pointer to the parent node, NULL in case of root node */ + struct lyd_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + struct lyd_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + struct lyd_meta *meta; /**< pointer to the list of metadata of this node */ + void *priv; /**< private user data, not used by libyang */ + }; + }; /**< common part corresponding to ::lyd_node */ + + struct lyd_value value; /**< node's value representation */ +}; + +/** + * @brief union for anydata/anyxml value representation. + */ +union lyd_any_value { + struct lyd_node *tree; /**< data tree */ + const char *str; /**< Generic string data */ + const char *xml; /**< Serialized XML data */ + const char *json; /**< I-JSON encoded string */ + char *mem; /**< LYD_ANYDATA_LYB memory chunk */ +}; + +/** + * @brief Data node structure for the anydata data tree nodes - anydata or + * anyxml. + */ +struct lyd_node_any { + union { + struct lyd_node node; /**< implicit cast for the members compatible with ::lyd_node */ + + struct { + uint32_t hash; /**< hash of this particular node (module name + schema name + key string + values if list or hashes of all nodes of subtree in case of keyless + list). Note that while hash can be used to get know that nodes are + not equal, it cannot be used to decide that the nodes are equal due + to possible collisions. */ + uint32_t flags; /**< [data node flags](@ref dnodeflags) */ + const struct lysc_node *schema; /**< pointer to the schema definition of this node */ + struct lyd_node_inner *parent; /**< pointer to the parent node, NULL in case of root node */ + struct lyd_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + struct lyd_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + struct lyd_meta *meta; /**< pointer to the list of metadata of this node */ + void *priv; /**< private user data, not used by libyang */ + }; + }; /**< common part corresponding to ::lyd_node */ + + union lyd_any_value value; /**< pointer to the stored value representation of the anydata/anyxml node */ + LYD_ANYDATA_VALUETYPE value_type; /**< type of the data stored as ::lyd_node_any.value */ +}; + +/** + * @brief Get the name (associated with) of a data node. Works for opaque nodes as well. + * + * @param[in] node Node to examine. + * @return Data node name. + */ +#define LYD_NAME(node) ((node)->schema ? (node)->schema->name : ((struct lyd_node_opaq *)node)->name.name) + +/** + * @ingroup datatree + * @defgroup lydvalhints Value format hints. + * @{ + * + * Hints for the type of the data value. + * + * Any information about value types encoded in the format is hinted by these values. + */ +#define LYD_VALHINT_STRING 0x0001 /**< value is allowed to be a string */ +#define LYD_VALHINT_DECNUM 0x0002 /**< value is allowed to be a decimal number */ +#define LYD_VALHINT_OCTNUM 0x0004 /**< value is allowed to be an octal number */ +#define LYD_VALHINT_HEXNUM 0x0008 /**< value is allowed to be a hexadecimal number */ +#define LYD_VALHINT_NUM64 0x0010 /**< value is allowed to be an int64 or uint64 */ +#define LYD_VALHINT_BOOLEAN 0x0020 /**< value is allowed to be a boolean */ +#define LYD_VALHINT_EMPTY 0x0040 /**< value is allowed to be empty */ +/** + * @} lydvalhints + */ + +/** + * @ingroup datatree + * @defgroup lydnodehints Node type format hints + * @{ + * + * Hints for the type of the data node. + * + * Any information about node types encoded in the format is hinted by these values. + */ +#define LYD_NODEHINT_LIST 0x0080 /**< node is allowed to be a list instance */ +#define LYD_NODEHINT_LEAFLIST 0x0100 /**< node is allowed to be a leaf-list instance */ +/** + * @} lydnodehints + */ + +/** + * @ingroup datatree + * @defgroup lydhints Value and node type format hints + * @{ + * + * Hints for the types of data node and its value. + * + * Any information about value and node types encoded in the format is hinted by these values. + * It combines [value hints](@ref lydvalhints) and [node hints](@ref lydnodehints). + */ +#define LYD_HINT_DATA 0x01F3 /**< special node/value hint to be used for generic data node/value (for cases when + there is no encoding or it does not provide any additional information about + a node/value type); do not combine with specific [value hints](@ref lydvalhints) + or [node hints](@ref lydnodehints). */ +#define LYD_HINT_SCHEMA 0x01FF /**< special node/value hint to be used for generic schema node/value(for cases when + there is no encoding or it does not provide any additional information about + a node/value type); do not combine with specific [value hints](@ref lydvalhints) + or [node hints](@ref lydnodehints). */ +/** + * @} lydhints + */ + +/** + * @brief Data node structure for unparsed (opaque) nodes. + */ +struct lyd_node_opaq { + union { + struct lyd_node node; /**< implicit cast for the members compatible with ::lyd_node */ + + struct { + uint32_t hash; /**< always 0 */ + uint32_t flags; /**< always 0 */ + const struct lysc_node *schema; /**< always NULL */ + struct lyd_node_inner *parent; /**< pointer to the parent node, NULL in case of root node */ + struct lyd_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + struct lyd_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + struct lyd_meta *meta; /**< always NULL */ + void *priv; /**< private user data, not used by libyang */ + }; + }; /**< common part corresponding to ::lyd_node */ + + struct lyd_node *child; /**< pointer to the child node (compatible with ::lyd_node_inner) */ + + struct ly_opaq_name name; /**< node name with module information */ + const char *value; /**< original value */ + uint32_t hints; /**< additional information about from the data source, see the [hints list](@ref lydhints) */ + LY_VALUE_FORMAT format; /**< format of the node and any prefixes, ::LY_VALUE_XML or ::LY_VALUE_JSON */ + void *val_prefix_data; /**< format-specific prefix data */ + + struct lyd_attr *attr; /**< pointer to the list of generic attributes of this node */ + const struct ly_ctx *ctx; /**< libyang context */ +}; + +/** + * @brief Get the generic parent pointer of a data node. + * + * @param[in] node Node whose parent pointer to get. + * @return Pointer to the parent node of the @p node. + * @return NULL in case of the top-level node or if the @p node is NULL itself. + */ +static inline struct lyd_node * +lyd_parent(const struct lyd_node *node) +{ + if (!node || !node->parent) { + return NULL; + } + + return &node->parent->node; +} + +/** + * @brief Get the child pointer of a generic data node. + * + * Decides the node's type and in case it has a children list, returns it. Supports even the opaq nodes (::lyd_node_opaq). + * + * If you need to skip key children, use ::lyd_child_no_keys(). + * + * @param[in] node Node to use. + * @return Pointer to the first child node (if any) of the @p node. + */ +static inline struct lyd_node * +lyd_child(const struct lyd_node *node) +{ + if (!node) { + return NULL; + } + + if (!node->schema) { + /* opaq node */ + return ((const struct lyd_node_opaq *)node)->child; + } + + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + return ((const struct lyd_node_inner *)node)->child; + default: + return NULL; + } +} + +/** + * @brief Get the child pointer of a generic data node but skip its keys in case it is ::LYS_LIST. + * + * Decides the node's type and in case it has a children list, returns it. Supports even the opaq nodes (::lyd_node_opaq). + * + * If you need to take key children into account, use ::lyd_child(). + * + * @param[in] node Node to use. + * @return Pointer to the first child node (if any) of the @p node. + */ +LIBYANG_API_DECL struct lyd_node *lyd_child_no_keys(const struct lyd_node *node); + +/** + * @brief Get the owner module of the data node. It is the module of the top-level schema node. Generally, + * in case of augments it is the target module, recursively, otherwise it is the module where the data node is defined. + * + * Also works for opaque nodes, if it is possible to resolve the module. + * + * @param[in] node Data node to examine. + * @return Module owner of the node. + */ +LIBYANG_API_DECL const struct lys_module *lyd_owner_module(const struct lyd_node *node); + +/** + * @brief Check whether a node value equals to its default one. + * + * @param[in] node Term node to test. + * @return false (no, it is not a default node) or true (yes, it is default) + */ +LIBYANG_API_DECL ly_bool lyd_is_default(const struct lyd_node *node); + +/** + * @brief Learn the relative position of a list or leaf-list instance within other instances of the same schema node. + * + * @param[in] instance List or leaf-list instance to get the position of. + * @return 0 on error. + * @return Positive integer of the @p instance position. + */ +LIBYANG_API_DECL uint32_t lyd_list_pos(const struct lyd_node *instance); + +/** + * @brief Get the first sibling of the given node. + * + * @param[in] node Node which first sibling is going to be the result. + * @return The first sibling of the given node or the node itself if it is the first child of the parent. + */ +LIBYANG_API_DECL struct lyd_node *lyd_first_sibling(const struct lyd_node *node); + +/** + * @brief Learn the length of LYB data. + * + * @param[in] data LYB data to examine. + * @return Length of the LYB data chunk, + * @return -1 on error. + */ +LIBYANG_API_DECL int lyd_lyb_data_length(const char *data); + +/** + * @brief Check node parsed into an opaque node for the reason (error) why it could not be parsed as data node. + * + * The node is expected to be produced by a parser and must either have no parent or a data node parent (not opaque). + * + * @param[in] node Opaque node to check. + * @return LY_EINVAL if @p node is in some way unexpected (even valid); + * @return LY_ERR value of the reason. + */ +LIBYANG_API_DECL LY_ERR lyd_parse_opaq_error(const struct lyd_node *node); + +/** + * @brief Get the (canonical) value of a lyd_value. + * + * Whenever possible, ::lyd_get_value() or ::lyd_get_meta_value() should be used instead. + * + * @param[in] ctx Context for the value + * @param[in] value Value structure to use. + * @return Canonical value. + */ +LIBYANG_API_DECL const char *lyd_value_get_canonical(const struct ly_ctx *ctx, const struct lyd_value *value); + +/** + * @brief Get the (canonical) value of a data node. + * + * @param[in] node Data node to use. + * @return Canonical value. + */ +static inline const char * +lyd_get_value(const struct lyd_node *node) +{ + if (!node) { + return NULL; + } + + if (!node->schema) { + return ((const struct lyd_node_opaq *)node)->value; + } else if (node->schema->nodetype & LYD_NODE_TERM) { + const struct lyd_value *value = &((const struct lyd_node_term *)node)->value; + + return value->_canonical ? value->_canonical : lyd_value_get_canonical(LYD_CTX(node), value); + } + + return NULL; +} + +/** + * @brief Get anydata string value. + * + * @param[in] any Anyxml/anydata node to read from. + * @param[out] value_str String representation of the value. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_any_value_str(const struct lyd_node *any, char **value_str); + +/** + * @brief Copy anydata value from one node to another. Target value is freed first. + * + * @param[in,out] trg Target node. + * @param[in] value Source value, may be NULL when the target value is only freed. + * @param[in] value_type Source value type. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_any_copy_value(struct lyd_node *trg, const union lyd_any_value *value, + LYD_ANYDATA_VALUETYPE value_type); + +/** + * @brief Create a new inner node in the data tree. + * + * To create list, use ::lyd_new_list() or ::lyd_new_list2(). + * + * To create a top-level inner node defined in an extension instance, use ::lyd_new_ext_inner(). + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node can be #LYS_CONTAINER, #LYS_NOTIF, #LYS_RPC, or #LYS_ACTION. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_inner(struct lyd_node *parent, const struct lys_module *module, const char *name, + ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new top-level inner node defined in the given extension instance. + * + * To create list, use ::lyd_new_list() or ::lyd_new_list2(). + * + * To create an inner node with parent (no matter if defined inside extension instance or a standard tree) or a top-level + * node of a standard module's tree, use ::lyd_new_inner(). + * + * @param[in] ext Extension instance where the inner node being created is defined. + * @param[in] name Schema node name of the new data node. The node can be #LYS_CONTAINER, #LYS_NOTIF, #LYS_RPC, or #LYS_ACTION. + * @param[out] node The created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_ext_inner(const struct lysc_ext_instance *ext, const char *name, struct lyd_node **node); + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @param[in] ... Ordered key values of the new list instance, all must be set. In case of an instance-identifier + * or identityref value, the JSON format is expected (module names instead of prefixes). No keys are expected for + * key-less lists. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const char *name, + ly_bool output, struct lyd_node **node, ...); + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @param[in] ... Ordered binary key values of the new list instance, all must be set. Every key value must be followed + * by its length. No keys are expected for key-less lists. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list_bin(struct lyd_node *parent, const struct lys_module *module, const char *name, + ly_bool output, struct lyd_node **node, ...); + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @param[in] ... Ordered canonical key values of the new list instance, all must be set. No keys are expected for + * key-less lists. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list_canon(struct lyd_node *parent, const struct lys_module *module, const char *name, + ly_bool output, struct lyd_node **node, ...); + +/** + * @brief Create a new top-level list node defined in the given extension instance. + * + * To create a list node with parent (no matter if defined inside extension instance or a standard tree) or a top-level + * list node of a standard module's tree, use ::lyd_new_list() or ::lyd_new_list2(). + * + * @param[in] ext Extension instance where the list node being created is defined. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[out] node The created node. + * @param[in] ... Ordered key values of the new list instance, all must be set. In case of an instance-identifier + * or identityref value, the JSON format is expected (module names instead of prefixes). No keys are expected for + * key-less lists. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_ext_list(const struct lysc_ext_instance *ext, const char *name, struct lyd_node **node, ...); + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] keys All key values predicate in the form of "[key1='val1'][key2='val2']...", they do not have to be ordered. + * In case of an instance-identifier or identityref value, the JSON format is expected (module names instead of prefixes). + * Use NULL or string of length 0 in case of key-less list. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list2(struct lyd_node *parent, const struct lys_module *module, const char *name, + const char *keys, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new term node in the data tree. + * + * To create a top-level term node defined in an extension instance, use ::lyd_new_ext_term(). + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node can be #LYS_LEAF or #LYS_LEAFLIST. + * @param[in] val_str String value of the node. If it varies based on the format, ::LY_VALUE_JSON is expected. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_term(struct lyd_node *parent, const struct lys_module *module, const char *name, + const char *val_str, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new term node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node can be #LYS_LEAF or #LYS_LEAFLIST. + * @param[in] value Binary value of the node. To learn what exactly is expected see @ref howtoDataLYB. + * @param[in] value_len Length of @p value. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_term_bin(struct lyd_node *parent, const struct lys_module *module, const char *name, + const void *value, size_t value_len, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new term node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node can be #LYS_LEAF or #LYS_LEAFLIST. + * @param[in] val_str Canonical string value of the node. If it is not, it may lead to unexpected behavior. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_term_canon(struct lyd_node *parent, const struct lys_module *module, const char *name, + const char *val_str, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new top-level term node defined in the given extension instance. + * + * To create a term node with parent (no matter if defined inside extension instance or a standard tree) or a top-level + * node of a standard module's tree, use ::lyd_new_term(). + * + * @param[in] ext Extension instance where the term node being created is defined. + * @param[in] name Schema node name of the new data node. The node can be #LYS_LEAF or #LYS_LEAFLIST. + * @param[in] val_str String form of the value of the node being created. In case of an instance-identifier or identityref + * value, the JSON format is expected (module names instead of prefixes). + * @param[out] node The created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_ext_term(const struct lysc_ext_instance *ext, const char *name, const char *val_str, + struct lyd_node **node); + +/** + * @brief Create a new any node in the data tree. + * + * To create a top-level any node defined in an extension instance, use ::lyd_new_ext_any(). + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node can be #LYS_ANYDATA or #LYS_ANYXML. + * @param[in] value Value for the node. Expected type is determined by @p value_type. + * @param[in] use_value Whether to use dynamic @p value or make a copy. + * @param[in] value_type Type of the provided value in @p value. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_any(struct lyd_node *parent, const struct lys_module *module, const char *name, + const void *value, ly_bool use_value, LYD_ANYDATA_VALUETYPE value_type, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new top-level any node defined in the given extension instance. + * + * To create an any node with parent (no matter if defined inside extension instance or a standard tree) or a top-level + * any node of a standard module's tree, use ::lyd_new_any(). + * + * @param[in] ext Extension instance where the any node being created is defined. + * @param[in] name Schema node name of the new data node. The node can be #LYS_ANYDATA or #LYS_ANYXML. + * @param[in] value Value for the node. Expected type is determined by @p value_type. + * @param[in] use_value Whether to use dynamic @p value or make a copy. + * @param[in] value_type Type of the provided value in @p value. + * @param[out] node The created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_ext_any(const struct lysc_ext_instance *ext, const char *name, const void *value, + ly_bool use_value, LYD_ANYDATA_VALUETYPE value_type, struct lyd_node **node); + +/** + * @brief Create new metadata. + * + * @param[in] ctx libyang context, + * @param[in] parent Optional parent node for the metadata being created. Must be set if @p meta is NULL. + * @param[in] module Module of the metadata being created. If NULL, @p name must include module name as the prefix. + * @param[in] name Annotation name of the new metadata. It can include the annotation module as the prefix. + * If the prefix is specified it is always used but if not specified, @p module must be set. + * @param[in] val_str String form of the value of the metadata. In case of an instance-identifier or identityref + * value, the JSON format is expected (module names instead of prefixes). + * @param[in] clear_dflt Whether to clear the default flag starting from @p parent, recursively all NP containers. + * @param[out] meta Optional created metadata. Must be set if @p parent is NULL. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_meta(const struct ly_ctx *ctx, struct lyd_node *parent, const struct lys_module *module, + const char *name, const char *val_str, ly_bool clear_dflt, struct lyd_meta **meta); + +/** + * @brief Create new metadata from an opaque node attribute if possible. + * + * @param[in] ctx libyang context. + * @param[in] parent Optional parent node for the metadata being created. Must be set if @p meta is NULL. + * @param[in] clear_dflt Whether to clear the default flag starting from @p parent, recursively all NP containers. + * @param[in] attr Opaque node attribute to parse into metadata. + * @param[out] meta Optional created metadata. Must be set if @p parent is NULL. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the attribute could not be parsed into any metadata. + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_new_meta2(const struct ly_ctx *ctx, struct lyd_node *parent, ly_bool clear_dflt, + const struct lyd_attr *attr, struct lyd_meta **meta); + +/** + * @brief Create a new JSON opaque node in the data tree. To create an XML opaque node, use ::lyd_new_opaq2(). + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] ctx libyang context. If NULL, @p parent context will be used. + * @param[in] name Node name. + * @param[in] value Optional node value. + * @param[in] prefix Optional node prefix, must be equal to @p module_name if set. + * @param[in] module_name Node module name. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_opaq(struct lyd_node *parent, const struct ly_ctx *ctx, const char *name, const char *value, + const char *prefix, const char *module_name, struct lyd_node **node); + +/** + * @brief Create a new XML opaque node in the data tree. To create a JSON opaque node, use ::lyd_new_opaq(). + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] ctx libyang context. If NULL, @p parent context will be used. + * @param[in] name Node name. + * @param[in] value Optional node value. + * @param[in] prefix Optional node prefix. + * @param[in] module_ns Node module namespace. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_opaq2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *name, const char *value, + const char *prefix, const char *module_ns, struct lyd_node **node); + +/** + * @brief Create new JSON attribute for an opaque data node. To create an XML attribute, use ::lyd_new_attr2(). + * + * Note that for an attribute to be later resolved as YANG metadata, it needs @p module_nane and a prefix in @p name. + * + * @param[in] parent Parent opaque node for the attribute. + * @param[in] module_name Optional name of the module of the attribute. + * @param[in] name Attribute name with optional prefix, which is a module name. If the prefix is set, it is also stored + * as the explicit module name if @p module_name is not set. + * @param[in] value Optional attribute value. + * @param[out] attr Optional created attribute. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_attr(struct lyd_node *parent, const char *module_name, const char *name, const char *value, + struct lyd_attr **attr); + +/** + * @brief Create new XML attribute for an opaque data node. To create a JSON attribute, use ::lyd_new_attr(). + * + * Note that for an attribute to be later resolved as YANG metadata, it needs @p module_ns and a prefix in @p name. + * + * @param[in] parent Parent opaque node for the attribute being created. + * @param[in] module_ns Optional namespace of the module of the attribute. + * @param[in] name Attribute name with optional prefix, which is an XML prefix. + * @param[in] value Optional attribute value. + * @param[out] attr Optional created attribute. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_attr2(struct lyd_node *parent, const char *module_ns, const char *name, const char *value, + struct lyd_attr **attr); + +/** + * @ingroup datatree + * @defgroup pathoptions Data path creation options + * + * Various options to change lyd_new_path*() behavior. + * + * Default behavior: + * - if the target node already exists (and is not default), an error is returned. + * - the whole path to the target node is created (with any missing parents) if necessary. + * - RPC output schema children are completely ignored in all modules. Input is searched and nodes created normally. + * @{ + */ + +#define LYD_NEW_PATH_UPDATE 0x01 /**< If the target node exists, is a leaf, and it is updated with a new value or its + default flag is changed, it is returned. If the target node exists and is not + a leaf or generally no change occurs in the @p parent tree, NULL is returned and + no error set. */ +#define LYD_NEW_PATH_OUTPUT 0x02 /**< Changes the behavior to ignoring RPC/action input schema nodes and using only + output ones. */ +#define LYD_NEW_PATH_OPAQ 0x04 /**< Enables the creation of opaque nodes with some specific rules. If the __last node__ + in the path is not uniquely defined ((leaf-)list without a predicate) or has an + invalid value (leaf/leaf-list), it is created as opaque. */ +#define LYD_NEW_PATH_BIN_VALUE 0x08 /**< Interpret the provided leaf/leaf-list @p value as being in the binary + ::LY_VALUE_LYB format, to learn what exactly is expected see @ref howtoDataLYB. */ +#define LYD_NEW_PATH_CANON_VALUE 0x10 /**< Interpret the provided leaf/leaf-list @p value as being in the canonical + (or JSON if no defined) ::LY_VALUE_CANON format. If it is not, it may lead + to unexpected behavior. */ + +/** @} pathoptions */ + +/** + * @brief Create a new node in the data tree based on a path. If creating anyxml/anydata nodes, ::lyd_new_path2 + * should be used instead, this function expects the value as string. + * + * If creating data nodes defined inside an extension instance, use ::lyd_new_ext_path(). + * + * If @p path points to a list key, the key value from the predicate is used and @p value is ignored. + * Also, if a leaf-list is being created and both a predicate is defined in @p path + * and @p value is set, the predicate is preferred. + * + * For key-less lists and non-configuration leaf-lists, positional predicates should be used (indices starting from 1). + * If no predicate is used for these nodes, they are always created. + * + * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, + * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted + * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. + * @param[in] ctx libyang context, must be set if @p parent is NULL. + * @param[in] path [Path](@ref howtoXPath) to create. + * @param[in] value String value of the new leaf/leaf-list. If it varies based on the format, ::LY_VALUE_JSON is expected. + * For other node types, it should be NULL. + * @param[in] options Bitmask of options, see @ref pathoptions. + * @param[out] node Optional first created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_path(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const char *value, + uint32_t options, struct lyd_node **node); + +/** + * @brief Create a new node in the data tree based on a path. All node types can be created. + * + * Details are mentioned in ::lyd_new_path(). + * + * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, + * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted + * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. + * @param[in] ctx libyang context, must be set if @p parent is NULL. + * @param[in] path [Path](@ref howtoXPath) to create. + * @param[in] value Value of the new leaf/leaf-list (const char *) in ::LY_VALUE_JSON format. If creating an + * anyxml/anydata node, the expected type depends on @p value_type. For other node types, it should be NULL. + * @param[in] value_len Length of @p value in bytes. May be 0 if @p value is a zero-terminated string. Ignored when + * creating anyxml/anydata nodes. + * @param[in] value_type Anyxml/anydata node @p value type. + * @param[in] options Bitmask of options, see @ref pathoptions. + * @param[out] new_parent Optional first parent node created. If only one node was created, equals to @p new_node. + * @param[out] new_node Optional last node created. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_path2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value, + size_t value_len, LYD_ANYDATA_VALUETYPE value_type, uint32_t options, struct lyd_node **new_parent, + struct lyd_node **new_node); + +/** + * @brief Create a new node defined in the given extension instance. In case of anyxml/anydata nodes, this function expects + * the @p value as string. + * + * If creating data nodes defined in a module's standard tree, use ::lyd_new_path() or ::lyd_new_path2(). + * + * Details are mentioned in ::lyd_new_path(). + * + * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, + * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted + * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. + * @param[in] ext Extension instance where the node being created is defined. + * @param[in] path [Path](@ref howtoXPath) to create. + * @param[in] value Value of the new leaf/leaf-list. For other node types, it should be NULL. + * @param[in] options Bitmask of options, see @ref pathoptions. + * @param[out] node Optional first created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_ext_path(struct lyd_node *parent, const struct lysc_ext_instance *ext, const char *path, + const void *value, uint32_t options, struct lyd_node **node); + +/** + * @ingroup datatree + * @defgroup implicitoptions Implicit node creation options + * + * Various options to change lyd_new_implicit*() behavior. + * + * Default behavior: + * - both configuration and state missing implicit nodes are added. + * - for existing RPC/action nodes, input implicit nodes are added. + * - all implicit node types are added (non-presence containers, default leaves, and default leaf-lists). + * @{ + */ + +#define LYD_IMPLICIT_NO_STATE 0x01 /**< Do not add any implicit state nodes. */ +#define LYD_IMPLICIT_NO_CONFIG 0x02 /**< Do not add any implicit config nodes. */ +#define LYD_IMPLICIT_OUTPUT 0x04 /**< For RPC/action nodes, add output implicit nodes instead of input. */ +#define LYD_IMPLICIT_NO_DEFAULTS 0x08 /**< Do not add any default nodes (leaves/leaf-lists), only non-presence + containers. */ + +/** @} implicitoptions */ + +/** + * @brief Add any missing implicit nodes into a data subtree. Default nodes with a false "when" are not added. + * + * @param[in] tree Tree to add implicit nodes into. + * @param[in] implicit_options Options for implicit node creation, see @ref implicitoptions. + * @param[out] diff Optional diff with any created nodes. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_implicit_tree(struct lyd_node *tree, uint32_t implicit_options, struct lyd_node **diff); + +/** + * @brief Add any missing implicit nodes. Default nodes with a false "when" are not added. + * + * @param[in,out] tree Tree to add implicit nodes into. Note that in case a first top-level sibling is used, + * it may no longer be first if an implicit node was inserted before @p tree. Use ::lyd_first_sibling() to + * adjust @p tree in these cases. + * @param[in] ctx libyang context, must be set only if @p tree is an empty tree. + * @param[in] implicit_options Options for implicit node creation, see @ref implicitoptions. + * @param[out] diff Optional diff with any created nodes. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_implicit_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t implicit_options, + struct lyd_node **diff); + +/** + * @brief Add any missing implicit nodes of one module. Default nodes with a false "when" are not added. + * + * @param[in,out] tree Tree to add implicit nodes into. Note that in case a first top-level sibling is used, + * it may no longer be first if an implicit node was inserted before @p tree. Use ::lyd_first_sibling() to + * adjust @p tree in these cases. + * @param[in] module Module whose implicit nodes to create. + * @param[in] implicit_options Options for implicit node creation, see @ref implicitoptions. + * @param[out] diff Optional diff with any created nodes. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_implicit_module(struct lyd_node **tree, const struct lys_module *module, + uint32_t implicit_options, struct lyd_node **diff); + +/** + * @brief Change the value of a term (leaf or leaf-list) node to a string value. + * + * Node changed this way is always considered explicitly set, meaning its default flag + * is always cleared. + * + * @param[in] term Term node to change. + * @param[in] val_str New value to set, any prefixes are expected in JSON format. + * @return LY_SUCCESS if value was changed, + * @return LY_EEXIST if value was the same and only the default flag was cleared, + * @return LY_ENOT if the values were equal and no change occurred, + * @return LY_ERR value on other errors. + */ +LIBYANG_API_DECL LY_ERR lyd_change_term(struct lyd_node *term, const char *val_str); + +/** + * @brief Change the value of a term (leaf or leaf-list) node to a binary value. + * + * Node changed this way is always considered explicitly set, meaning its default flag + * is always cleared. + * + * @param[in] term Term node to change. + * @param[in] value New value to set in binary format, see @ref howtoDataLYB. + * @param[in] value_len Length of @p value. + * @return LY_SUCCESS if value was changed, + * @return LY_EEXIST if value was the same and only the default flag was cleared, + * @return LY_ENOT if the values were equal and no change occurred, + * @return LY_ERR value on other errors. + */ +LIBYANG_API_DECL LY_ERR lyd_change_term_bin(struct lyd_node *term, const void *value, size_t value_len); + +/** + * @brief Change the value of a term (leaf or leaf-list) node to a canonical string value. + * + * Node changed this way is always considered explicitly set, meaning its default flag + * is always cleared. + * + * @param[in] term Term node to change. + * @param[in] val_str New value to set in canonical (or JSON if no defined) format. If the value is not + * canonical, it may lead to unexpected behavior. + * @return LY_SUCCESS if value was changed, + * @return LY_EEXIST if value was the same and only the default flag was cleared, + * @return LY_ENOT if the values were equal and no change occurred, + * @return LY_ERR value on other errors. + */ +LIBYANG_API_DECL LY_ERR lyd_change_term_canon(struct lyd_node *term, const char *val_str); + +/** + * @brief Change the value of a metadata instance. + * + * @param[in] meta Metadata to change. + * @param[in] val_str New value to set, any prefixes are expected in JSON format. + * @return LY_SUCCESS if value was changed, + * @return LY_ENOT if the values were equal and no change occurred, + * @return LY_ERR value on other errors. + */ +LIBYANG_API_DECL LY_ERR lyd_change_meta(struct lyd_meta *meta, const char *val_str); + +/** + * @brief Insert a child into a parent. + * + * - if the node is part of some other tree, it is automatically unlinked. + * - if the node is the first node of a node list (with no parent), all the subsequent nodes are also inserted. + * + * @param[in] parent Parent node to insert into. + * @param[in] node Node to insert. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_insert_child(struct lyd_node *parent, struct lyd_node *node); + +/** + * @brief Insert a node into siblings. + * + * - if the node is part of some other tree, it is automatically unlinked. + * - if the node is the first node of a node list (with no parent), all the subsequent nodes are also inserted. + * + * @param[in] sibling Siblings to insert into, can even be NULL. + * @param[in] node Node to insert. + * @param[out] first Optionally return the first sibling after insertion. Can be the address of @p sibling. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_insert_sibling(struct lyd_node *sibling, struct lyd_node *node, struct lyd_node **first); + +/** + * @brief Insert a node before another node, can be used only for user-ordered nodes. + * If inserting several siblings, each of them must be inserted individually. + * + * - if the node is part of some other tree, it is automatically unlinked. + * + * @param[in] sibling Sibling node to insert before. + * @param[in] node Node to insert. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_insert_before(struct lyd_node *sibling, struct lyd_node *node); + +/** + * @brief Insert a node after another node, can be used only for user-ordered nodes. + * If inserting several siblings, each of them must be inserted individually. + * + * - if the node is part of some other tree, it is automatically unlinked. + * + * @param[in] sibling Sibling node to insert after. + * @param[in] node Node to insert. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_insert_after(struct lyd_node *sibling, struct lyd_node *node); + +/** + * @brief Unlink the specified node with all the following siblings. + * + * @param[in] node Data tree node to be unlinked (together with all the children and following siblings). + */ +LIBYANG_API_DECL void lyd_unlink_siblings(struct lyd_node *node); + +/** + * @brief Unlink the specified data subtree. + * + * @param[in] node Data tree node to be unlinked (together with all the children). + */ +LIBYANG_API_DECL void lyd_unlink_tree(struct lyd_node *node); + +/** + * @brief Free all the nodes (even parents of the node) in the data tree. + * + * @param[in] node Any of the nodes inside the tree. + */ +LIBYANG_API_DECL void lyd_free_all(struct lyd_node *node); + +/** + * @brief Free all the sibling nodes (preceding as well as succeeding). + * + * @param[in] node Any of the sibling nodes to free. + */ +LIBYANG_API_DECL void lyd_free_siblings(struct lyd_node *node); + +/** + * @brief Free (and unlink) the specified data (sub)tree. + * + * @param[in] node Root of the (sub)tree to be freed. + */ +LIBYANG_API_DECL void lyd_free_tree(struct lyd_node *node); + +/** + * @brief Free a single metadata instance. + * + * @param[in] meta Metadata to free. + */ +LIBYANG_API_DECL void lyd_free_meta_single(struct lyd_meta *meta); + +/** + * @brief Free the metadata instance with any following instances. + * + * @param[in] meta Metadata to free. + */ +LIBYANG_API_DECL void lyd_free_meta_siblings(struct lyd_meta *meta); + +/** + * @brief Free a single attribute. + * + * @param[in] ctx Context where the attributes were created. + * @param[in] attr Attribute to free. + */ +LIBYANG_API_DECL void lyd_free_attr_single(const struct ly_ctx *ctx, struct lyd_attr *attr); + +/** + * @brief Free the attribute with any following attributes. + * + * @param[in] ctx Context where the attributes were created. + * @param[in] attr First attribute to free. + */ +LIBYANG_API_DECL void lyd_free_attr_siblings(const struct ly_ctx *ctx, struct lyd_attr *attr); + +/** + * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value. + * + * The given node is not modified in any way - it is just checked if the @p value can be set to the node. + * + * @param[in] ctx libyang context for logging (function does not log errors when @p ctx is NULL) + * @param[in] schema Schema node of the @p value. + * @param[in] value String value to be checked, it is expected to be in JSON format. + * @param[in] value_len Length of the given @p value (mandatory). + * @param[in] ctx_node Optional data tree context node for the value (leafref target, instance-identifier). + * If not set and is required for the validation to complete, ::LY_EINCOMPLETE is be returned. + * @param[out] realtype Optional real type of @p value. + * @param[out] canonical Optional canonical value of @p value (in the dictionary). + * @return LY_SUCCESS on success + * @return LY_EINCOMPLETE in case the @p ctx_node is not provided and it was needed to finish the validation + * (e.g. due to require-instance). + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_value_validate(const struct ly_ctx *ctx, const struct lysc_node *schema, const char *value, + size_t value_len, const struct lyd_node *ctx_node, const struct lysc_type **realtype, const char **canonical); + +/** + * @brief Compare the node's value with the given string value. The string value is first validated according to + * the (current) node's type. + * + * @param[in] node Data node to compare. + * @param[in] value String value to be compared. It does not need to be in a canonical form - as part of the process, + * it is validated and canonized if possible. But it is expected to be in JSON format. + * @param[in] value_len Length of the given @p value (mandatory). + * @return LY_SUCCESS on success, + * @return LY_ENOT if the values do not match, + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_value_compare(const struct lyd_node_term *node, const char *value, size_t value_len); + +/** + * @ingroup datatree + * @defgroup datacompareoptions Data compare options + * @{ + * Various options to change the ::lyd_compare_single() and ::lyd_compare_siblings() behavior. + */ +#define LYD_COMPARE_FULL_RECURSION 0x01 /* Lists and containers are the same only in case all they children + (subtree, so direct as well as indirect children) are the same. By default, + containers are the same in case of the same schema node and lists are the same + in case of equal keys (keyless lists do the full recursion comparison all the time). */ +#define LYD_COMPARE_DEFAULTS 0x02 /* By default, implicit and explicit default nodes are considered to be equal. This flag + changes this behavior and implicit (automatically created default node) and explicit + (explicitly created node with the default value) default nodes are considered different. */ +#define LYD_COMPARE_OPAQ 0x04 /* Opaque nodes can normally be never equal to data nodes. Using this flag even + opaque nodes members are compared to data node schema and value and can result + in a match. */ +/** @} datacompareoptions */ + +/** + * @brief Compare 2 data nodes if they are equivalent. + * + * Works correctly even if @p node1 and @p node2 have different contexts. + * + * @param[in] node1 The first node to compare. + * @param[in] node2 The second node to compare. + * @param[in] options Various @ref datacompareoptions. + * @return LY_SUCCESS if the nodes are equivalent. + * @return LY_ENOT if the nodes are not equivalent. + */ +LIBYANG_API_DECL LY_ERR lyd_compare_single(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options); + +/** + * @brief Compare 2 lists of siblings if they are equivalent. + * + * Works correctly even if @p node1 and @p node2 have different contexts. + * + * @param[in] node1 The first sibling list to compare. + * @param[in] node2 The second sibling list to compare. + * @param[in] options Various @ref datacompareoptions. + * @return LY_SUCCESS if all the siblings are equivalent. + * @return LY_ENOT if the siblings are not equivalent. + */ +LIBYANG_API_DECL LY_ERR lyd_compare_siblings(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options); + +/** + * @brief Compare 2 metadata. + * + * If @p meta1 and @p meta2 have different contexts, they are never equivalent. + * + * @param[in] meta1 First metadata. + * @param[in] meta2 Second metadata. + * @return LY_SUCCESS if the metadata are equivalent. + * @return LY_ENOT if not. + */ +LIBYANG_API_DECL LY_ERR lyd_compare_meta(const struct lyd_meta *meta1, const struct lyd_meta *meta2); + +/** + * @ingroup datatree + * @defgroup dupoptions Data duplication options + * + * Various options to change ::lyd_dup_single() and ::lyd_dup_siblings() behavior. + * + * Default behavior: + * - only the specified node is duplicated without siblings, parents, or children. + * - all the metadata of the duplicated nodes are also duplicated. + * @{ + */ + +#define LYD_DUP_RECURSIVE 0x01 /**< Duplicate not just the node but also all the children. Note that + list's keys are always duplicated. */ +#define LYD_DUP_NO_META 0x02 /**< Do not duplicate metadata (or attributes) of any node. */ +#define LYD_DUP_WITH_PARENTS 0x04 /**< If a nested node is being duplicated, duplicate also all the parents. + Keys are also duplicated for lists. Return value does not change! */ +#define LYD_DUP_WITH_FLAGS 0x08 /**< Also copy any data node flags. That will cause the duplicated data to preserve + its validation/default node state. */ +#define LYD_DUP_NO_EXT 0x10 /**< Do not duplicate nodes with the ::LYD_EXT flag (nested extension instance data). */ + +/** @} dupoptions */ + +/** + * @brief Create a copy of the specified data tree @p node. Schema references are kept the same. + * + * @param[in] node Data tree node to be duplicated. + * @param[in] parent Optional parent node where to connect the duplicated node(s). If set in combination with + * ::LYD_DUP_WITH_PARENTS, the missing parents' chain is duplicated and connected with @p parent. + * @param[in] options Bitmask of options flags, see @ref dupoptions. + * @param[out] dup Optional created copy of the node. Note that in case the parents chain is duplicated for the duplicated + * node(s) (when ::LYD_DUP_WITH_PARENTS used), the first duplicated node is still returned. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_dup_single(const struct lyd_node *node, struct lyd_node_inner *parent, uint32_t options, + struct lyd_node **dup); + +/** + * @brief Create a copy of the specified data tree @p node. Schema references are assigned from @p trg_ctx. + * + * @param[in] node Data tree node to be duplicated. + * @param[in] trg_ctx Target context for duplicated nodes. + * @param[in] parent Optional parent node where to connect the duplicated node(s). If set in combination with + * ::LYD_DUP_WITH_PARENTS, the missing parents' chain is duplicated and connected with @p parent. + * @param[in] options Bitmask of options flags, see @ref dupoptions. + * @param[out] dup Optional created copy of the node. Note that in case the parents chain is duplicated for the duplicated + * node(s) (when ::LYD_DUP_WITH_PARENTS used), the first duplicated node is still returned. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_dup_single_to_ctx(const struct lyd_node *node, const struct ly_ctx *trg_ctx, + struct lyd_node_inner *parent, uint32_t options, struct lyd_node **dup); + +/** + * @brief Create a copy of the specified data tree @p node with any following siblings. Schema references are kept the same. + * + * @param[in] node Data tree node to be duplicated. + * @param[in] parent Optional parent node where to connect the duplicated node(s). If set in combination with + * ::LYD_DUP_WITH_PARENTS, the missing parents' chain is duplicated and connected with @p parent. + * @param[in] options Bitmask of options flags, see @ref dupoptions. + * @param[out] dup Optional created copy of the node. Note that in case the parents chain is duplicated for the duplicated + * node(s) (when ::LYD_DUP_WITH_PARENTS used), the first duplicated node is still returned. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_dup_siblings(const struct lyd_node *node, struct lyd_node_inner *parent, uint32_t options, + struct lyd_node **dup); + +/** + * @brief Create a copy of the specified data tree @p node with any following siblings. Schema references are assigned + * from @p trg_ctx. + * + * @param[in] node Data tree node to be duplicated. + * @param[in] trg_ctx Target context for duplicated nodes. + * @param[in] parent Optional parent node where to connect the duplicated node(s). If set in combination with + * ::LYD_DUP_WITH_PARENTS, the missing parents' chain is duplicated and connected with @p parent. + * @param[in] options Bitmask of options flags, see @ref dupoptions. + * @param[out] dup Optional created copy of the node. Note that in case the parents chain is duplicated for the duplicated + * node(s) (when ::LYD_DUP_WITH_PARENTS used), the first duplicated node is still returned. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_dup_siblings_to_ctx(const struct lyd_node *node, const struct ly_ctx *trg_ctx, + struct lyd_node_inner *parent, uint32_t options, struct lyd_node **dup); + +/** + * @brief Create a copy of the metadata. + * + * @param[in] meta Metadata to copy. + * @param[in] parent Node where to append the new metadata. + * @param[out] dup Optional created metadata copy. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_dup_meta_single(const struct lyd_meta *meta, struct lyd_node *parent, struct lyd_meta **dup); + +/** + * @ingroup datatree + * @defgroup mergeoptions Data merge options. + * + * Various options to change ::lyd_merge_tree(), ::lyd_merge_siblings(), and ::lyd_merge_module() behavior. + * + * Default behavior: + * - source data tree is not modified in any way, + * - any default nodes in the source are ignored if there are explicit nodes in the target, + * - any metadata are ignored - those present in the target are kept, those in the source are not merged. + * - any merged nodes flags are set as non-validated. + * @{ + */ + +#define LYD_MERGE_DESTRUCT 0x01 /**< Spend source data tree in the function, it cannot be used afterwards! */ +#define LYD_MERGE_DEFAULTS 0x02 /**< Default nodes in the source tree replace even explicit nodes in the target. */ +#define LYD_MERGE_WITH_FLAGS 0x04 /**< Merged nodes (those missing in the source) keep their exact flags. */ + +/** @} mergeoptions */ + +/** + * @brief Merge the source data subtree into the target data tree. Merge may not be complete until validation + * is called on the resulting data tree (data from more cases may be present, default and non-default values). + * + * Example input: + * + * source (A1) - A2 - A3 target (B1) - B2 - B3 + * /\ /\ /\ /\ /\ /\ + * .... .... .... .... .... .... + * + * result target (A1) - B1 - B2 - B3 + * /\ /\ /\ /\ + * .... .... .... .... + * + * @param[in,out] target Target data tree to merge into, must be a top-level tree. Always points to the first sibling. + * @param[in] source Source data tree to merge, must be a top-level tree. + * @param[in] options Bitmask of option flags, see @ref mergeoptions. + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lyd_merge_tree(struct lyd_node **target, const struct lyd_node *source, uint16_t options); + +/** + * @brief Merge the source data tree with any following siblings into the target data tree. Merge may not be + * complete until validation called on the resulting data tree (data from more cases may be present, default + * and non-default values). + * + * Example input: + * + * source (A1) - A2 - A3 target (B1) - B2 - B3 + * /\ /\ /\ /\ /\ /\ + * .... .... .... .... .... .... + * + * result target (A1) - A2 - A3 - B1 - B2 - B3 + * /\ /\ /\ /\ /\ /\ + * .... .... .... .... .... .... + * + * @param[in,out] target Target data tree to merge into, must be a top-level tree. Always points to the first sibling. + * @param[in] source Source data tree to merge, must be a top-level tree. + * @param[in] options Bitmask of option flags, see @ref mergeoptions. + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lyd_merge_siblings(struct lyd_node **target, const struct lyd_node *source, uint16_t options); + +/** + * @brief Callback for matching merge nodes. + * + * @param[in] trg_node Target data node. + * @param[in] src_node Source data node, is NULL if it was actually duplicated (no target node found) and + * its copy is @p trg_node. + * @param[in] cb_data Arbitrary callback data. + * @return LY_ERR value. + */ +typedef LY_ERR (*lyd_merge_cb)(struct lyd_node *trg_node, const struct lyd_node *src_node, void *cb_data); + +/** + * @brief Merge all the nodes of a module from source data tree into the target data tree. Merge may not be + * complete until validation called on the resulting data tree (data from more cases may be present, default + * and non-default values). + * + * @param[in,out] target Target data tree to merge into, must be a top-level tree. Always points to the first sibling. + * @param[in] source Source data tree to merge, must be a top-level tree. + * @param[in] mod Module, whose source data only to consider, NULL for all modules. + * @param[in] merge_cb Optional merge callback that will be called for every merged node, before merging its descendants. + * If a subtree is being added into target (no matching node found), callback is called only once with the subtree root. + * @param[in] cb_data Arbitrary callback data. + * @param[in] options Bitmask of option flags, see @ref mergeoptions. + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lyd_merge_module(struct lyd_node **target, const struct lyd_node *source, const struct lys_module *mod, + lyd_merge_cb merge_cb, void *cb_data, uint16_t options); + +/** + * @ingroup datatree + * @defgroup diffoptions Data diff options. + * + * Various options to change ::lyd_diff_tree() and ::lyd_diff_siblings() behavior. + * + * Default behavior: + * - any default nodes are treated as non-existent and ignored. + * @{ + */ + +#define LYD_DIFF_DEFAULTS 0x01 /**< Default nodes in the trees are not ignored but treated similarly to explicit + nodes. Also, leaves and leaf-lists are added into diff even in case only their + default flag (state) was changed. */ + +/** @} diffoptions */ + +/** + * @brief Learn the differences between 2 data trees. + * + * The resulting diff is represented as a data tree with specific metadata from the internal 'yang' + * module. Most importantly, every node has an effective 'operation' metadata. If there is none + * defined on the node, it inherits the operation from the nearest parent. Top-level nodes must + * always have the 'operation' metadata defined. Additional metadata ('orig-default', 'value', + * 'orig-value', 'key', 'orig-key') are used for storing more information about the value in the first + * or the second tree. + * + * The diff tree is completely independent on the @p first and @p second trees, meaning all + * the information about the change is stored in the diff and the trees are not needed. + * + * __!! Caution !!__ + * The diff tree should never be validated because it may easily not be valid! For example, + * when data from one case branch are deleted and data from another branch created - data from both + * branches are then stored in the diff tree simultaneously. + * + * @param[in] first First data tree. + * @param[in] second Second data tree. + * @param[in] options Bitmask of options flags, see @ref diffoptions. + * @param[out] diff Generated diff. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_tree(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, + struct lyd_node **diff); + +/** + * @brief Learn the differences between 2 data trees including all the following siblings. + * + * Details are mentioned in ::lyd_diff_tree(). + * + * @param[in] first First data tree. + * @param[in] second Second data tree. + * @param[in] options Bitmask of options flags, see @ref diffoptions. + * @param[out] diff Generated diff. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_siblings(const struct lyd_node *first, const struct lyd_node *second, uint16_t options, + struct lyd_node **diff); + +/** + * @brief Callback for diff nodes. + * + * @param[in] diff_node Diff node. + * @param[in] data_node Matching node in data. + * @param[in] cb_data Arbitrary callback data. + * @return LY_ERR value. + */ +typedef LY_ERR (*lyd_diff_cb)(const struct lyd_node *diff_node, struct lyd_node *data_node, void *cb_data); + +/** + * @brief Apply the whole diff on a data tree but restrict the operation to one module. + * + * __!! Caution !!__ + * If applying a diff that was created __without__ the ::LYD_DIFF_DEFAULTS flag, there may be some duplicate values + * created. Unless the resulting tree is validated (and default values thus consolidated), using it further + * (such as applying another diff) may cause unexpected results or errors. + * + * @param[in,out] data Data to apply the diff on. + * @param[in] diff Diff to apply. + * @param[in] mod Module, whose diff/data only to consider, NULL for all modules. + * @param[in] diff_cb Optional diff callback that will be called for every changed node. + * @param[in] cb_data Arbitrary callback data. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_apply_module(struct lyd_node **data, const struct lyd_node *diff, + const struct lys_module *mod, lyd_diff_cb diff_cb, void *cb_data); + +/** + * @brief Apply the whole diff tree on a data tree. + * + * Details are mentioned in ::lyd_diff_apply_module(). + * + * @param[in,out] data Data to apply the diff on. + * @param[in] diff Diff to apply. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_apply_all(struct lyd_node **data, const struct lyd_node *diff); + +/** + * @ingroup datatree + * @defgroup diffmergeoptions Data diff merge options. + * + * Various options to change ::lyd_diff_merge_module(), ::lyd_diff_merge_tree(), and ::lyd_diff_merge_all() behavior. + * + * Default behavior: + * - any default nodes are expected to be a result of validation corrections and not explicitly modified. + * @{ + */ + +#define LYD_DIFF_MERGE_DEFAULTS 0x01 /**< Default nodes in the diffs are treated as possibly explicitly modified. */ + +/** @} diffmergeoptions */ + +/** + * @brief Merge 2 diffs into each other but restrict the operation to one module. + * + * The diffs must be possible to be merged, which is guaranteed only if the source diff was + * created on data that had the target diff applied on them. In other words, this sequence is legal + * + * 1) get diff1 from data1 and data2 -> get data11 from apply diff1 on data1 -> get diff2 from data11 and data3 -> + * -> get data 33 from apply diff2 on data1 + * + * and reusing these diffs + * + * 2) get diff11 from merge diff1 and diff2 -> get data33 from apply diff11 on data1 + * + * @param[in,out] diff Target diff to merge into. + * @param[in] src_diff Source diff. + * @param[in] mod Module, whose diff only to consider, NULL for all modules. + * @param[in] diff_cb Optional diff callback that will be called for every merged node. Param @p diff_node is the source + * diff node while @p data_node is the updated target diff node. In case a whole subtree is added, the callback is + * called on the root with @p diff_node being NULL. + * @param[in] cb_data Arbitrary callback data. + * @param[in] options Bitmask of options flags, see @ref diffmergeoptions. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_merge_module(struct lyd_node **diff, const struct lyd_node *src_diff, + const struct lys_module *mod, lyd_diff_cb diff_cb, void *cb_data, uint16_t options); + +/** + * @brief Merge 2 diff trees into each other. + * + * Details are mentioned in ::lyd_diff_merge_module(). + * + * @param[in,out] diff_first Target diff first sibling to merge into. + * @param[in] diff_parent Target diff parent to merge into. + * @param[in] src_sibling Source diff sibling to merge. + * @param[in] diff_cb Optional diff callback that will be called for every merged node. Param @p diff_node is the source + * diff node while @p data_node is the updated target diff node. In case a whole subtree is added, the callback is + * called on the root with @p diff_node being NULL. + * @param[in] cb_data Arbitrary callback data. + * @param[in] options Bitmask of options flags, see @ref diffmergeoptions. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_merge_tree(struct lyd_node **diff_first, struct lyd_node *diff_parent, + const struct lyd_node *src_sibling, lyd_diff_cb diff_cb, void *cb_data, uint16_t options); + +/** + * @brief Merge 2 diffs into each other. + * + * Details are mentioned in ::lyd_diff_merge_module(). + * + * @param[in,out] diff Target diff to merge into. + * @param[in] src_diff Source diff. + * @param[in] options Bitmask of options flags, see @ref diffmergeoptions. + * @return LY_SUCCESS on success, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_merge_all(struct lyd_node **diff, const struct lyd_node *src_diff, uint16_t options); + +/** + * @brief Reverse a diff and make the opposite changes. Meaning change create to delete, delete to create, + * or move from place A to B to move from B to A and so on. + * + * @param[in] src_diff Diff to reverse. + * @param[out] diff Reversed diff. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lyd_diff_reverse_all(const struct lyd_node *src_diff, struct lyd_node **diff); + +/** + * @brief Deprecated, use ::lyd_find_target() instead. + * + * @param[in] path Compiled path structure. + * @param[in] tree Data tree to be searched. + * @return Found target node, + * @return NULL if not found. + */ +LIBYANG_API_DECL const struct lyd_node_term *lyd_target(const struct ly_path *path, const struct lyd_node *tree); + +/** + * @brief Types of the different data paths. + */ +typedef enum { + LYD_PATH_STD, /**< Generic data path used for logging, node searching (::lyd_find_xpath(), ::lys_find_path()) as well as + creating new nodes (::lyd_new_path(), ::lyd_new_path2(), ::lyd_new_ext_path()). */ + LYD_PATH_STD_NO_LAST_PRED /**< Similar to ::LYD_PATH_STD except there is never a predicate on the last node. While it + can be used to search for nodes, do not use it to create new data nodes (lists). */ +} LYD_PATH_TYPE; + +/** + * @brief Generate path of the given node in the requested format. + * + * @param[in] node Data path of this node will be generated. + * @param[in] pathtype Format of the path to generate. + * @param[in,out] buffer Prepared buffer of the @p buflen length to store the generated path. + * If NULL, memory for the complete path is allocated. + * @param[in] buflen Size of the provided @p buffer. + * @return NULL in case of memory allocation error, path of the node otherwise. + * In case the @p buffer is NULL, the returned string is dynamically allocated and caller is responsible to free it. + */ +LIBYANG_API_DECL char *lyd_path(const struct lyd_node *node, LYD_PATH_TYPE pathtype, char *buffer, size_t buflen); + +/** + * @brief Find a specific metadata. + * + * @param[in] first First metadata to consider. + * @param[in] module Module of the metadata definition, may be NULL if @p name includes a prefix. + * @param[in] name Name of the metadata to find, may not include a prefix (module name) if @p module is set. + * @return Found metadata, + * @return NULL if not found. + */ +LIBYANG_API_DECL struct lyd_meta *lyd_find_meta(const struct lyd_meta *first, const struct lys_module *module, + const char *name); + +/** + * @brief Search in the given siblings (NOT recursively) for the first target instance with the same value. + * Uses hashes - should be used whenever possible for best performance. + * + * @param[in] siblings Siblings to search in including preceding and succeeding nodes. + * @param[in] target Target node to find. + * @param[out] match Can be NULL, otherwise the found data node. + * @return LY_SUCCESS on success, @p match set. + * @return LY_ENOTFOUND if not found, @p match set to NULL. + * @return LY_ERR value if another error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_sibling_first(const struct lyd_node *siblings, const struct lyd_node *target, + struct lyd_node **match); + +/** + * @brief Search in the given siblings for the first schema instance. + * Uses hashes - should be used whenever possible for best performance. + * + * @param[in] siblings Siblings to search in including preceding and succeeding nodes. + * @param[in] schema Schema node of the data node to find. + * @param[in] key_or_value If it is NULL, the first schema node data instance is found. For nodes with many + * instances, it can be set based on the type of @p schema: + * LYS_LEAFLIST: + * Searched instance value. + * LYS_LIST: + * Searched instance key values in the form of "[key1='val1'][key2='val2']...". + * The keys do not have to be ordered but all of them must be set. + * + * Note that any explicit values (leaf-list or list key values) will be canonized first + * before comparison. But values that do not have a canonical value are expected to be in the + * JSON format! + * @param[in] val_len Optional length of @p key_or_value in case it is not 0-terminated. + * @param[out] match Can be NULL, otherwise the found data node. + * @return LY_SUCCESS on success, @p match set. + * @return LY_ENOTFOUND if not found, @p match set to NULL. + * @return LY_EINVAL if @p schema is a key-less list. + * @return LY_ERR value if another error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_sibling_val(const struct lyd_node *siblings, const struct lysc_node *schema, + const char *key_or_value, size_t val_len, struct lyd_node **match); + +/** + * @brief Search the given siblings for all the exact same instances of a specific node instance. + * Uses hashes to whatever extent possible. + * + * @param[in] siblings Siblings to search in including preceding and succeeding nodes. + * @param[in] target Target node instance to find. + * @param[out] set Set with all the found instances. The first item is always the first instance. + * @return LY_SUCCESS on success, @p set returned. + * @return LY_ENOTFOUND if not found, empty @p set returned. + * @return LY_ERR value if another error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_sibling_dup_inst_set(const struct lyd_node *siblings, const struct lyd_node *target, + struct ly_set **set); + +/** + * @brief Search the given siblings for an opaque node with a specific name. + * + * @param[in] first First sibling to consider. + * @param[in] name Opaque node name to find. + * @param[out] match Can be NULL, otherwise the found data node. + * @return LY_SUCCESS on success, @p match set. + * @return LY_ENOTFOUND if not found, @p match set to NULL. + * @return LY_ERR value is an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_sibling_opaq_next(const struct lyd_node *first, const char *name, struct lyd_node **match); + +/** + * @brief Set a new XPath variable to @p vars. + * + * @param[in,out] vars Pointer to [sized array](@ref sizedarrays) of XPath variables. + * To create a new array, set the @p vars target pointer to NULL. + * Otherwise variable named @p name with a value @p value will be added to the @p vars + * or its value will be changed if the variable is already defined. + * @param[in] name Name of the added/edited variable. + * @param[in] value Value of the variable. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyxp_vars_set(struct lyxp_var **vars, const char *name, const char *value); + +/** + * @brief Free the XPath variables. + * + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + */ +LIBYANG_API_DECL void lyxp_vars_free(struct lyxp_var *vars); + +/** + * @brief Search in the given data for instances of nodes matching the provided XPath. + * + * If a list instance is being selected with all its key values specified and ordered + * in the form `list[key1=...][key2=...][key3=...]` or a leaf-list instance in the form + * `leaf-list[.=...]`, these instances are found using hashes with constant (*O(1)*) complexity + * (unless they are defined in top-level). Other predicates can still follow the aforementioned ones. + * + * @param[in] ctx_node XPath context node. + * @param[in] xpath [XPath](@ref howtoXPath) to select in JSON format. It must evaluate into a node set. + * @param[out] set Set of found data nodes. In case the result is a number, a string, or a boolean, + * the returned set is empty. + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_xpath(const struct lyd_node *ctx_node, const char *xpath, struct ly_set **set); + +/** + * @brief Search in the given data for instances of nodes matching the provided XPath. + * + * It is ::lyd_find_xpath() with @p vars added. + * + * @param[in] ctx_node XPath context node. + * @param[in] xpath [XPath](@ref howtoXPath) to select in JSON format. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[out] set Set of found data nodes. In case the result is a number, a string, or a boolean, + * the returned set is empty. + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, + struct ly_set **set); + +/** + * @brief Search in the given data for instances of nodes matching the provided XPath. + * + * It is ::lyd_find_xpath2() with @p tree added so that @p ctx_node may be the root. + * + * @param[in] ctx_node XPath context node, NULL for the root node. + * @param[in] tree Data tree to evaluate on. + * @param[in] xpath [XPath](@ref howtoXPath) to select in JSON format. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[out] set Set of found data nodes. In case the result is a number, a string, or a boolean, + * the returned set is empty. + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_xpath3(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, + const struct lyxp_var *vars, struct ly_set **set); + +/** + * @brief Search in the given data for instances of nodes matching the provided XPath. + * + * It is ::lyd_find_xpath3() with @p format and @p prefix_data added for special use-cases. + * + * @param[in] ctx_node XPath context node, NULL for the root node. + * @param[in] tree Data tree to evaluate on. + * @param[in] xpath [XPath](@ref howtoXPath) to select with prefix in @p format. + * @param[in] format Format of any prefixes in @p xpath. + * @param[in] prefix_data Format-specific prefix data. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[out] set Set of found data nodes. In case the result is a number, a string, or a boolean, + * the returned set is empty. + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_find_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, + LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, struct ly_set **set); + +/** + * @brief Evaluate an XPath on data and return the result converted to boolean. + * + * Optimizations similar as in ::lyd_find_xpath(). + * + * @param[in] ctx_node XPath context node. + * @param[in] xpath [XPath](@ref howtoXPath) to select. + * @param[out] result Expression result converted to boolean. + * @return LY_SUCCESS on success, @p result is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_eval_xpath(const struct lyd_node *ctx_node, const char *xpath, ly_bool *result); + +/** + * @brief Evaluate an XPath on data and return the result converted to boolean. + * + * It is ::lyd_eval_xpath() with @p vars added. + * + * @param[in] ctx_node XPath context node. + * @param[in] xpath [XPath](@ref howtoXPath) to select. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[out] result Expression result converted to boolean. + * @return LY_SUCCESS on success, @p result is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_eval_xpath2(const struct lyd_node *ctx_node, const char *xpath, + const struct lyxp_var *vars, ly_bool *result); + +/** + * @brief Evaluate an XPath on data and return the result converted to boolean. + * + * It is ::lyd_eval_xpath2() with @p format and @p prefix_data added for special use-cases. + * + * @param[in] ctx_node XPath context node. + * @param[in] cur_mod Current module of @p xpath, needed for some kinds of @p format. + * @param[in] xpath [XPath](@ref howtoXPath) to select. + * @param[in] format Format of any prefixes in @p xpath. + * @param[in] prefix_data Format-specific prefix data. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[out] result Expression result converted to boolean. + * @return LY_SUCCESS on success, @p result is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lyd_eval_xpath3(const struct lyd_node *ctx_node, const struct lys_module *cur_mod, + const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result); + +/** + * @brief Search in given data for a node uniquely identified by a path. + * + * Always works in constant (*O(1)*) complexity. To be exact, it is *O(n)* where *n* is the depth + * of the path used. + * + * @param[in] ctx_node Path context node. + * @param[in] path [Path](@ref howtoXPath) to find. + * @param[in] output Whether to search in RPC/action output nodes or in input nodes. + * @param[out] match Can be NULL, otherwise the found data node. + * @return LY_SUCCESS on success, @p match is set to the found node. + * @return LY_EINCOMPLETE if only a parent of the node was found, @p match is set to this parent node. + * @return LY_ENOTFOUND if no nodes in the path were found. + * @return LY_ERR on other errors. + */ +LIBYANG_API_DECL LY_ERR lyd_find_path(const struct lyd_node *ctx_node, const char *path, ly_bool output, + struct lyd_node **match); + +/** + * @brief Find the target node of a compiled path (::lyd_value instance-identifier). + * + * @param[in] path Compiled path structure. + * @param[in] tree Data tree to be searched. + * @param[out] match Can be NULL, otherwise the found data node. + * @return LY_SUCCESS on success, @p match is set to the found node. + * @return LY_ENOTFOUND if no match was found. + * @return LY_ERR on other errors. + */ +LIBYANG_API_DECL LY_ERR lyd_find_target(const struct ly_path *path, const struct lyd_node *tree, struct lyd_node **match); + +/** + * @brief Convert date-and-time from string to UNIX timestamp and fractions of a second. + * + * @param[in] value Valid string date-and-time value. + * @param[out] time UNIX timestamp. + * @param[out] fractions_s Optional fractions of a second, set to NULL if none. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR ly_time_str2time(const char *value, time_t *time, char **fractions_s); + +/** + * @brief Convert UNIX timestamp and fractions of a second into canonical date-and-time string value. + * + * @param[in] time UNIX timestamp. + * @param[in] fractions_s Fractions of a second, if any. + * @param[out] str String date-and-time value in the local timezone. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR ly_time_time2str(time_t time, const char *fractions_s, char **str); + +/** + * @brief Convert date-and-time from string to timespec. + * + * @param[in] value Valid string date-and-time value. + * @param[out] ts Timespec. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR ly_time_str2ts(const char *value, struct timespec *ts); + +/** + * @brief Convert timespec into date-and-time string value. + * + * @param[in] ts Timespec. + * @param[out] str String date-and-time value in the local timezone. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR ly_time_ts2str(const struct timespec *ts, char **str); + +#ifdef __cplusplus +} +#endif + +#endif /* LY_TREE_DATA_H_ */ diff --git a/src/tree_data_common.c b/src/tree_data_common.c new file mode 100644 index 0000000..f35f8f5 --- /dev/null +++ b/src/tree_data_common.c @@ -0,0 +1,1626 @@ +/** + * @file tree_data_common.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Parsing and validation common functions for data trees + * + * Copyright (c) 2015 - 2022 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 /* asprintf, strdup */ + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "hash_table.h" +#include "log.h" +#include "lyb.h" +#include "parser_data.h" +#include "plugins_exts.h" +#include "printer_data.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "validation.h" +#include "xml.h" +#include "xpath.h" + +/** + * @brief Find an entry in duplicate instance cache for an instance. Create it if it does not exist. + * + * @param[in] first_inst Instance of the cache entry. + * @param[in,out] dup_inst_cache Duplicate instance cache. + * @return Instance cache entry. + */ +static struct lyd_dup_inst * +lyd_dup_inst_get(const struct lyd_node *first_inst, struct lyd_dup_inst **dup_inst_cache) +{ + struct lyd_dup_inst *item; + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(*dup_inst_cache, u) { + if ((*dup_inst_cache)[u].inst_set->dnodes[0] == first_inst) { + return &(*dup_inst_cache)[u]; + } + } + + /* it was not added yet, add it now */ + LY_ARRAY_NEW_RET(LYD_CTX(first_inst), *dup_inst_cache, item, NULL); + + return item; +} + +LY_ERR +lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, struct lyd_dup_inst **dup_inst_cache) +{ + struct lyd_dup_inst *dup_inst; + + if (!*inst) { + /* no match, inst is unchanged */ + return LY_SUCCESS; + } + + /* there can be more exact same instances (even if not allowed in invalid data) and we must make sure we do not + * match a single node more times */ + dup_inst = lyd_dup_inst_get(*inst, dup_inst_cache); + LY_CHECK_ERR_RET(!dup_inst, LOGMEM(LYD_CTX(siblings)), LY_EMEM); + + if (!dup_inst->used) { + /* we did not cache these instances yet, do so */ + lyd_find_sibling_dup_inst_set(siblings, *inst, &dup_inst->inst_set); + assert(dup_inst->inst_set->count && (dup_inst->inst_set->dnodes[0] == *inst)); + } + + if (dup_inst->used == dup_inst->inst_set->count) { + if (lysc_is_dup_inst_list((*inst)->schema)) { + /* we have used all the instances */ + *inst = NULL; + } /* else just keep using the last (ideally only) instance */ + } else { + assert(dup_inst->used < dup_inst->inst_set->count); + + /* use another instance */ + *inst = dup_inst->inst_set->dnodes[dup_inst->used]; + ++dup_inst->used; + } + + return LY_SUCCESS; +} + +void +lyd_dup_inst_free(struct lyd_dup_inst *dup_inst) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(dup_inst, u) { + ly_set_free(dup_inst[u].inst_set, NULL); + } + LY_ARRAY_FREE(dup_inst); +} + +struct lyd_node * +lys_getnext_data(const struct lyd_node *last, const struct lyd_node *sibling, const struct lysc_node **slast, + const struct lysc_node *parent, const struct lysc_module *module) +{ + const struct lysc_node *siter = NULL; + struct lyd_node *match = NULL; + + assert(parent || module); + assert(!last || (slast && *slast)); + + if (slast) { + siter = *slast; + } + + if (last && last->next && (last->next->schema == siter)) { + /* return next data instance */ + return last->next; + } + + /* find next schema node data instance */ + while ((siter = lys_getnext(siter, parent, module, 0))) { + if (!lyd_find_sibling_val(sibling, siter, NULL, 0, &match)) { + break; + } + } + + if (slast) { + *slast = siter; + } + return match; +} + +struct lyd_node ** +lyd_node_child_p(struct lyd_node *node) +{ + assert(node); + + if (!node->schema) { + return &((struct lyd_node_opaq *)node)->child; + } else { + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + return &((struct lyd_node_inner *)node)->child; + default: + return NULL; + } + } +} + +LIBYANG_API_DEF LY_ERR +lyxp_vars_set(struct lyxp_var **vars, const char *name, const char *value) +{ + LY_ERR ret = LY_SUCCESS; + char *var_name = NULL, *var_value = NULL; + struct lyxp_var *item; + + if (!vars || !name || !value) { + return LY_EINVAL; + } + + /* If variable is already defined then change its value. */ + if (*vars && !lyxp_vars_find(*vars, name, 0, &item)) { + var_value = strdup(value); + LY_CHECK_RET(!var_value, LY_EMEM); + + /* Set new value. */ + free(item->value); + item->value = var_value; + } else { + var_name = strdup(name); + var_value = strdup(value); + LY_CHECK_ERR_GOTO(!var_name || !var_value, ret = LY_EMEM, error); + + /* Add new variable. */ + LY_ARRAY_NEW_GOTO(NULL, *vars, item, ret, error); + item->name = var_name; + item->value = var_value; + } + + return LY_SUCCESS; + +error: + free(var_name); + free(var_value); + return ret; +} + +LIBYANG_API_DEF void +lyxp_vars_free(struct lyxp_var *vars) +{ + LY_ARRAY_COUNT_TYPE u; + + if (!vars) { + return; + } + + LY_ARRAY_FOR(vars, u) { + free(vars[u].name); + free(vars[u].value); + } + + LY_ARRAY_FREE(vars); +} + +LIBYANG_API_DEF struct lyd_node * +lyd_child_no_keys(const struct lyd_node *node) +{ + struct lyd_node **children; + + if (!node) { + return NULL; + } + + if (!node->schema) { + /* opaq node */ + return ((struct lyd_node_opaq *)node)->child; + } + + children = lyd_node_child_p((struct lyd_node *)node); + if (children) { + struct lyd_node *child = *children; + + while (child && child->schema && (child->schema->flags & LYS_KEY)) { + child = child->next; + } + return child; + } else { + return NULL; + } +} + +LIBYANG_API_DEF const struct lys_module * +lyd_owner_module(const struct lyd_node *node) +{ + const struct lyd_node_opaq *opaq; + + if (!node) { + return NULL; + } + + if (!node->schema) { + opaq = (struct lyd_node_opaq *)node; + switch (opaq->format) { + case LY_VALUE_XML: + return opaq->name.module_ns ? ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns) : NULL; + case LY_VALUE_JSON: + return opaq->name.module_name ? ly_ctx_get_module_implemented(LYD_CTX(node), opaq->name.module_name) : NULL; + default: + return NULL; + } + } + + return lysc_owner_module(node->schema); +} + +void +lyd_first_module_sibling(struct lyd_node **node, const struct lys_module *mod) +{ + int cmp; + struct lyd_node *first; + const struct lys_module *own_mod; + + assert(node && mod); + + if (!*node) { + return; + } + + first = *node; + own_mod = lyd_owner_module(first); + cmp = own_mod ? strcmp(own_mod->name, mod->name) : 1; + if (cmp > 0) { + /* there may be some preceding data */ + while (first->prev->next) { + first = first->prev; + if (lyd_owner_module(first) == mod) { + cmp = 0; + break; + } + } + } + + if (cmp == 0) { + /* there may be some preceding data belonging to this module */ + while (first->prev->next) { + if (lyd_owner_module(first->prev) != mod) { + break; + } + first = first->prev; + } + } + + if (cmp < 0) { + /* there may be some following data */ + LY_LIST_FOR(first, first) { + if (lyd_owner_module(first) == mod) { + cmp = 0; + break; + } + } + } + + if (cmp == 0) { + /* we have found the first module data node */ + *node = first; + } +} + +const struct lys_module * +lyd_mod_next_module(struct lyd_node *tree, const struct lys_module *module, const struct ly_ctx *ctx, uint32_t *i, + struct lyd_node **first) +{ + struct lyd_node *iter; + const struct lys_module *mod; + + /* get the next module */ + if (module) { + if (*i) { + mod = NULL; + } else { + mod = module; + ++(*i); + } + } else { + do { + mod = ly_ctx_get_module_iter(ctx, i); + } while (mod && !mod->implemented); + } + + /* find its data */ + *first = NULL; + if (mod) { + LY_LIST_FOR(tree, iter) { + if (lyd_owner_module(iter) == mod) { + *first = iter; + break; + } + } + } + + return mod; +} + +const struct lys_module * +lyd_data_next_module(struct lyd_node **next, struct lyd_node **first) +{ + const struct lys_module *mod; + + if (!*next) { + /* all data traversed */ + *first = NULL; + return NULL; + } + + *first = *next; + + /* prepare next */ + mod = lyd_owner_module(*next); + LY_LIST_FOR(*next, *next) { + if (lyd_owner_module(*next) != mod) { + break; + } + } + + return mod; +} + +LY_ERR +lyd_value_store(const struct ly_ctx *ctx, struct lyd_value *val, const struct lysc_type *type, const void *value, + size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, ly_bool *incomplete) +{ + LY_ERR ret; + struct ly_err_item *err = NULL; + uint32_t options = (dynamic && *dynamic ? LYPLG_TYPE_STORE_DYNAMIC : 0); + + if (!value) { + value = ""; + } + if (incomplete) { + *incomplete = 0; + } + + ret = type->plugin->store(ctx, type, value, value_len, options, format, prefix_data, hints, ctx_node, val, NULL, &err); + if (dynamic) { + *dynamic = 0; + } + + if (ret == LY_EINCOMPLETE) { + if (incomplete) { + *incomplete = 1; + } + } else if (ret) { + if (err) { + LOGVAL_ERRITEM(ctx, err); + ly_err_free(err); + } else { + LOGVAL(ctx, LYVE_OTHER, "Storing value failed."); + } + return ret; + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_value_validate_incomplete(const struct ly_ctx *ctx, const struct lysc_type *type, struct lyd_value *val, + const struct lyd_node *ctx_node, const struct lyd_node *tree) +{ + LY_ERR ret; + struct ly_err_item *err = NULL; + + assert(type->plugin->validate); + + ret = type->plugin->validate(ctx, type, ctx_node, tree, val, &err); + if (ret) { + if (err) { + LOGVAL_ERRITEM(ctx, err); + ly_err_free(err); + } else { + LOGVAL(ctx, LYVE_OTHER, "Resolving value \"%s\" failed.", type->plugin->print(ctx, val, LY_VALUE_CANON, + NULL, NULL, NULL)); + } + return ret; + } + + return LY_SUCCESS; +} + +LY_ERR +lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len, + LY_VALUE_FORMAT format, void *prefix_data) +{ + LY_ERR rc = LY_SUCCESS; + struct ly_err_item *err = NULL; + struct lyd_value storage; + struct lysc_type *type; + + LY_CHECK_ARG_RET(ctx, node, value, LY_EINVAL); + + if (!(node->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGARG(ctx, node); + return LY_EINVAL; + } + + type = ((struct lysc_node_leaf *)node)->type; + rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data, + LYD_HINT_SCHEMA, node, &storage, NULL, &err); + if (rc == LY_EINCOMPLETE) { + /* actually success since we do not provide the context tree and call validation with + * LY_TYPE_OPTS_INCOMPLETE_DATA */ + rc = LY_SUCCESS; + } else if (rc && err) { + if (ctx) { + /* log only in case the ctx was provided as input parameter */ + if (err->path) { + LOG_LOCSET(NULL, NULL, err->path, NULL); + } else { + /* use at least the schema path */ + LOG_LOCSET(node, NULL, NULL, NULL); + } + LOGVAL_ERRITEM(ctx, err); + if (err->path) { + LOG_LOCBACK(0, 0, 1, 0); + } else { + LOG_LOCBACK(1, 0, 0, 0); + } + } + ly_err_free(err); + } + + if (!rc) { + type->plugin->free(ctx ? ctx : node->module->ctx, &storage); + } + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_value_validate(const struct ly_ctx *ctx, const struct lysc_node *schema, const char *value, size_t value_len, + const struct lyd_node *ctx_node, const struct lysc_type **realtype, const char **canonical) +{ + LY_ERR rc; + struct ly_err_item *err = NULL; + struct lysc_type *type; + struct lyd_value val = {0}; + ly_bool stored = 0, log = 1; + + LY_CHECK_ARG_RET(ctx, schema, !value_len || value, LY_EINVAL); + + if (!ctx) { + ctx = schema->module->ctx; + log = 0; + } + if (!value_len) { + value = ""; + } + type = ((struct lysc_node_leaf *)schema)->type; + + /* store */ + rc = type->plugin->store(ctx, type, value, value_len, 0, LY_VALUE_JSON, NULL, + LYD_HINT_DATA, schema, &val, NULL, &err); + if (!rc || (rc == LY_EINCOMPLETE)) { + stored = 1; + } + + if (ctx_node && (rc == LY_EINCOMPLETE)) { + /* resolve */ + rc = type->plugin->validate(ctx, type, ctx_node, ctx_node, &val, &err); + } + + if (rc && (rc != LY_EINCOMPLETE) && err) { + if (log) { + /* log error */ + if (err->path) { + LOG_LOCSET(NULL, NULL, err->path, NULL); + } else if (ctx_node) { + LOG_LOCSET(NULL, ctx_node, NULL, NULL); + } else { + LOG_LOCSET(schema, NULL, NULL, NULL); + } + LOGVAL_ERRITEM(ctx, err); + if (err->path) { + LOG_LOCBACK(0, 0, 1, 0); + } else if (ctx_node) { + LOG_LOCBACK(0, 1, 0, 0); + } else { + LOG_LOCBACK(1, 0, 0, 0); + } + } + ly_err_free(err); + } + + if (!rc || (rc == LY_EINCOMPLETE)) { + if (realtype) { + /* return realtype */ + if (val.realtype->basetype == LY_TYPE_UNION) { + *realtype = val.subvalue->value.realtype; + } else { + *realtype = val.realtype; + } + } + + if (canonical) { + /* return canonical value */ + lydict_insert(ctx, val.realtype->plugin->print(ctx, &val, LY_VALUE_CANON, NULL, NULL, NULL), 0, canonical); + } + } + + if (stored) { + /* free value */ + type->plugin->free(ctx ? ctx : schema->module->ctx, &val); + } + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_value_compare(const struct lyd_node_term *node, const char *value, size_t value_len) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_ctx *ctx; + struct lysc_type *type; + struct lyd_value val = {0}; + + LY_CHECK_ARG_RET(node ? node->schema->module->ctx : NULL, node, value, LY_EINVAL); + + ctx = node->schema->module->ctx; + type = ((struct lysc_node_leaf *)node->schema)->type; + + /* store the value */ + LOG_LOCSET(node->schema, &node->node, NULL, NULL); + ret = lyd_value_store(ctx, &val, type, value, value_len, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, node->schema, NULL); + LOG_LOCBACK(1, 1, 0, 0); + LY_CHECK_RET(ret); + + /* compare values */ + ret = type->plugin->compare(&node->value, &val); + + type->plugin->free(ctx, &val); + return ret; +} + +LIBYANG_API_DEF ly_bool +lyd_is_default(const struct lyd_node *node) +{ + const struct lysc_node_leaf *leaf; + const struct lysc_node_leaflist *llist; + const struct lyd_node_term *term; + LY_ARRAY_COUNT_TYPE u; + + if (!(node->schema->nodetype & LYD_NODE_TERM)) { + return 0; + } + + term = (const struct lyd_node_term *)node; + + if (node->schema->nodetype == LYS_LEAF) { + leaf = (const struct lysc_node_leaf *)node->schema; + if (!leaf->dflt) { + return 0; + } + + /* compare with the default value */ + if (!leaf->type->plugin->compare(&term->value, leaf->dflt)) { + return 1; + } + } else { + llist = (const struct lysc_node_leaflist *)node->schema; + if (!llist->dflts) { + return 0; + } + + LY_ARRAY_FOR(llist->dflts, u) { + /* compare with each possible default value */ + if (!llist->type->plugin->compare(&term->value, llist->dflts[u])) { + return 1; + } + } + } + + return 0; +} + +LIBYANG_API_DEF uint32_t +lyd_list_pos(const struct lyd_node *instance) +{ + const struct lyd_node *iter = NULL; + uint32_t pos = 0; + + if (!instance || !(instance->schema->nodetype & (LYS_LIST | LYS_LEAFLIST))) { + return 0; + } + + /* data instances are ordered, so we can stop when we found instance of other schema node */ + for (iter = instance; iter->schema == instance->schema; iter = iter->prev) { + if (pos && (iter->next == NULL)) { + /* overrun to the end of the siblings list */ + break; + } + ++pos; + } + + return pos; +} + +LIBYANG_API_DEF struct lyd_node * +lyd_first_sibling(const struct lyd_node *node) +{ + struct lyd_node *start; + + if (!node) { + return NULL; + } + + /* get the first sibling */ + if (node->parent) { + start = node->parent->child; + } else { + for (start = (struct lyd_node *)node; start->prev->next; start = start->prev) {} + } + + return start; +} + +/** + * @brief Check list node parsed into an opaque node for the reason. + * + * @param[in] node Opaque node. + * @param[in] snode Schema node of @p opaq. + * @return LY_SUCCESS if the node is valid; + * @return LY_ERR on error. + */ +static LY_ERR +lyd_parse_opaq_list_error(const struct lyd_node *node, const struct lysc_node *snode) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_set key_set = {0}; + const struct lysc_node *key = NULL; + const struct lyd_node *child; + const struct lyd_node_opaq *opaq_k; + uint32_t i; + + assert(!node->schema); + + /* get all keys into a set */ + while ((key = lys_getnext(key, snode, NULL, 0)) && (snode->flags & LYS_KEY)) { + LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)snode, 1, NULL), cleanup); + } + + LY_LIST_FOR(lyd_child(node), child) { + if (child->schema) { + LOGERR(LYD_CTX(node), LY_EINVAL, "Unexpected node %s \"%s\".", lys_nodetype2str(child->schema->nodetype), + LYD_NAME(child)); + ret = LY_EINVAL; + goto cleanup; + } + + opaq_k = (struct lyd_node_opaq *)child; + + /* find the key schema node */ + for (i = 0; i < key_set.count; ++i) { + key = key_set.snodes[i]; + if (!strcmp(key->name, opaq_k->name.name)) { + break; + } + } + if (i == key_set.count) { + /* some other node, skip */ + continue; + } + + /* key found */ + ly_set_rm_index(&key_set, i, NULL); + + /* check value */ + ret = lys_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format, + opaq_k->val_prefix_data); + LY_CHECK_GOTO(ret, cleanup); + } + + if (key_set.count) { + /* missing keys */ + LOGVAL(LYD_CTX(node), LY_VCODE_NOKEY, key_set.snodes[0]->name); + ret = LY_EVALID; + goto cleanup; + } + +cleanup: + ly_set_erase(&key_set, NULL); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_parse_opaq_error(const struct lyd_node *node) +{ + const struct ly_ctx *ctx; + const struct lyd_node_opaq *opaq; + const struct lyd_node *parent; + const struct lys_module *mod; + const struct lysc_node *snode; + + LY_CHECK_ARG_RET(LYD_CTX(node), node, !node->schema, !lyd_parent(node) || lyd_parent(node)->schema, LY_EINVAL); + + ctx = LYD_CTX(node); + opaq = (struct lyd_node_opaq *)node; + parent = lyd_parent(node); + + if (!opaq->name.module_ns) { + LOGVAL(ctx, LYVE_REFERENCE, "Unknown module of node \"%s\".", opaq->name.name); + return LY_EVALID; + } + + /* module */ + switch (opaq->format) { + case LY_VALUE_XML: + if (!parent || strcmp(opaq->name.module_ns, parent->schema->module->ns)) { + mod = ly_ctx_get_module_implemented_ns(ctx, opaq->name.module_ns); + if (!mod) { + LOGVAL(ctx, LYVE_REFERENCE, "No (implemented) module with namespace \"%s\" of node \"%s\" in the context.", + opaq->name.module_ns, opaq->name.name); + return LY_EVALID; + } + } else { + /* inherit */ + mod = parent->schema->module; + } + break; + case LY_VALUE_JSON: + case LY_VALUE_LYB: + if (!parent || strcmp(opaq->name.module_name, parent->schema->module->name)) { + mod = ly_ctx_get_module_implemented(ctx, opaq->name.module_name); + if (!mod) { + LOGVAL(ctx, LYVE_REFERENCE, "No (implemented) module named \"%s\" of node \"%s\" in the context.", + opaq->name.module_name, opaq->name.name); + return LY_EVALID; + } + } else { + /* inherit */ + mod = parent->schema->module; + } + break; + default: + LOGERR(ctx, LY_EINVAL, "Unsupported value format."); + return LY_EINVAL; + } + + /* schema */ + snode = lys_find_child(parent ? parent->schema : NULL, mod, opaq->name.name, 0, 0, 0); + if (!snode && parent && parent->schema && (parent->schema->nodetype & (LYS_RPC | LYS_ACTION))) { + /* maybe output node */ + snode = lys_find_child(parent->schema, mod, opaq->name.name, 0, 0, LYS_GETNEXT_OUTPUT); + } + if (!snode) { + if (parent) { + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%s\" not found as a child of \"%s\" node.", opaq->name.name, + LYD_NAME(parent)); + } else { + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%s\" not found in the \"%s\" module.", opaq->name.name, mod->name); + } + return LY_EVALID; + } + + if (snode->nodetype & LYD_NODE_TERM) { + /* leaf / leaf-list */ + LY_CHECK_RET(lys_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data)); + } else if (snode->nodetype == LYS_LIST) { + /* list */ + LY_CHECK_RET(lyd_parse_opaq_list_error(node, snode)); + } else if (snode->nodetype & LYD_NODE_INNER) { + /* inner node */ + if (opaq->value) { + LOGVAL(ctx, LYVE_DATA, "Invalid value \"%s\" for %s \"%s\".", opaq->value, + lys_nodetype2str(snode->nodetype), snode->name); + return LY_EVALID; + } + } else { + LOGERR(ctx, LY_EINVAL, "Unexpected opaque schema node %s \"%s\".", lys_nodetype2str(snode->nodetype), snode->name); + return LY_EINVAL; + } + + LOGERR(ctx, LY_EINVAL, "Unexpected valid opaque node %s \"%s\".", lys_nodetype2str(snode->nodetype), snode->name); + return LY_EINVAL; +} + +LIBYANG_API_DEF const char * +lyd_value_get_canonical(const struct ly_ctx *ctx, const struct lyd_value *value) +{ + LY_CHECK_ARG_RET(ctx, ctx, value, NULL); + + return value->_canonical ? value->_canonical : + (const char *)value->realtype->plugin->print(ctx, value, LY_VALUE_CANON, NULL, NULL, NULL); +} + +LIBYANG_API_DEF LY_ERR +lyd_any_value_str(const struct lyd_node *any, char **value_str) +{ + const struct lyd_node_any *a; + struct lyd_node *tree = NULL; + const char *str = NULL; + ly_bool dynamic = 0; + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(NULL, any, value_str, LY_EINVAL); + LY_CHECK_ARG_RET(NULL, any->schema, any->schema->nodetype & LYS_ANYDATA, LY_EINVAL); + + a = (struct lyd_node_any *)any; + *value_str = NULL; + + if (!a->value.str) { + /* there is no value in the union */ + return LY_SUCCESS; + } + + switch (a->value_type) { + case LYD_ANYDATA_LYB: + /* parse into a data tree */ + ret = lyd_parse_data_mem(LYD_CTX(any), a->value.mem, LYD_LYB, LYD_PARSE_ONLY, 0, &tree); + LY_CHECK_GOTO(ret, cleanup); + dynamic = 1; + break; + case LYD_ANYDATA_DATATREE: + tree = a->value.tree; + break; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + /* simply use the string */ + str = a->value.str; + break; + } + + if (tree) { + /* print into a string */ + ret = lyd_print_mem(value_str, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS); + LY_CHECK_GOTO(ret, cleanup); + } else { + assert(str); + *value_str = strdup(str); + LY_CHECK_ERR_GOTO(!*value_str, LOGMEM(LYD_CTX(any)), cleanup); + } + + /* success */ + +cleanup: + if (dynamic) { + lyd_free_all(tree); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_any_copy_value(struct lyd_node *trg, const union lyd_any_value *value, LYD_ANYDATA_VALUETYPE value_type) +{ + struct lyd_node_any *t; + + LY_CHECK_ARG_RET(NULL, trg, LY_EINVAL); + LY_CHECK_ARG_RET(NULL, trg->schema, trg->schema->nodetype & LYS_ANYDATA, LY_EINVAL); + + t = (struct lyd_node_any *)trg; + + /* free trg */ + switch (t->value_type) { + case LYD_ANYDATA_DATATREE: + lyd_free_all(t->value.tree); + break; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + lydict_remove(LYD_CTX(trg), t->value.str); + break; + case LYD_ANYDATA_LYB: + free(t->value.mem); + break; + } + t->value.str = NULL; + + if (!value) { + /* only free value in this case */ + return LY_SUCCESS; + } + + /* copy src */ + t->value_type = value_type; + switch (value_type) { + case LYD_ANYDATA_DATATREE: + if (value->tree) { + LY_CHECK_RET(lyd_dup_siblings(value->tree, NULL, LYD_DUP_RECURSIVE, &t->value.tree)); + } + break; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + if (value->str) { + LY_CHECK_RET(lydict_insert(LYD_CTX(trg), value->str, 0, &t->value.str)); + } + break; + case LYD_ANYDATA_LYB: + if (value->mem) { + int len = lyd_lyb_data_length(value->mem); + + LY_CHECK_RET(len == -1, LY_EINVAL); + t->value.mem = malloc(len); + LY_CHECK_ERR_RET(!t->value.mem, LOGMEM(LYD_CTX(trg)), LY_EMEM); + memcpy(t->value.mem, value->mem, len); + } + break; + } + + return LY_SUCCESS; +} + +const struct lysc_node * +lyd_node_schema(const struct lyd_node *node) +{ + const struct lysc_node *schema = NULL; + const struct lyd_node *prev_iter = NULL, *iter; + const struct lys_module *mod; + + if (!node) { + return NULL; + } else if (node->schema) { + return node->schema; + } + + /* get schema node of an opaque node */ + do { + /* get next data node */ + for (iter = node; lyd_parent(iter) != prev_iter; iter = lyd_parent(iter)) {} + + /* get equivalent schema node */ + if (iter->schema) { + schema = iter->schema; + } else { + /* get module */ + mod = lyd_owner_module(iter); + if (!mod && !schema) { + /* top-level opaque node has unknown module */ + break; + } + + /* get schema node */ + schema = lys_find_child(schema, mod ? mod : schema->module, LYD_NAME(iter), 0, 0, 0); + } + + /* remember to move to the descendant */ + prev_iter = iter; + } while (schema && (iter != node)); + + return schema; +} + +void +lyd_cont_set_dflt(struct lyd_node *node) +{ + const struct lyd_node *child; + + while (node) { + if (!node->schema || (node->flags & LYD_DEFAULT) || !lysc_is_np_cont(node->schema)) { + /* not a non-dflt NP container */ + break; + } + + LY_LIST_FOR(lyd_child(node), child) { + if (!(child->flags & LYD_DEFAULT)) { + break; + } + } + if (child) { + /* explicit child, no dflt change */ + break; + } + + /* set the dflt flag */ + node->flags |= LYD_DEFAULT; + + /* check all parent containers */ + node = lyd_parent(node); + } +} + +/** + * @brief Comparison callback to match schema node with a schema of a data node. + * + * @param[in] val1_p Pointer to the schema node + * @param[in] val2_p Pointer to the data node + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lyd_hash_table_schema_val_equal(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + struct lysc_node *val1; + struct lyd_node *val2; + + val1 = *((struct lysc_node **)val1_p); + val2 = *((struct lyd_node **)val2_p); + + if (val1 == val2->schema) { + /* schema match is enough */ + return 1; + } else { + return 0; + } +} + +LY_ERR +lyd_find_sibling_schema(const struct lyd_node *siblings, const struct lysc_node *schema, struct lyd_node **match) +{ + struct lyd_node **match_p; + struct lyd_node_inner *parent; + const struct lysc_node *cur_schema; + uint32_t hash; + lyht_value_equal_cb ht_cb; + + assert(schema); + if (!siblings) { + /* no data */ + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + parent = siblings->parent; + if (parent && parent->schema && parent->children_ht) { + /* calculate our hash */ + hash = dict_hash_multi(0, schema->module->name, strlen(schema->module->name)); + hash = dict_hash_multi(hash, schema->name, strlen(schema->name)); + hash = dict_hash_multi(hash, NULL, 0); + + /* use special hash table function */ + ht_cb = lyht_set_cb(parent->children_ht, lyd_hash_table_schema_val_equal); + + /* find by hash */ + if (!lyht_find(parent->children_ht, &schema, hash, (void **)&match_p)) { + siblings = *match_p; + } else { + /* not found */ + siblings = NULL; + } + + /* set the original hash table compare function back */ + lyht_set_cb(parent->children_ht, ht_cb); + } else { + /* find first sibling */ + if (siblings->parent) { + siblings = siblings->parent->child; + } else { + while (siblings->prev->next) { + siblings = siblings->prev; + } + } + + /* search manually without hashes */ + for ( ; siblings; siblings = siblings->next) { + cur_schema = lyd_node_schema(siblings); + if (!cur_schema) { + /* some unknown opaque node */ + continue; + } + + /* schema match is enough */ + if (cur_schema->module->ctx == schema->module->ctx) { + if (cur_schema == schema) { + break; + } + } else { + if (!strcmp(cur_schema->name, schema->name) && !strcmp(cur_schema->module->name, schema->module->name)) { + break; + } + } + } + } + + if (!siblings) { + if (match) { + *match = NULL; + } + return LY_ENOTFOUND; + } + + if (match) { + *match = (struct lyd_node *)siblings; + } + return LY_SUCCESS; +} + +void +lyd_del_move_root(struct lyd_node **root, const struct lyd_node *to_del, const struct lys_module *mod) +{ + if (*root && (lyd_owner_module(*root) != mod)) { + /* there are no data of mod so this is simply the first top-level sibling */ + mod = NULL; + } + + if ((*root != to_del) || (*root)->parent) { + return; + } + + if (mod && (*root)->prev->next && (!(*root)->next || (lyd_owner_module(to_del) != lyd_owner_module((*root)->next)))) { + /* there are no more nodes from mod, simply get the first top-level sibling */ + *root = lyd_first_sibling(*root); + } else { + *root = (*root)->next; + } +} + +LY_ERR +ly_nested_ext_schema(const struct lyd_node *parent, const struct lysc_node *sparent, const char *prefix, + size_t prefix_len, LY_VALUE_FORMAT format, void *prefix_data, const char *name, size_t name_len, + const struct lysc_node **snode, struct lysc_ext_instance **ext) +{ + LY_ERR r; + LY_ARRAY_COUNT_TYPE u; + struct lysc_ext_instance *nested_exts = NULL; + lyplg_ext_data_snode_clb ext_snode_cb; + + /* check if there are any nested extension instances */ + if (parent && parent->schema) { + nested_exts = parent->schema->exts; + } else if (sparent) { + nested_exts = sparent->exts; + } + LY_ARRAY_FOR(nested_exts, u) { + if (!nested_exts[u].def->plugin) { + /* no plugin */ + continue; + } + + ext_snode_cb = nested_exts[u].def->plugin->snode; + if (!ext_snode_cb) { + /* not an extension with nested data */ + continue; + } + + /* try to get the schema node */ + r = ext_snode_cb(&nested_exts[u], parent, sparent, prefix, prefix_len, format, prefix_data, name, name_len, snode); + if (!r) { + if (ext) { + /* data successfully created, remember the ext instance */ + *ext = &nested_exts[u]; + } + return LY_SUCCESS; + } else if (r != LY_ENOT) { + /* fatal error */ + return r; + } + /* data was not from this module, continue */ + } + + /* no extensions or none matched */ + return LY_ENOT; +} + +void +ly_free_prefix_data(LY_VALUE_FORMAT format, void *prefix_data) +{ + struct ly_set *ns_list; + struct lysc_prefix *prefixes; + uint32_t i; + LY_ARRAY_COUNT_TYPE u; + + if (!prefix_data) { + return; + } + + switch (format) { + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + ns_list = prefix_data; + for (i = 0; i < ns_list->count; ++i) { + free(((struct lyxml_ns *)ns_list->objs[i])->prefix); + free(((struct lyxml_ns *)ns_list->objs[i])->uri); + } + ly_set_free(ns_list, free); + break; + case LY_VALUE_SCHEMA_RESOLVED: + prefixes = prefix_data; + LY_ARRAY_FOR(prefixes, u) { + free(prefixes[u].prefix); + } + LY_ARRAY_FREE(prefixes); + break; + case LY_VALUE_CANON: + case LY_VALUE_SCHEMA: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + break; + } +} + +LY_ERR +ly_dup_prefix_data(const struct ly_ctx *ctx, LY_VALUE_FORMAT format, const void *prefix_data, + void **prefix_data_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxml_ns *ns; + struct lysc_prefix *prefixes = NULL, *orig_pref; + struct ly_set *ns_list, *orig_ns; + uint32_t i; + LY_ARRAY_COUNT_TYPE u; + + assert(!*prefix_data_p); + + switch (format) { + case LY_VALUE_SCHEMA: + *prefix_data_p = (void *)prefix_data; + break; + case LY_VALUE_SCHEMA_RESOLVED: + /* copy all the value prefixes */ + orig_pref = (struct lysc_prefix *)prefix_data; + LY_ARRAY_CREATE_GOTO(ctx, prefixes, LY_ARRAY_COUNT(orig_pref), ret, cleanup); + *prefix_data_p = prefixes; + + LY_ARRAY_FOR(orig_pref, u) { + if (orig_pref[u].prefix) { + prefixes[u].prefix = strdup(orig_pref[u].prefix); + LY_CHECK_ERR_GOTO(!prefixes[u].prefix, LOGMEM(ctx); ret = LY_EMEM, cleanup); + } + prefixes[u].mod = orig_pref[u].mod; + LY_ARRAY_INCREMENT(prefixes); + } + break; + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + /* copy all the namespaces */ + LY_CHECK_GOTO(ret = ly_set_new(&ns_list), cleanup); + *prefix_data_p = ns_list; + + orig_ns = (struct ly_set *)prefix_data; + for (i = 0; i < orig_ns->count; ++i) { + ns = calloc(1, sizeof *ns); + LY_CHECK_ERR_GOTO(!ns, LOGMEM(ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(ns_list, ns, 1, NULL), cleanup); + + if (((struct lyxml_ns *)orig_ns->objs[i])->prefix) { + ns->prefix = strdup(((struct lyxml_ns *)orig_ns->objs[i])->prefix); + LY_CHECK_ERR_GOTO(!ns->prefix, LOGMEM(ctx); ret = LY_EMEM, cleanup); + } + ns->uri = strdup(((struct lyxml_ns *)orig_ns->objs[i])->uri); + LY_CHECK_ERR_GOTO(!ns->uri, LOGMEM(ctx); ret = LY_EMEM, cleanup); + } + break; + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + assert(!prefix_data); + *prefix_data_p = NULL; + break; + } + +cleanup: + if (ret) { + ly_free_prefix_data(format, *prefix_data_p); + *prefix_data_p = NULL; + } + return ret; +} + +LY_ERR +ly_store_prefix_data(const struct ly_ctx *ctx, const void *value, size_t value_len, LY_VALUE_FORMAT format, + const void *prefix_data, LY_VALUE_FORMAT *format_p, void **prefix_data_p) +{ + LY_ERR ret = LY_SUCCESS; + const struct lys_module *mod; + const struct lyxml_ns *ns; + struct lyxml_ns *new_ns; + struct ly_set *ns_list; + struct lysc_prefix *prefixes = NULL, *val_pref; + const char *value_iter, *value_next, *value_end; + uint32_t substr_len; + ly_bool is_prefix; + + switch (format) { + case LY_VALUE_SCHEMA: + /* copy all referenced modules as prefix - module pairs */ + if (!*prefix_data_p) { + /* new prefix data */ + LY_ARRAY_CREATE_GOTO(ctx, prefixes, 0, ret, cleanup); + *format_p = LY_VALUE_SCHEMA_RESOLVED; + *prefix_data_p = prefixes; + } else { + /* reuse prefix data */ + assert(*format_p == LY_VALUE_SCHEMA_RESOLVED); + prefixes = *prefix_data_p; + } + + /* add current module for unprefixed values */ + LY_ARRAY_NEW_GOTO(ctx, prefixes, val_pref, ret, cleanup); + *prefix_data_p = prefixes; + + val_pref->prefix = NULL; + val_pref->mod = ((const struct lysp_module *)prefix_data)->mod; + + /* add all used prefixes */ + value_end = (char *)value + value_len; + for (value_iter = value; value_iter; value_iter = value_next) { + LY_CHECK_GOTO(ret = ly_value_prefix_next(value_iter, value_end, &substr_len, &is_prefix, &value_next), cleanup); + if (is_prefix) { + /* we have a possible prefix. Do we already have the prefix? */ + mod = ly_resolve_prefix(ctx, value_iter, substr_len, *format_p, *prefix_data_p); + if (!mod) { + mod = ly_resolve_prefix(ctx, value_iter, substr_len, format, prefix_data); + if (mod) { + assert(*format_p == LY_VALUE_SCHEMA_RESOLVED); + /* store a new prefix - module pair */ + LY_ARRAY_NEW_GOTO(ctx, prefixes, val_pref, ret, cleanup); + *prefix_data_p = prefixes; + + val_pref->prefix = strndup(value_iter, substr_len); + LY_CHECK_ERR_GOTO(!val_pref->prefix, LOGMEM(ctx); ret = LY_EMEM, cleanup); + val_pref->mod = mod; + } /* else it is not even defined */ + } /* else the prefix is already present */ + } + } + break; + case LY_VALUE_XML: + case LY_VALUE_STR_NS: + /* copy all referenced namespaces as prefix - namespace pairs */ + if (!*prefix_data_p) { + /* new prefix data */ + LY_CHECK_GOTO(ret = ly_set_new(&ns_list), cleanup); + *format_p = format; + *prefix_data_p = ns_list; + } else { + /* reuse prefix data */ + assert(*format_p == format); + ns_list = *prefix_data_p; + } + + /* store default namespace */ + ns = lyxml_ns_get(prefix_data, NULL, 0); + if (ns) { + new_ns = calloc(1, sizeof *new_ns); + LY_CHECK_ERR_GOTO(!new_ns, LOGMEM(ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(ns_list, new_ns, 1, NULL), cleanup); + + new_ns->prefix = NULL; + new_ns->uri = strdup(ns->uri); + LY_CHECK_ERR_GOTO(!new_ns->uri, LOGMEM(ctx); ret = LY_EMEM, cleanup); + } + + /* add all used prefixes */ + value_end = (char *)value + value_len; + for (value_iter = value; value_iter; value_iter = value_next) { + LY_CHECK_GOTO(ret = ly_value_prefix_next(value_iter, value_end, &substr_len, &is_prefix, &value_next), cleanup); + if (is_prefix) { + /* we have a possible prefix. Do we already have the prefix? */ + ns = lyxml_ns_get(ns_list, value_iter, substr_len); + if (!ns) { + ns = lyxml_ns_get(prefix_data, value_iter, substr_len); + if (ns) { + /* store a new prefix - namespace pair */ + new_ns = calloc(1, sizeof *new_ns); + LY_CHECK_ERR_GOTO(!new_ns, LOGMEM(ctx); ret = LY_EMEM, cleanup); + LY_CHECK_GOTO(ret = ly_set_add(ns_list, new_ns, 1, NULL), cleanup); + + new_ns->prefix = strndup(value_iter, substr_len); + LY_CHECK_ERR_GOTO(!new_ns->prefix, LOGMEM(ctx); ret = LY_EMEM, cleanup); + new_ns->uri = strdup(ns->uri); + LY_CHECK_ERR_GOTO(!new_ns->uri, LOGMEM(ctx); ret = LY_EMEM, cleanup); + } /* else it is not even defined */ + } /* else the prefix is already present */ + } + } + break; + case LY_VALUE_CANON: + case LY_VALUE_SCHEMA_RESOLVED: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + if (!*prefix_data_p) { + /* new prefix data - simply copy all the prefix data */ + *format_p = format; + LY_CHECK_GOTO(ret = ly_dup_prefix_data(ctx, format, prefix_data, prefix_data_p), cleanup); + } /* else reuse prefix data - the prefix data are always the same, nothing to do */ + break; + } + +cleanup: + if (ret) { + ly_free_prefix_data(*format_p, *prefix_data_p); + *prefix_data_p = NULL; + } + return ret; +} + +const char * +ly_format2str(LY_VALUE_FORMAT format) +{ + switch (format) { + case LY_VALUE_CANON: + return "canonical"; + case LY_VALUE_SCHEMA: + return "schema imports"; + case LY_VALUE_SCHEMA_RESOLVED: + return "schema stored mapping"; + case LY_VALUE_XML: + return "XML prefixes"; + case LY_VALUE_JSON: + return "JSON module names"; + case LY_VALUE_LYB: + return "LYB prefixes"; + default: + break; + } + + return NULL; +} + +LIBYANG_API_DEF LY_ERR +ly_time_str2time(const char *value, time_t *time, char **fractions_s) +{ + struct tm tm = {0}; + uint32_t i, frac_len; + const char *frac; + int64_t shift, shift_m; + time_t t; + + LY_CHECK_ARG_RET(NULL, value, time, LY_EINVAL); + + tm.tm_year = atoi(&value[0]) - 1900; + tm.tm_mon = atoi(&value[5]) - 1; + tm.tm_mday = atoi(&value[8]); + tm.tm_hour = atoi(&value[11]); + tm.tm_min = atoi(&value[14]); + tm.tm_sec = atoi(&value[17]); + + t = timegm(&tm); + i = 19; + + /* fractions of a second */ + if (value[i] == '.') { + ++i; + frac = &value[i]; + for (frac_len = 0; isdigit(frac[frac_len]); ++frac_len) {} + + i += frac_len; + } else { + frac = NULL; + } + + /* apply offset */ + if ((value[i] == 'Z') || (value[i] == 'z')) { + /* zero shift */ + shift = 0; + } else { + shift = strtol(&value[i], NULL, 10); + shift = shift * 60 * 60; /* convert from hours to seconds */ + shift_m = strtol(&value[i + 4], NULL, 10) * 60; /* includes conversion from minutes to seconds */ + /* correct sign */ + if (shift < 0) { + shift_m *= -1; + } + /* connect hours and minutes of the shift */ + shift = shift + shift_m; + } + + /* we have to shift to the opposite way to correct the time */ + t -= shift; + + *time = t; + if (fractions_s) { + if (frac) { + *fractions_s = strndup(frac, frac_len); + LY_CHECK_RET(!*fractions_s, LY_EMEM); + } else { + *fractions_s = NULL; + } + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_time_time2str(time_t time, const char *fractions_s, char **str) +{ + struct tm tm; + char zoneshift[8]; + int32_t zonediff_h, zonediff_m; + + LY_CHECK_ARG_RET(NULL, str, LY_EINVAL); + + /* initialize the local timezone */ + tzset(); + +#ifdef HAVE_TM_GMTOFF + /* convert */ + if (!localtime_r(&time, &tm)) { + return LY_ESYS; + } + + /* get timezone offset */ + if (tm.tm_gmtoff == 0) { + /* time is Zulu (UTC) */ + zonediff_h = 0; + zonediff_m = 0; + } else { + /* timezone offset */ + zonediff_h = tm.tm_gmtoff / 60 / 60; + zonediff_m = tm.tm_gmtoff / 60 % 60; + } + sprintf(zoneshift, "%+03d:%02d", zonediff_h, zonediff_m); +#else + /* convert */ + if (!gmtime_r(&time, &tm)) { + return LY_ESYS; + } + + (void)zonediff_h; + (void)zonediff_m; + sprintf(zoneshift, "-00:00"); +#endif + + /* print */ + if (asprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d%s%s%s", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + fractions_s ? "." : "", fractions_s ? fractions_s : "", zoneshift) == -1) { + return LY_EMEM; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_time_str2ts(const char *value, struct timespec *ts) +{ + LY_ERR rc; + char *fractions_s, frac_buf[10]; + int frac_len; + + LY_CHECK_ARG_RET(NULL, value, ts, LY_EINVAL); + + rc = ly_time_str2time(value, &ts->tv_sec, &fractions_s); + LY_CHECK_RET(rc); + + /* convert fractions of a second to nanoseconds */ + if (fractions_s) { + /* init frac_buf with zeroes */ + memset(frac_buf, '0', 9); + frac_buf[9] = '\0'; + + frac_len = strlen(fractions_s); + memcpy(frac_buf, fractions_s, frac_len > 9 ? 9 : frac_len); + ts->tv_nsec = atol(frac_buf); + free(fractions_s); + } else { + ts->tv_nsec = 0; + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +ly_time_ts2str(const struct timespec *ts, char **str) +{ + char frac_buf[10]; + + LY_CHECK_ARG_RET(NULL, ts, str, ((ts->tv_nsec <= 999999999) && (ts->tv_nsec >= 0)), LY_EINVAL); + + /* convert nanoseconds to fractions of a second */ + if (ts->tv_nsec) { + sprintf(frac_buf, "%09ld", ts->tv_nsec); + } + + return ly_time_time2str(ts->tv_sec, ts->tv_nsec ? frac_buf : NULL, str); +} diff --git a/src/tree_data_free.c b/src/tree_data_free.c new file mode 100644 index 0000000..bf17a91 --- /dev/null +++ b/src/tree_data_free.c @@ -0,0 +1,241 @@ +/** + * @file tree_data_free.c + * @author Radek Krejci + * @brief Freeing functions for data tree structures + * + * Copyright (c) 2019 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include + +#include "common.h" +#include "dict.h" +#include "hash_table.h" +#include "log.h" +#include "plugins_exts/metadata.h" +#include "plugins_types.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" + +static void +lyd_free_meta(struct lyd_meta *meta, ly_bool siblings) +{ + struct lyd_meta *iter; + + if (!meta) { + return; + } + + if (meta->parent) { + if (meta->parent->meta == meta) { + if (siblings) { + meta->parent->meta = NULL; + } else { + meta->parent->meta = meta->next; + } + } else { + for (iter = meta->parent->meta; iter->next != meta; iter = iter->next) {} + if (iter->next) { + if (siblings) { + iter->next = NULL; + } else { + iter->next = meta->next; + } + } + } + } + + if (!siblings) { + meta->next = NULL; + } + + for (iter = meta; iter; ) { + meta = iter; + iter = iter->next; + + lydict_remove(meta->annotation->module->ctx, meta->name); + meta->value.realtype->plugin->free(meta->annotation->module->ctx, &meta->value); + free(meta); + } +} + +LIBYANG_API_DEF void +lyd_free_meta_single(struct lyd_meta *meta) +{ + lyd_free_meta(meta, 0); +} + +LIBYANG_API_DEF void +lyd_free_meta_siblings(struct lyd_meta *meta) +{ + lyd_free_meta(meta, 1); +} + +static void +lyd_free_attr(const struct ly_ctx *ctx, struct lyd_attr *attr, ly_bool siblings) +{ + struct lyd_attr *iter; + + LY_CHECK_ARG_RET(NULL, ctx, ); + if (!attr) { + return; + } + + if (attr->parent) { + if (attr->parent->attr == attr) { + if (siblings) { + attr->parent->attr = NULL; + } else { + attr->parent->attr = attr->next; + } + } else { + for (iter = attr->parent->attr; iter->next != attr; iter = iter->next) {} + if (iter->next) { + if (siblings) { + iter->next = NULL; + } else { + iter->next = attr->next; + } + } + } + } + + if (!siblings) { + attr->next = NULL; + } + + for (iter = attr; iter; ) { + attr = iter; + iter = iter->next; + + ly_free_prefix_data(attr->format, attr->val_prefix_data); + lydict_remove(ctx, attr->name.name); + lydict_remove(ctx, attr->name.prefix); + lydict_remove(ctx, attr->name.module_ns); + lydict_remove(ctx, attr->value); + free(attr); + } +} + +LIBYANG_API_DEF void +lyd_free_attr_single(const struct ly_ctx *ctx, struct lyd_attr *attr) +{ + lyd_free_attr(ctx, attr, 0); +} + +LIBYANG_API_DEF void +lyd_free_attr_siblings(const struct ly_ctx *ctx, struct lyd_attr *attr) +{ + lyd_free_attr(ctx, attr, 1); +} + +/** + * @brief Free Data (sub)tree. + * @param[in] node Data node to be freed. + * @param[in] top Recursion flag to unlink the root of the subtree being freed. + */ +static void +lyd_free_subtree(struct lyd_node *node, ly_bool top) +{ + struct lyd_node *iter, *next; + struct lyd_node_opaq *opaq = NULL; + + assert(node); + + if (!node->schema) { + opaq = (struct lyd_node_opaq *)node; + + /* free the children */ + LY_LIST_FOR_SAFE(lyd_child(node), next, iter) { + lyd_free_subtree(iter, 0); + } + + lydict_remove(LYD_CTX(opaq), opaq->name.name); + lydict_remove(LYD_CTX(opaq), opaq->name.prefix); + lydict_remove(LYD_CTX(opaq), opaq->name.module_ns); + lydict_remove(LYD_CTX(opaq), opaq->value); + ly_free_prefix_data(opaq->format, opaq->val_prefix_data); + } else if (node->schema->nodetype & LYD_NODE_INNER) { + /* remove children hash table in case of inner data node */ + lyht_free(((struct lyd_node_inner *)node)->children_ht); + ((struct lyd_node_inner *)node)->children_ht = NULL; + + /* free the children */ + LY_LIST_FOR_SAFE(lyd_child(node), next, iter) { + lyd_free_subtree(iter, 0); + } + } else if (node->schema->nodetype & LYD_NODE_ANY) { + /* only frees the value this way */ + lyd_any_copy_value(node, NULL, 0); + } else if (node->schema->nodetype & LYD_NODE_TERM) { + ((struct lysc_node_leaf *)node->schema)->type->plugin->free(LYD_CTX(node), &((struct lyd_node_term *)node)->value); + } + + if (!node->schema) { + lyd_free_attr_siblings(LYD_CTX(node), opaq->attr); + } else { + /* free the node's metadata */ + lyd_free_meta_siblings(node->meta); + } + + /* unlink only the nodes from the first level, nodes in subtree are freed all, so no unlink is needed */ + if (top) { + lyd_unlink_tree(node); + } + + free(node); +} + +LIBYANG_API_DEF void +lyd_free_tree(struct lyd_node *node) +{ + if (!node) { + return; + } + + lyd_free_subtree(node, 1); +} + +static void +lyd_free_(struct lyd_node *node, ly_bool top) +{ + struct lyd_node *iter, *next; + + if (!node) { + return; + } + + /* get the first (top-level) sibling */ + if (top) { + for ( ; node->parent; node = lyd_parent(node)) {} + } + while (node->prev->next) { + node = node->prev; + } + + LY_LIST_FOR_SAFE(node, next, iter) { + /* in case of the top-level nodes (node->parent is NULL), no unlinking needed */ + lyd_free_subtree(iter, iter->parent ? 1 : 0); + } +} + +LIBYANG_API_DEF void +lyd_free_siblings(struct lyd_node *node) +{ + lyd_free_(node, 0); +} + +LIBYANG_API_DEF void +lyd_free_all(struct lyd_node *node) +{ + lyd_free_(node, 1); +} diff --git a/src/tree_data_hash.c b/src/tree_data_hash.c new file mode 100644 index 0000000..52f99a8 --- /dev/null +++ b/src/tree_data_hash.c @@ -0,0 +1,237 @@ +/** + * @file tree_data_hash.c + * @author Radek Krejci + * @brief Functions to manipulate with the data node's hashes. + * + * Copyright (c) 2019 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "hash_table.h" +#include "log.h" +#include "plugins_types.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_schema.h" + +LY_ERR +lyd_hash(struct lyd_node *node) +{ + struct lyd_node *iter; + const void *hash_key; + ly_bool dyn; + size_t key_len; + + if (!node->schema) { + return LY_SUCCESS; + } + + /* hash always starts with the module and schema name */ + node->hash = dict_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); + node->hash = dict_hash_multi(node->hash, node->schema->name, strlen(node->schema->name)); + + if (node->schema->nodetype == LYS_LIST) { + if (node->schema->flags & LYS_KEYLESS) { + /* key-less list simply calls hash function again with empty key, + * just so that it differs from the first-instance hash + */ + node->hash = dict_hash_multi(node->hash, NULL, 0); + } else { + struct lyd_node_inner *list = (struct lyd_node_inner *)node; + + /* list hash is made up from its keys */ + for (iter = list->child; iter && iter->schema && (iter->schema->flags & LYS_KEY); iter = iter->next) { + struct lyd_node_term *key = (struct lyd_node_term *)iter; + + hash_key = key->value.realtype->plugin->print(NULL, &key->value, LY_VALUE_LYB, NULL, &dyn, &key_len); + node->hash = dict_hash_multi(node->hash, hash_key, key_len); + if (dyn) { + free((void *)hash_key); + } + } + } + } else if (node->schema->nodetype == LYS_LEAFLIST) { + /* leaf-list adds its hash key */ + struct lyd_node_term *llist = (struct lyd_node_term *)node; + + hash_key = llist->value.realtype->plugin->print(NULL, &llist->value, LY_VALUE_LYB, NULL, &dyn, &key_len); + node->hash = dict_hash_multi(node->hash, hash_key, key_len); + if (dyn) { + free((void *)hash_key); + } + } + + /* finish the hash */ + node->hash = dict_hash_multi(node->hash, NULL, 0); + + return LY_SUCCESS; +} + +/** + * @brief Compare callback for values in hash table. + * + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lyd_hash_table_val_equal(void *val1_p, void *val2_p, ly_bool mod, void *UNUSED(cb_data)) +{ + struct lyd_node *val1, *val2; + + val1 = *((struct lyd_node **)val1_p); + val2 = *((struct lyd_node **)val2_p); + + if (mod) { + if (val1 == val2) { + return 1; + } else { + return 0; + } + } + + if (val1->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { + /* match on exact instance */ + if (!lyd_compare_single(val1, val2, 0)) { + return 1; + } + } else if (val1->schema == val2->schema) { + /* just schema match */ + return 1; + } + return 0; +} + +/** + * @brief Add single node into children hash table. + * + * @param[in] ht Children hash table. + * @param[in] node Node to insert. + * @param[in] empty_ht Whether we started with an empty HT meaning no nodes were inserted yet. + * @return LY_ERR value. + */ +static LY_ERR +lyd_insert_hash_add(struct hash_table *ht, struct lyd_node *node, ly_bool empty_ht) +{ + uint32_t hash; + + assert(ht && node && node->schema); + + /* add node itself */ + if (lyht_insert(ht, &node, node->hash, NULL)) { + LOGINT_RET(LYD_CTX(node)); + } + + /* add first instance of a (leaf-)list */ + if ((node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && + (!node->prev->next || (node->prev->schema != node->schema))) { + /* get the simple hash */ + hash = dict_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); + hash = dict_hash_multi(hash, node->schema->name, strlen(node->schema->name)); + hash = dict_hash_multi(hash, NULL, 0); + + /* remove any previous stored instance, only if we did not start with an empty HT */ + if (!empty_ht && node->next && (node->next->schema == node->schema)) { + if (lyht_remove(ht, &node->next, hash)) { + LOGINT_RET(LYD_CTX(node)); + } + } + + /* in this case there would be the exact same value twice in the hash table, not supported (by the HT) */ + assert(hash != node->hash); + + /* insert this instance as the first (leaf-)list instance */ + if (lyht_insert(ht, &node, hash, NULL)) { + LOGINT_RET(LYD_CTX(node)); + } + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_insert_hash(struct lyd_node *node) +{ + struct lyd_node *iter; + uint32_t u; + + if (!node->parent || !node->schema || !node->parent->schema) { + /* nothing to do */ + return LY_SUCCESS; + } + + /* create parent hash table if required, otherwise just add the new child */ + if (!node->parent->children_ht) { + /* the hash table is created only when the number of children in a node exceeds the + * defined minimal limit LYD_HT_MIN_ITEMS + */ + u = 0; + LY_LIST_FOR(node->parent->child, iter) { + if (iter->schema) { + ++u; + } + } + if (u >= LYD_HT_MIN_ITEMS) { + /* create hash table, insert all the children */ + node->parent->children_ht = lyht_new(1, sizeof(struct lyd_node *), lyd_hash_table_val_equal, NULL, 1); + LY_LIST_FOR(node->parent->child, iter) { + if (iter->schema) { + LY_CHECK_RET(lyd_insert_hash_add(node->parent->children_ht, iter, 1)); + } + } + } + } else { + LY_CHECK_RET(lyd_insert_hash_add(node->parent->children_ht, node, 0)); + } + + return LY_SUCCESS; +} + +void +lyd_unlink_hash(struct lyd_node *node) +{ + uint32_t hash; + + if (!node->parent || !node->schema || !node->parent->schema || !node->parent->children_ht) { + /* not in any HT */ + return; + } + + /* remove from the parent HT */ + if (lyht_remove(node->parent->children_ht, &node, node->hash)) { + LOGINT(LYD_CTX(node)); + return; + } + + /* first instance of the (leaf-)list, needs to be removed from HT */ + if ((node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && (!node->prev->next || (node->prev->schema != node->schema))) { + /* get the simple hash */ + hash = dict_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); + hash = dict_hash_multi(hash, node->schema->name, strlen(node->schema->name)); + hash = dict_hash_multi(hash, NULL, 0); + + /* remove the instance */ + if (lyht_remove(node->parent->children_ht, &node, hash)) { + LOGINT(LYD_CTX(node)); + return; + } + + /* add the next instance */ + if (node->next && (node->next->schema == node->schema)) { + if (lyht_insert(node->parent->children_ht, &node->next, hash, NULL)) { + LOGINT(LYD_CTX(node)); + return; + } + } + } +} diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h new file mode 100644 index 0000000..fd0792d --- /dev/null +++ b/src/tree_data_internal.h @@ -0,0 +1,590 @@ +/** + * @file tree_data_internal.h + * @author Radek Krejci + * @author Michal Vasko + * @brief internal functions for YANG schema trees. + * + * Copyright (c) 2015 - 2022 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_TREE_DATA_INTERNAL_H_ +#define LY_TREE_DATA_INTERNAL_H_ + +#include "log.h" +#include "plugins_types.h" +#include "tree_data.h" + +#include + +struct ly_path_predicate; +struct lyd_ctx; +struct lysc_module; + +#define LY_XML_SUFFIX ".xml" +#define LY_XML_SUFFIX_LEN 4 +#define LY_JSON_SUFFIX ".json" +#define LY_JSON_SUFFIX_LEN 5 +#define LY_LYB_SUFFIX ".lyb" +#define LY_LYB_SUFFIX_LEN 4 + +/** + * @brief Internal structure for remembering "used" instances of lists with duplicate instances allowed. + */ +struct lyd_dup_inst { + struct ly_set *inst_set; + uint32_t used; +}; + +/** + * @brief Update a found inst using a duplicate instance cache. Needs to be called for every "used" + * (that should not be considered next time) instance. + * + * @param[in,out] inst Found instance, is updated so that the same instance is not returned twice. + * @param[in] siblings Siblings where @p inst was found. + * @param[in,out] dup_inst_cache Duplicate instance cache. + * @return LY_ERR value. + */ +LY_ERR lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, + struct lyd_dup_inst **dup_inst_cache); + +/** + * @brief Free duplicate instance cache. + * + * @param[in] dup_inst Duplicate instance cache to free. + */ +void lyd_dup_inst_free(struct lyd_dup_inst *dup_inst); + +/** + * @brief Just like ::lys_getnext() but iterates over all data instances of the schema nodes. + * + * @param[in] last Last returned data node. + * @param[in] sibling Data node sibling to search in. + * @param[in,out] slast Schema last node, set to NULL for first call and do not change afterwards. + * May not be set if the function is used only for any suitable node existence check (such as the existence + * of any choice case data). + * @param[in] parent Schema parent of the iterated children nodes. + * @param[in] module Schema module of the iterated top-level nodes. + * @return Next matching data node, + * @return NULL if last data node was already returned. + */ +struct lyd_node *lys_getnext_data(const struct lyd_node *last, const struct lyd_node *sibling, + const struct lysc_node **slast, const struct lysc_node *parent, const struct lysc_module *module); + +/** + * @brief Get address of a node's child pointer if any. + * + * @param[in] node Node to check. + * @return Address of the node's child member, + * @return NULL if there is no child pointer. + */ +struct lyd_node **lyd_node_child_p(struct lyd_node *node); + +/** + * @brief Update node pointer to point to the first data node of a module, leave unchanged if there is none. + * + * @param[in,out] node Node pointer, may be updated. + * @param[in] mod Module whose data to search for. + */ +void lyd_first_module_sibling(struct lyd_node **node, const struct lys_module *mod); + +/** + * @brief Iterate over implemented modules for functions that accept specific modules or the whole context. + * + * @param[in] tree Data tree. + * @param[in] module Selected module, NULL for all. + * @param[in] ctx Context, NULL for selected modules. + * @param[in,out] i Iterator, set to 0 on first call. + * @param[out] first First sibling of the returned module. + * @return Next module. + * @return NULL if all modules were traversed. + */ +const struct lys_module *lyd_mod_next_module(struct lyd_node *tree, const struct lys_module *module, + const struct ly_ctx *ctx, uint32_t *i, struct lyd_node **first); + +/** + * @brief Iterate over modules for functions that want to traverse all the top-level data. + * + * @param[in,out] next Pointer to the next module data, set to first top-level sibling on first call. + * @param[out] first First sibling of the returned module. + * @return Next module. + * @return NULL if all modules were traversed. + */ +const struct lys_module *lyd_data_next_module(struct lyd_node **next, struct lyd_node **first); + +/** + * @brief Get schema node of a data node. Useful especially for opaque nodes. + * + * @param[in] node Data node to use. + * @return Schema node represented by data @p node, NULL if there is none. + */ +const struct lysc_node *lyd_node_schema(const struct lyd_node *node); + +/** + * @brief Set dflt flag for a NP container if applicable, recursively for parents. + * + * @param[in] node Node whose criteria for the dflt flag has changed. + */ +void lyd_cont_set_dflt(struct lyd_node *node); + +/** + * @brief Search in the given siblings (NOT recursively) for the first schema node data instance. + * Uses hashes - should be used whenever possible for best performance. + * + * @param[in] siblings Siblings to search in including preceding and succeeding nodes. + * @param[in] schema Target data node schema to find. + * @param[out] match Can be NULL, otherwise the found data node. + * @return LY_SUCCESS on success, @p match set. + * @return LY_ENOTFOUND if not found, @p match set to NULL. + * @return LY_ERR value if another error occurred. + */ +LY_ERR lyd_find_sibling_schema(const struct lyd_node *siblings, const struct lysc_node *schema, struct lyd_node **match); + +/** + * @brief Check whether a node to be deleted is the root node, move it if it is. + * + * @param[in] root Root sibling. + * @param[in] to_del Node to be deleted. + * @param[in] mod If set, it is expected @p tree should point to the first node of @p mod. Otherwise it will simply be + * the first top-level sibling. + */ +void lyd_del_move_root(struct lyd_node **root, const struct lyd_node *to_del, const struct lys_module *mod); + +/** + * @brief Try to get schema node for data with a parent based on an extension instance. + * + * @param[in] parent Parsed parent data node. Set if @p sparent is NULL. + * @param[in] sparent Schema parent node. Set if @p sparent is NULL. + * @param[in] prefix Element prefix, if any. + * @param[in] prefix_len Length of @p prefix. + * @param[in] format Format of @p prefix. + * @param[in] prefix_data Format-specific data. + * @param[in] name Element name. + * @param[in] name_len Length of @p name. + * @param[out] snode Found schema node, NULL if no suitable was found. + * @param[out] ext Optional extension instance that provided @p snode. + * @return LY_SUCCESS on success; + * @return LY_ENOT if no extension instance parsed the data; + * @return LY_ERR on error. + */ +LY_ERR ly_nested_ext_schema(const struct lyd_node *parent, const struct lysc_node *sparent, const char *prefix, + size_t prefix_len, LY_VALUE_FORMAT format, void *prefix_data, const char *name, size_t name_len, + const struct lysc_node **snode, struct lysc_ext_instance **ext); + +/** + * @brief Free stored prefix data. + * + * @param[in] format Format of the prefixes. + * @param[in] prefix_data Format-specific data to free: + * LY_PREF_SCHEMA - const struct lysp_module * (module used for resolving prefixes from imports) + * LY_PREF_SCHEMA_RESOLVED - struct lyd_value_prefix * (sized array of pairs: prefix - module) + * LY_PREF_XML - const struct ly_set * (set with defined namespaces stored as ::lyxml_ns) + * LY_PREF_JSON - NULL + */ +void ly_free_prefix_data(LY_VALUE_FORMAT format, void *prefix_data); + +/** + * @brief Duplicate prefix data. + * + * @param[in] ctx libyang context. + * @param[in] format Format of the prefixes in the value. + * @param[in] prefix_data Prefix data to duplicate. + * @param[out] prefix_data_p Duplicated prefix data. + * @return LY_ERR value. + */ +LY_ERR ly_dup_prefix_data(const struct ly_ctx *ctx, LY_VALUE_FORMAT format, const void *prefix_data, void **prefix_data_p); + +/** + * @brief Store used prefixes in a string. + * + * If @p prefix_data_p are non-NULL, they are treated as valid according to the @p format_p and new possible + * prefixes are simply added. This way it is possible to store prefix data for several strings together. + * + * @param[in] ctx libyang context. + * @param[in] value Value to be parsed. + * @param[in] value_len Length of the @p value. + * @param[in] format Format of the prefixes in the value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in,out] format_p Resulting format of the prefixes. + * @param[in,out] prefix_data_p Resulting prefix data for the value in format @p format_p. + * @return LY_ERR value. + */ +LY_ERR ly_store_prefix_data(const struct ly_ctx *ctx, const void *value, size_t value_len, LY_VALUE_FORMAT format, + const void *prefix_data, LY_VALUE_FORMAT *format_p, void **prefix_data_p); + +/** + * @brief Get string name of the format. + * + * @param[in] format Format whose name to get. + * @return Format string name. + */ +const char *ly_format2str(LY_VALUE_FORMAT format); + +/** + * @brief Create a term (leaf/leaf-list) node from a string value. + * + * Hash is calculated and new node flag is set. + * + * @param[in] schema Schema node of the new data node. + * @param[in] value String value to be parsed. + * @param[in] value_len Length of @p value, must be set correctly. + * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. + * @param[in] format Input format of @p value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] hints [Value hints](@ref lydvalhints) from the parser regarding the value type. + * @param[out] incomplete Whether the value needs to be resolved. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_EINCOMPLETE in case data tree is needed to finish the validation. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_len, ly_bool *dynamic, + LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, ly_bool *incomplete, struct lyd_node **node); + +/** + * @brief Create a term (leaf/leaf-list) node from a parsed value by duplicating it. + * + * Hash is calculated and new node flag is set. + * + * @param[in] schema Schema node of the new data node. + * @param[in] val Parsed value to use. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_term2(const struct lysc_node *schema, const struct lyd_value *val, struct lyd_node **node); + +/** + * @brief Create an inner (container/list/RPC/action/notification) node. + * + * Hash is calculated and new node flag is set except + * for list with keys, when the hash is not calculated! + * Also, non-presence container has its default flag set. + * + * @param[in] schema Schema node of the new data node. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_inner(const struct lysc_node *schema, struct lyd_node **node); + +/** + * @brief Create a list with all its keys (cannot be used for key-less list). + * + * Hash is calculated and new node flag is set. + * + * @param[in] schema Schema node of the new data node. + * @param[in] predicates Compiled key list predicates. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, struct lyd_node **node); + +/** + * @brief Create a list with all its keys (cannot be used for key-less list). + * + * Hash is calculated and new node flag is set. + * + * @param[in] schema Schema node of the new data node. + * @param[in] keys Key list predicates. + * @param[in] keys_len Length of @p keys. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_list2(const struct lysc_node *schema, const char *keys, size_t keys_len, struct lyd_node **node); + +/** + * @brief Create an anyxml/anydata node. + * + * Hash is calculated and flags are properly set based on @p is_valid. + * + * @param[in] schema Schema node of the new data node. + * @param[in] value Value of the any node. + * @param[in] value_type Value type of the value. + * @param[in] use_value Whether to use dynamic @p value or duplicate it. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_any(const struct lysc_node *schema, const void *value, LYD_ANYDATA_VALUETYPE value_type, + ly_bool use_value, struct lyd_node **node); + +/** + * @brief Create an opaque node. + * + * @param[in] ctx libyang context. + * @param[in] name Element name. + * @param[in] name_len Length of @p name, must be set correctly. + * @param[in] prefix Element prefix. + * @param[in] pref_len Length of @p prefix, must be set correctly. + * @param[in] module_key Mandatory key to reference module, can be namespace or name. + * @param[in] module_key_len Length of @p module_key, must be set correctly. + * @param[in] value String value to be parsed. + * @param[in] value_len Length of @p value, must be set correctly. + * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. + * @param[in] format Input format of @p value and @p ns. + * @param[in] val_prefix_data Format-specific prefix data, param is spent (even in case the function fails): + * LY_PREF_SCHEMA - const struct lysp_module * (module used for resolving prefixes from imports) + * LY_PREF_SCHEMA_RESOLVED - struct lyd_value_prefix * (sized array of pairs: prefix - module) + * LY_PREF_XML - const struct ly_set * (set with defined namespaces stored as ::lyxml_ns) + * LY_PREF_JSON - NULL + * @param[in] hints [Hints](@ref lydhints) from the parser regarding the node/value type. + * @param[out] node Created node. + * @return LY_SUCCESS on success. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_opaq(const struct ly_ctx *ctx, const char *name, size_t name_len, const char *prefix, size_t pref_len, + const char *module_key, size_t module_key_len, const char *value, size_t value_len, ly_bool *dynamic, + LY_VALUE_FORMAT format, void *val_prefix_data, uint32_t hints, struct lyd_node **node); + +/** + * @brief Check the existence and create any non-existing implicit siblings, recursively for the created nodes. + * + * @param[in] parent Parent of the potential default values, NULL for top-level siblings. + * @param[in,out] first First sibling. + * @param[in] sparent Schema parent of the siblings, NULL if schema of @p parent can be used. + * @param[in] mod Module of the default values, NULL for nested siblings. + * @param[in] node_when Optional set to add nodes with "when" conditions into. + * @param[in] node_types Optional set to add nodes with unresolved types into. + * @param[in] ext_node Optional set to add nodes with extension instance node callbacks into. + * @param[in] impl_opts Implicit options (@ref implicitoptions). + * @param[in,out] diff Validation diff. + * @return LY_ERR value. + */ +LY_ERR lyd_new_implicit_r(struct lyd_node *parent, struct lyd_node **first, const struct lysc_node *sparent, + const struct lys_module *mod, struct ly_set *node_when, struct ly_set *node_types, struct ly_set *ext_node, + uint32_t impl_opts, struct lyd_node **diff); + +/** + * @brief Find the next node, before which to insert the new node. + * + * @param[in] first_sibling First sibling of the nodes to consider. + * @param[in] new_node Node that will be inserted. + * @return Node to insert after. + * @return NULL if the new node should be first. + */ +struct lyd_node *lyd_insert_get_next_anchor(const struct lyd_node *first_sibling, const struct lyd_node *new_node); + +/** + * @brief Insert node after a sibling. + * + * Handles inserting into NP containers and key-less lists. + * + * @param[in] sibling Sibling to insert after. + * @param[in] node Node to insert. + */ +void lyd_insert_after_node(struct lyd_node *sibling, struct lyd_node *node); + +/** + * @brief Insert node before a sibling. + * + * Handles inserting into NP containers and key-less lists. + * + * @param[in] sibling Sibling to insert before. + * @param[in] node Node to insert. + */ +void lyd_insert_before_node(struct lyd_node *sibling, struct lyd_node *node); + +/** + * @brief Insert a node into parent/siblings. Order and hashes are fully handled. + * + * @param[in] parent Parent to insert into, NULL for top-level sibling. + * @param[in,out] first_sibling First sibling, NULL if no top-level sibling exist yet. Can be also NULL if @p parent is set. + * @param[in] node Individual node (without siblings) to insert. + * @param[in] last If set, do not search for the correct anchor but always insert at the end. + */ +void lyd_insert_node(struct lyd_node *parent, struct lyd_node **first_sibling, struct lyd_node *node, ly_bool last); + +/** + * @brief Insert a metadata (last) into a parent + * + * @param[in] parent Parent of the metadata. + * @param[in] meta Metadata (list) to be added into the @p parent. + * @param[in] clear_dflt Whether to clear dflt flag starting from @p parent, recursively all NP containers. + */ +void lyd_insert_meta(struct lyd_node *parent, struct lyd_meta *meta, ly_bool clear_dflt); + +/** + * @brief Create and insert a metadata (last) into a parent. + * + * @param[in] parent Parent of the metadata, can be NULL. + * @param[in,out] meta Metadata list to add at its end if @p parent is NULL, returned created attribute. + * @param[in] mod Metadata module (with the annotation definition). + * @param[in] name Attribute name. + * @param[in] name_len Length of @p name, must be set correctly. + * @param[in] value String value to be parsed. + * @param[in] value_len Length of @p value, must be set correctly. + * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. + * @param[in] format Input format of @p value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] hints [Value hints](@ref lydvalhints) from the parser regarding the value type. + * @param[in] ctx_node Value context node, may be NULL for metadata. + * @param[in] clear_dflt Whether to clear dflt flag starting from @p parent, recursively all NP containers. + * @param[out] incomplete Whether the value needs to be resolved. + * @return LY_SUCCESS on success. + * @return LY_EINCOMPLETE in case data tree is needed to finish the validation. + * @return LY_ERR value if an error occurred. + */ +LY_ERR lyd_create_meta(struct lyd_node *parent, struct lyd_meta **meta, const struct lys_module *mod, const char *name, + size_t name_len, const char *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, + void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, ly_bool clear_dflt, ly_bool *incomplete); + +/** + * @brief Insert an attribute (last) into a parent + * + * @param[in] parent Parent of the attributes. + * @param[in] attr Attribute (list) to be added into the @p parent. + */ +void lyd_insert_attr(struct lyd_node *parent, struct lyd_attr *attr); + +/** + * @brief Create and insert a generic attribute (last) into a parent. + * + * @param[in] parent Parent of the attribute, can be NULL. + * @param[in,out] attr Attribute list to add at its end if @p parent is NULL, returned created attribute. + * @param[in] ctx libyang context. + * @param[in] name Attribute name. + * @param[in] name_len Length of @p name, must be set correctly. + * @param[in] prefix Attribute prefix. + * @param[in] prefix_len Attribute prefix length. + * @param[in] module_key Mandatory key to reference module, can be namespace or name. + * @param[in] module_key_len Length of @p module_key, must be set correctly. + * @param[in] value String value to be parsed. + * @param[in] value_len Length of @p value, must be set correctly. + * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. + * @param[in] format Input format of @p value and @p ns. + * @param[in] val_prefix_data Format-specific prefix data, param is spent (even in case the function fails). + * @param[in] hints [Hints](@ref lydhints) from the parser regarding the node/value type. + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LY_ERR lyd_create_attr(struct lyd_node *parent, struct lyd_attr **attr, const struct ly_ctx *ctx, const char *name, + size_t name_len, const char *prefix, size_t prefix_len, const char *module_key, size_t module_key_len, + const char *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *val_prefix_data, uint32_t hints); + +/** + * @brief Store and canonize the given @p value into @p val according to the schema node type rules. + * + * @param[in] ctx libyang context. + * @param[in,out] val Storage for the value. + * @param[in] type Type of the value. + * @param[in] value Value to be parsed, must not be NULL. + * @param[in] value_len Length of the give @p value, must be set correctly. + * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. + * @param[in] format Input format of @p value. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] hints [Value hints](@ref lydvalhints) from the parser. + * @param[in] ctx_node Context schema node. + * @param[out] incomplete Optional, set if the value also needs to be resolved. + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LY_ERR lyd_value_store(const struct ly_ctx *ctx, struct lyd_value *val, const struct lysc_type *type, const void *value, + size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, ly_bool *incomplete); + +/** + * @brief Validate previously incompletely stored value. + * + * @param[in] ctx libyang context. + * @param[in] type Schema type of the value (not the stored one, but the original one). + * @param[in,out] val Stored value to resolve. + * @param[in] ctx_node Context node for the resolution. + * @param[in] tree Data tree for the resolution. + * @return LY_SUCCESS on success, + * @return LY_ERR value on error. + */ +LY_ERR lyd_value_validate_incomplete(const struct ly_ctx *ctx, const struct lysc_type *type, struct lyd_value *val, + const struct lyd_node *ctx_node, const struct lyd_node *tree); + +/** + * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value coming + * from a schema. + * + * This function check just the type's restriction, if you want to check also the data tree context (e.g. in case of + * require-instance restriction), use ::lyd_value_validate(). + * + * @param[in] ctx libyang context for logging (function does not log errors when @p ctx is NULL) + * @param[in] node Schema node for the @p value. + * @param[in] value String value to be checked, expected to be in JSON format. + * @param[in] value_len Length of the given @p value (mandatory). + * @param[in] format Value prefix format. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @return LY_SUCCESS on success + * @return LY_ERR value if an error occurred. + */ +LY_ERR lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len, + LY_VALUE_FORMAT format, void *prefix_data); + +/** + * @defgroup datahash Data nodes hash manipulation + * @ingroup datatree + * @{ + */ + +/** + * @brief Generate hash for the node. + * + * @param[in] node Data node to (re)generate hash value. + * @return LY_ERR value. + */ +LY_ERR lyd_hash(struct lyd_node *node); + +/** + * @brief Insert hash of the node into the hash table of its parent. + * + * @param[in] node Data node which hash will be inserted into the ::lyd_node_inner.children_ht hash table of its parent. + * @return LY_ERR value. + */ +LY_ERR lyd_insert_hash(struct lyd_node *node); + +/** + * @brief Maintain node's parent's children hash table when unlinking the node. + * + * When completely freeing data tree, it is expected to free the parent's children hash table first, at once. + * + * @param[in] node The data node being unlinked from its parent. + */ +void lyd_unlink_hash(struct lyd_node *node); + +/** @} datahash */ + +/** + * @brief Append all list key predicates to path. + * + * @param[in] node Node with keys to print. + * @param[in,out] buffer Buffer to print to. + * @param[in,out] buflen Current buffer length. + * @param[in,out] bufused Current number of characters used in @p buffer. + * @param[in] is_static Whether buffer is static or can be reallocated. + * @return LY_ERR value. + */ +LY_ERR lyd_path_list_predicate(const struct lyd_node *node, char **buffer, size_t *buflen, size_t *bufused, ly_bool is_static); + +/** + * @brief Generate a path similar to ::lyd_path() except read the parents from a set. + * + * @param[in] dnodes Set with the data nodes, from parent to the last descendant. + * @param[in] pathtype Type of data path to generate. + * @return Generated data path. + */ +char *lyd_path_set(const struct ly_set *dnodes, LYD_PATH_TYPE pathtype); + +/** + * @brief Remove an object on the specific set index keeping the order of the other objects. + * + * @param[in] set Set from which a node will be removed. + * @param[in] index Index of the object to remove in the \p set. + * @param[in] destructor Optional function to free the objects being removed. + * @return LY_ERR value. + */ +LY_ERR ly_set_rm_index_ordered(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)); + +#endif /* LY_TREE_DATA_INTERNAL_H_ */ diff --git a/src/tree_data_new.c b/src/tree_data_new.c new file mode 100644 index 0000000..752b181 --- /dev/null +++ b/src/tree_data_new.c @@ -0,0 +1,1914 @@ +/** + * @file tree_data_new.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Data tree new functions + * + * Copyright (c) 2015 - 2022 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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "diff.h" +#include "hash_table.h" +#include "in.h" +#include "in_internal.h" +#include "log.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "path.h" +#include "plugins.h" +#include "plugins_exts/metadata.h" +#include "plugins_internal.h" +#include "plugins_types.h" +#include "set.h" +#include "tree.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "validation.h" +#include "xml.h" +#include "xpath.h" + +LY_ERR +lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_len, ly_bool *dynamic, + LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, ly_bool *incomplete, struct lyd_node **node) +{ + LY_ERR ret; + struct lyd_node_term *term; + + assert(schema->nodetype & LYD_NODE_TERM); + + term = calloc(1, sizeof *term); + LY_CHECK_ERR_RET(!term, LOGMEM(schema->module->ctx), LY_EMEM); + + term->schema = schema; + term->prev = &term->node; + term->flags = LYD_NEW; + + LOG_LOCSET(schema, NULL, NULL, NULL); + ret = lyd_value_store(schema->module->ctx, &term->value, ((struct lysc_node_leaf *)term->schema)->type, value, + value_len, dynamic, format, prefix_data, hints, schema, incomplete); + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_ERR_RET(ret, free(term), ret); + lyd_hash(&term->node); + + *node = &term->node; + return ret; +} + +LY_ERR +lyd_create_term2(const struct lysc_node *schema, const struct lyd_value *val, struct lyd_node **node) +{ + LY_ERR ret; + struct lyd_node_term *term; + struct lysc_type *type; + + assert(schema->nodetype & LYD_NODE_TERM); + assert(val && val->realtype); + + term = calloc(1, sizeof *term); + LY_CHECK_ERR_RET(!term, LOGMEM(schema->module->ctx), LY_EMEM); + + term->schema = schema; + term->prev = &term->node; + term->flags = LYD_NEW; + + type = ((struct lysc_node_leaf *)schema)->type; + ret = type->plugin->duplicate(schema->module->ctx, val, &term->value); + if (ret) { + LOGERR(schema->module->ctx, ret, "Value duplication failed."); + free(term); + return ret; + } + lyd_hash(&term->node); + + *node = &term->node; + return ret; +} + +LY_ERR +lyd_create_inner(const struct lysc_node *schema, struct lyd_node **node) +{ + struct lyd_node_inner *in; + + assert(schema->nodetype & LYD_NODE_INNER); + + in = calloc(1, sizeof *in); + LY_CHECK_ERR_RET(!in, LOGMEM(schema->module->ctx), LY_EMEM); + + in->schema = schema; + in->prev = &in->node; + in->flags = LYD_NEW; + if ((schema->nodetype == LYS_CONTAINER) && !(schema->flags & LYS_PRESENCE)) { + in->flags |= LYD_DEFAULT; + } + + /* do not hash list with keys, we need them for the hash */ + if ((schema->nodetype != LYS_LIST) || (schema->flags & LYS_KEYLESS)) { + lyd_hash(&in->node); + } + + *node = &in->node; + return LY_SUCCESS; +} + +LY_ERR +lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, struct lyd_node **node) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *list = NULL, *key; + LY_ARRAY_COUNT_TYPE u; + + assert((schema->nodetype == LYS_LIST) && !(schema->flags & LYS_KEYLESS)); + + /* create list */ + LY_CHECK_GOTO(ret = lyd_create_inner(schema, &list), cleanup); + + LOG_LOCSET(schema, NULL, NULL, NULL); + + /* create and insert all the keys */ + LY_ARRAY_FOR(predicates, u) { + LY_CHECK_GOTO(ret = lyd_create_term2(predicates[u].key, &predicates[u].value, &key), cleanup); + lyd_insert_node(list, NULL, key, 0); + } + + /* hash having all the keys */ + lyd_hash(list); + + /* success */ + *node = list; + list = NULL; + +cleanup: + LOG_LOCBACK(1, 0, 0, 0); + lyd_free_tree(list); + return ret; +} + +LY_ERR +lyd_create_list2(const struct lysc_node *schema, const char *keys, size_t keys_len, struct lyd_node **node) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *expr = NULL; + uint32_t exp_idx = 0; + enum ly_path_pred_type pred_type = 0; + struct ly_path_predicate *predicates = NULL; + + LOG_LOCSET(schema, NULL, NULL, NULL); + + /* parse keys */ + LY_CHECK_GOTO(ret = ly_path_parse_predicate(schema->module->ctx, NULL, keys, keys_len, LY_PATH_PREFIX_OPTIONAL, + LY_PATH_PRED_KEYS, &expr), cleanup); + + /* compile them */ + LY_CHECK_GOTO(ret = ly_path_compile_predicate(schema->module->ctx, NULL, NULL, schema, expr, &exp_idx, LY_VALUE_JSON, + NULL, &predicates, &pred_type), cleanup); + + /* create the list node */ + LY_CHECK_GOTO(ret = lyd_create_list(schema, predicates, node), cleanup); + +cleanup: + LOG_LOCBACK(1, 0, 0, 0); + lyxp_expr_free(schema->module->ctx, expr); + ly_path_predicates_free(schema->module->ctx, pred_type, predicates); + return ret; +} + +/** + * @brief Convert an anydata value into a datatree. + * + * @param[in] ctx libyang context. + * @param[in] value Anydata value. + * @param[in] value_type Anydata @p value type. + * @param[out] tree Parsed data tree. + * @return LY_ERR value. + */ +static LY_ERR +lyd_create_anydata_datatree(const struct ly_ctx *ctx, const void *value, LYD_ANYDATA_VALUETYPE value_type, + struct lyd_node **tree) +{ + LY_ERR r; + struct ly_in *in = NULL; + struct lyd_ctx *lydctx = NULL; + uint32_t parse_opts, int_opts; + + *tree = NULL; + + if (!value) { + /* empty data tree no matter the value type */ + return LY_SUCCESS; + } + + if (value_type == LYD_ANYDATA_STRING) { + /* detect format */ + if (((char *)value)[0] == '<') { + value_type = LYD_ANYDATA_XML; + } else if (((char *)value)[0] == '{') { + value_type = LYD_ANYDATA_JSON; + } else if (!strncmp(value, "lyb", 3)) { + value_type = LYD_ANYDATA_LYB; + } else { + LOGERR(ctx, LY_EINVAL, "Invalid string value of an anydata node."); + return LY_EINVAL; + } + } + + /* create input */ + LY_CHECK_RET(ly_in_new_memory(value, &in)); + + /* set options */ + parse_opts = LYD_PARSE_ONLY | LYD_PARSE_OPAQ; + int_opts = LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; + + switch (value_type) { + case LYD_ANYDATA_DATATREE: + case LYD_ANYDATA_STRING: + /* unreachable */ + ly_in_free(in, 0); + LOGINT_RET(ctx); + case LYD_ANYDATA_XML: + r = lyd_parse_xml(ctx, NULL, NULL, tree, in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); + break; + case LYD_ANYDATA_JSON: + r = lyd_parse_json(ctx, NULL, NULL, tree, in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); + break; + case LYD_ANYDATA_LYB: + r = lyd_parse_lyb(ctx, NULL, NULL, tree, in, parse_opts | LYD_PARSE_STRICT, 0, int_opts, NULL, NULL, &lydctx); + break; + } + if (lydctx) { + lydctx->free(lydctx); + } + ly_in_free(in, 0); + + if (r) { + LOGERR(ctx, LY_EINVAL, "Failed to parse anydata content into a data tree."); + return LY_EINVAL; + } + return LY_SUCCESS; +} + +LY_ERR +lyd_create_any(const struct lysc_node *schema, const void *value, LYD_ANYDATA_VALUETYPE value_type, ly_bool use_value, + struct lyd_node **node) +{ + LY_ERR ret; + struct lyd_node *tree; + struct lyd_node_any *any = NULL; + union lyd_any_value any_val; + + assert(schema->nodetype & LYD_NODE_ANY); + + any = calloc(1, sizeof *any); + LY_CHECK_ERR_RET(!any, LOGMEM(schema->module->ctx), LY_EMEM); + + any->schema = schema; + any->prev = &any->node; + any->flags = LYD_NEW; + + if ((schema->nodetype == LYS_ANYDATA) && (value_type != LYD_ANYDATA_DATATREE)) { + /* only a data tree can be stored */ + LY_CHECK_GOTO(ret = lyd_create_anydata_datatree(schema->module->ctx, value, value_type, &tree), error); + if (use_value) { + free((void *)value); + } + use_value = 1; + value = tree; + value_type = LYD_ANYDATA_DATATREE; + } + + if (use_value) { + switch (value_type) { + case LYD_ANYDATA_DATATREE: + any->value.tree = (void *)value; + break; + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + LY_CHECK_GOTO(ret = lydict_insert_zc(schema->module->ctx, (void *)value, &any->value.str), error); + break; + case LYD_ANYDATA_LYB: + any->value.mem = (void *)value; + break; + } + any->value_type = value_type; + } else { + any_val.str = value; + LY_CHECK_GOTO(ret = lyd_any_copy_value(&any->node, &any_val, value_type), error); + } + lyd_hash(&any->node); + + *node = &any->node; + return LY_SUCCESS; + +error: + free(any); + return ret; +} + +LY_ERR +lyd_create_opaq(const struct ly_ctx *ctx, const char *name, size_t name_len, const char *prefix, size_t pref_len, + const char *module_key, size_t module_key_len, const char *value, size_t value_len, ly_bool *dynamic, + LY_VALUE_FORMAT format, void *val_prefix_data, uint32_t hints, struct lyd_node **node) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node_opaq *opaq; + + assert(ctx && name && name_len && format); + + if (!value_len && (!dynamic || !*dynamic)) { + value = ""; + } + + opaq = calloc(1, sizeof *opaq); + LY_CHECK_ERR_GOTO(!opaq, LOGMEM(ctx); ret = LY_EMEM, finish); + + opaq->prev = &opaq->node; + LY_CHECK_GOTO(ret = lydict_insert(ctx, name, name_len, &opaq->name.name), finish); + + if (pref_len) { + LY_CHECK_GOTO(ret = lydict_insert(ctx, prefix, pref_len, &opaq->name.prefix), finish); + } + if (module_key_len) { + LY_CHECK_GOTO(ret = lydict_insert(ctx, module_key, module_key_len, &opaq->name.module_ns), finish); + } + if (dynamic && *dynamic) { + LY_CHECK_GOTO(ret = lydict_insert_zc(ctx, (char *)value, &opaq->value), finish); + *dynamic = 0; + } else { + LY_CHECK_GOTO(ret = lydict_insert(ctx, value, value_len, &opaq->value), finish); + } + + opaq->format = format; + opaq->val_prefix_data = val_prefix_data; + opaq->hints = hints; + opaq->ctx = ctx; + +finish: + if (ret) { + lyd_free_tree(&opaq->node); + ly_free_prefix_data(format, val_prefix_data); + } else { + *node = &opaq->node; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_inner(struct lyd_node *parent, const struct lys_module *module, const char *name, ly_bool output, + struct lyd_node **node) +{ + LY_ERR r; + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct lysc_ext_instance *ext = NULL; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + if (!module) { + module = parent->schema->module; + } + + schema = lys_find_child(parent ? parent->schema : NULL, module, name, 0, + LYS_CONTAINER | LYS_NOTIF | LYS_RPC | LYS_ACTION, output ? LYS_GETNEXT_OUTPUT : 0); + if (!schema && parent) { + r = ly_nested_ext_schema(parent, NULL, module->name, strlen(module->name), LY_VALUE_JSON, NULL, name, + strlen(name), &schema, &ext); + LY_CHECK_RET(r && (r != LY_ENOT), r); + } + LY_CHECK_ERR_RET(!schema, LOGERR(ctx, LY_EINVAL, "Inner node (container, notif, RPC, or action) \"%s\" not found.", + name), LY_ENOTFOUND); + + LY_CHECK_RET(lyd_create_inner(schema, &ret)); + if (ext) { + ret->flags |= LYD_EXT; + } + if (parent) { + lyd_insert_node(parent, NULL, ret, 0); + } + + if (node) { + *node = ret; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_ext_inner(const struct lysc_ext_instance *ext, const char *name, struct lyd_node **node) +{ + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + + LY_CHECK_ARG_RET(ctx, ext, node, name, LY_EINVAL); + + schema = lysc_ext_find_node(ext, NULL, name, 0, LYS_CONTAINER | LYS_NOTIF | LYS_RPC | LYS_ACTION, 0); + if (!schema) { + if (ext->argument) { + LOGERR(ctx, LY_EINVAL, "Inner node (not a list) \"%s\" not found in instance \"%s\" of extension %s.", + name, ext->argument, ext->def->name); + } else { + LOGERR(ctx, LY_EINVAL, "Inner node (not a list) \"%s\" not found in instance of extension %s.", + name, ext->def->name); + } + return LY_ENOTFOUND; + } + LY_CHECK_RET(lyd_create_inner(schema, &ret)); + + *node = ret; + + return LY_SUCCESS; +} + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] format Format of key values. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @param[in] ap Ordered key values of the new list instance, all must be set. For ::LY_VALUE_LYB, every value must + * be followed by the value length. + * @return LY_ERR value. + */ +static LY_ERR +_lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const char *name, LY_VALUE_FORMAT format, + ly_bool output, struct lyd_node **node, va_list ap) +{ + struct lyd_node *ret = NULL, *key; + const struct lysc_node *schema, *key_s; + struct lysc_ext_instance *ext = NULL; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + const void *key_val; + uint32_t key_len; + LY_ERR r, rc = LY_SUCCESS; + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + if (!module) { + module = parent->schema->module; + } + + schema = lys_find_child(parent ? parent->schema : NULL, module, name, 0, LYS_LIST, output ? LYS_GETNEXT_OUTPUT : 0); + if (!schema && parent) { + r = ly_nested_ext_schema(parent, NULL, module->name, strlen(module->name), LY_VALUE_JSON, NULL, name, + strlen(name), &schema, &ext); + LY_CHECK_RET(r && (r != LY_ENOT), r); + } + LY_CHECK_ERR_RET(!schema, LOGERR(ctx, LY_EINVAL, "List node \"%s\" not found.", name), LY_ENOTFOUND); + + /* create list inner node */ + LY_CHECK_RET(lyd_create_inner(schema, &ret)); + + /* create and insert all the keys */ + for (key_s = lysc_node_child(schema); key_s && (key_s->flags & LYS_KEY); key_s = key_s->next) { + if (format == LY_VALUE_LYB) { + key_val = va_arg(ap, const void *); + key_len = va_arg(ap, uint32_t); + } else { + key_val = va_arg(ap, const char *); + key_len = key_val ? strlen((char *)key_val) : 0; + } + + rc = lyd_create_term(key_s, key_val, key_len, NULL, format, NULL, LYD_HINT_DATA, NULL, &key); + LY_CHECK_GOTO(rc, cleanup); + lyd_insert_node(ret, NULL, key, 1); + } + + if (ext) { + ret->flags |= LYD_EXT; + } + if (parent) { + lyd_insert_node(parent, NULL, ret, 0); + } + +cleanup: + if (rc) { + lyd_free_tree(ret); + ret = NULL; + } else if (node) { + *node = ret; + } + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const char *name, ly_bool output, + struct lyd_node **node, ...) +{ + LY_ERR rc; + va_list ap; + + va_start(ap, node); + + rc = _lyd_new_list(parent, module, name, LY_VALUE_JSON, output, node, ap); + + va_end(ap); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list_bin(struct lyd_node *parent, const struct lys_module *module, const char *name, ly_bool output, + struct lyd_node **node, ...) +{ + LY_ERR rc; + va_list ap; + + va_start(ap, node); + + rc = _lyd_new_list(parent, module, name, LY_VALUE_LYB, output, node, ap); + + va_end(ap); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list_canon(struct lyd_node *parent, const struct lys_module *module, const char *name, ly_bool output, + struct lyd_node **node, ...) +{ + LY_ERR rc; + va_list ap; + + va_start(ap, node); + + rc = _lyd_new_list(parent, module, name, LY_VALUE_CANON, output, node, ap); + + va_end(ap); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_ext_list(const struct lysc_ext_instance *ext, const char *name, struct lyd_node **node, ...) +{ + struct lyd_node *ret = NULL, *key; + const struct lysc_node *schema, *key_s; + struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + va_list ap; + const char *key_val; + LY_ERR rc = LY_SUCCESS; + + LY_CHECK_ARG_RET(ctx, ext, node, name, LY_EINVAL); + + schema = lysc_ext_find_node(ext, NULL, name, 0, LYS_LIST, 0); + if (!schema) { + if (ext->argument) { + LOGERR(ctx, LY_EINVAL, "List node \"%s\" not found in instance \"%s\" of extension %s.", + name, ext->argument, ext->def->name); + } else { + LOGERR(ctx, LY_EINVAL, "List node \"%s\" not found in instance of extension %s.", name, ext->def->name); + } + return LY_ENOTFOUND; + } + /* create list inner node */ + LY_CHECK_RET(lyd_create_inner(schema, &ret)); + + va_start(ap, node); + + /* create and insert all the keys */ + for (key_s = lysc_node_child(schema); key_s && (key_s->flags & LYS_KEY); key_s = key_s->next) { + key_val = va_arg(ap, const char *); + + rc = lyd_create_term(key_s, key_val, key_val ? strlen(key_val) : 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, + NULL, &key); + LY_CHECK_GOTO(rc, cleanup); + lyd_insert_node(ret, NULL, key, 1); + } + +cleanup: + va_end(ap); + if (rc) { + lyd_free_tree(ret); + ret = NULL; + } + *node = ret; + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list2(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *keys, + ly_bool output, struct lyd_node **node) +{ + LY_ERR r; + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct lysc_ext_instance *ext = NULL; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + if (!module) { + module = parent->schema->module; + } + if (!keys) { + keys = ""; + } + + /* find schema node */ + schema = lys_find_child(parent ? parent->schema : NULL, module, name, 0, LYS_LIST, output ? LYS_GETNEXT_OUTPUT : 0); + if (!schema && parent) { + r = ly_nested_ext_schema(parent, NULL, module->name, strlen(module->name), LY_VALUE_JSON, NULL, name, strlen(name), + &schema, &ext); + LY_CHECK_RET(r && (r != LY_ENOT), r); + } + LY_CHECK_ERR_RET(!schema, LOGERR(ctx, LY_EINVAL, "List node \"%s\" not found.", name), LY_ENOTFOUND); + + if ((schema->flags & LYS_KEYLESS) && !keys[0]) { + /* key-less list */ + LY_CHECK_RET(lyd_create_inner(schema, &ret)); + } else { + /* create the list node */ + LY_CHECK_RET(lyd_create_list2(schema, keys, strlen(keys), &ret)); + } + if (ext) { + ret->flags |= LYD_EXT; + } + if (parent) { + lyd_insert_node(parent, NULL, ret, 0); + } + + if (node) { + *node = ret; + } + return LY_SUCCESS; +} + +/** + * @brief Create a new term node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node can be ::LYS_LEAF or ::LYS_LEAFLIST. + * @param[in] value Value of the node being created. + * @param[in] value_len Length of @p value. + * @param[in] format Format of @p value. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +static LY_ERR +_lyd_new_term(struct lyd_node *parent, const struct lys_module *module, const char *name, const void *value, + size_t value_len, LY_VALUE_FORMAT format, ly_bool output, struct lyd_node **node) +{ + LY_ERR r; + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct lysc_ext_instance *ext = NULL; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + if (!module) { + module = parent->schema->module; + } + + schema = lys_find_child(parent ? parent->schema : NULL, module, name, 0, LYD_NODE_TERM, output ? LYS_GETNEXT_OUTPUT : 0); + if (!schema && parent) { + r = ly_nested_ext_schema(parent, NULL, module->name, strlen(module->name), LY_VALUE_JSON, NULL, name, + strlen(name), &schema, &ext); + LY_CHECK_RET(r && (r != LY_ENOT), r); + } + LY_CHECK_ERR_RET(!schema, LOGERR(ctx, LY_EINVAL, "Term node \"%s\" not found.", name), LY_ENOTFOUND); + + LY_CHECK_RET(lyd_create_term(schema, value, value_len, NULL, format, NULL, LYD_HINT_DATA, NULL, &ret)); + if (ext) { + ret->flags |= LYD_EXT; + } + if (parent) { + lyd_insert_node(parent, NULL, ret, 0); + } + + if (node) { + *node = ret; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_term(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *val_str, + ly_bool output, struct lyd_node **node) +{ + return _lyd_new_term(parent, module, name, val_str, val_str ? strlen(val_str) : 0, LY_VALUE_JSON, output, node); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_term_bin(struct lyd_node *parent, const struct lys_module *module, const char *name, const void *value, + size_t value_len, ly_bool output, struct lyd_node **node) +{ + return _lyd_new_term(parent, module, name, value, value_len, LY_VALUE_LYB, output, node); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_term_canon(struct lyd_node *parent, const struct lys_module *module, const char *name, const char *val_str, + ly_bool output, struct lyd_node **node) +{ + return _lyd_new_term(parent, module, name, val_str, val_str ? strlen(val_str) : 0, LY_VALUE_CANON, output, node); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_ext_term(const struct lysc_ext_instance *ext, const char *name, const char *val_str, struct lyd_node **node) +{ + LY_ERR rc; + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + + LY_CHECK_ARG_RET(ctx, ext, node, name, LY_EINVAL); + + schema = lysc_ext_find_node(ext, NULL, name, 0, LYD_NODE_TERM, 0); + if (!schema) { + if (ext->argument) { + LOGERR(ctx, LY_EINVAL, "Term node \"%s\" not found in instance \"%s\" of extension %s.", + name, ext->argument, ext->def->name); + } else { + LOGERR(ctx, LY_EINVAL, "Term node \"%s\" not found in instance of extension %s.", name, ext->def->name); + } + return LY_ENOTFOUND; + } + rc = lyd_create_term(schema, val_str, val_str ? strlen(val_str) : 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, &ret); + LY_CHECK_RET(rc); + + *node = ret; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_any(struct lyd_node *parent, const struct lys_module *module, const char *name, const void *value, + ly_bool use_value, LYD_ANYDATA_VALUETYPE value_type, ly_bool output, struct lyd_node **node) +{ + LY_ERR r; + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct lysc_ext_instance *ext = NULL; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + if (!module) { + module = parent->schema->module; + } + + schema = lys_find_child(parent ? parent->schema : NULL, module, name, 0, LYD_NODE_ANY, output ? LYS_GETNEXT_OUTPUT : 0); + if (!schema && parent) { + r = ly_nested_ext_schema(parent, NULL, module->name, strlen(module->name), LY_VALUE_JSON, NULL, name, + strlen(name), &schema, &ext); + LY_CHECK_RET(r && (r != LY_ENOT), r); + } + LY_CHECK_ERR_RET(!schema, LOGERR(ctx, LY_EINVAL, "Any node \"%s\" not found.", name), LY_ENOTFOUND); + + LY_CHECK_RET(lyd_create_any(schema, value, value_type, use_value, &ret)); + if (ext) { + ret->flags |= LYD_EXT; + } + if (parent) { + lyd_insert_node(parent, NULL, ret, 0); + } + + if (node) { + *node = ret; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_ext_any(const struct lysc_ext_instance *ext, const char *name, const void *value, ly_bool use_value, + LYD_ANYDATA_VALUETYPE value_type, struct lyd_node **node) +{ + struct lyd_node *ret = NULL; + const struct lysc_node *schema; + struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + + LY_CHECK_ARG_RET(ctx, ext, node, name, LY_EINVAL); + + schema = lysc_ext_find_node(ext, NULL, name, 0, LYD_NODE_ANY, 0); + if (!schema) { + if (ext->argument) { + LOGERR(ctx, LY_EINVAL, "Any node \"%s\" not found in instance \"%s\" of extension %s.", + name, ext->argument, ext->def->name); + } else { + LOGERR(ctx, LY_EINVAL, "Any node \"%s\" not found in instance of extension %s.", name, ext->def->name); + } + return LY_ENOTFOUND; + } + LY_CHECK_RET(lyd_create_any(schema, value, value_type, use_value, &ret)); + + *node = ret; + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_meta(const struct ly_ctx *ctx, struct lyd_node *parent, const struct lys_module *module, const char *name, + const char *val_str, ly_bool clear_dflt, struct lyd_meta **meta) +{ + const char *prefix, *tmp; + size_t pref_len, name_len; + + LY_CHECK_ARG_RET(ctx, ctx || parent, name, module || strchr(name, ':'), parent || meta, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + if (!ctx) { + ctx = module ? module->ctx : LYD_CTX(parent); + } + + if (parent && !parent->schema) { + LOGERR(ctx, LY_EINVAL, "Cannot add metadata \"%s\" to an opaque node \"%s\".", name, LYD_NAME(parent)); + return LY_EINVAL; + } + if (meta) { + *meta = NULL; + } + + /* parse the name */ + tmp = name; + if (ly_parse_nodeid(&tmp, &prefix, &pref_len, &name, &name_len) || tmp[0]) { + LOGERR(ctx, LY_EINVAL, "Metadata name \"%s\" is not valid.", name); + return LY_EINVAL; + } + + /* find the module */ + if (prefix) { + module = ly_ctx_get_module_implemented2(ctx, prefix, pref_len); + LY_CHECK_ERR_RET(!module, LOGERR(ctx, LY_EINVAL, "Module \"%.*s\" not found.", (int)pref_len, prefix), LY_ENOTFOUND); + } + + /* set value if none */ + if (!val_str) { + val_str = ""; + } + + return lyd_create_meta(parent, meta, module, name, name_len, val_str, strlen(val_str), NULL, LY_VALUE_JSON, + NULL, LYD_HINT_DATA, parent ? parent->schema : NULL, clear_dflt, NULL); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_meta2(const struct ly_ctx *ctx, struct lyd_node *parent, ly_bool clear_dflt, const struct lyd_attr *attr, + struct lyd_meta **meta) +{ + const struct lys_module *mod; + + LY_CHECK_ARG_RET(NULL, ctx, attr, parent || meta, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, parent ? LYD_CTX(parent) : NULL, LY_EINVAL); + + if (parent && !parent->schema) { + LOGERR(ctx, LY_EINVAL, "Cannot add metadata to an opaque node \"%s\".", ((struct lyd_node_opaq *)parent)->name); + return LY_EINVAL; + } + if (meta) { + *meta = NULL; + } + + switch (attr->format) { + case LY_VALUE_XML: + mod = ly_ctx_get_module_implemented_ns(ctx, attr->name.module_ns); + if (!mod) { + LOGERR(ctx, LY_EINVAL, "Module with namespace \"%s\" not found.", attr->name.module_ns); + return LY_ENOTFOUND; + } + break; + case LY_VALUE_JSON: + mod = ly_ctx_get_module_implemented(ctx, attr->name.module_name); + if (!mod) { + LOGERR(ctx, LY_EINVAL, "Module \"%s\" not found.", attr->name.module_name); + return LY_ENOTFOUND; + } + break; + default: + LOGINT_RET(ctx); + } + + return lyd_create_meta(parent, meta, mod, attr->name.name, strlen(attr->name.name), attr->value, strlen(attr->value), + NULL, attr->format, attr->val_prefix_data, attr->hints, parent ? parent->schema : NULL, clear_dflt, NULL); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_opaq(struct lyd_node *parent, const struct ly_ctx *ctx, const char *name, const char *value, + const char *prefix, const char *module_name, struct lyd_node **node) +{ + struct lyd_node *ret = NULL; + + LY_CHECK_ARG_RET(ctx, parent || ctx, parent || node, name, module_name, !prefix || !strcmp(prefix, module_name), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, parent ? LYD_CTX(parent) : NULL, LY_EINVAL); + + if (!ctx) { + ctx = LYD_CTX(parent); + } + if (!value) { + value = ""; + } + + LY_CHECK_RET(lyd_create_opaq(ctx, name, strlen(name), prefix, prefix ? strlen(prefix) : 0, module_name, + strlen(module_name), value, strlen(value), NULL, LY_VALUE_JSON, NULL, 0, &ret)); + if (parent) { + lyd_insert_node(parent, NULL, ret, 1); + } + + if (node) { + *node = ret; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_opaq2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *name, const char *value, + const char *prefix, const char *module_ns, struct lyd_node **node) +{ + struct lyd_node *ret = NULL; + + LY_CHECK_ARG_RET(ctx, parent || ctx, parent || node, name, module_ns, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, parent ? LYD_CTX(parent) : NULL, LY_EINVAL); + + if (!ctx) { + ctx = LYD_CTX(parent); + } + if (!value) { + value = ""; + } + + LY_CHECK_RET(lyd_create_opaq(ctx, name, strlen(name), prefix, prefix ? strlen(prefix) : 0, module_ns, + strlen(module_ns), value, strlen(value), NULL, LY_VALUE_XML, NULL, 0, &ret)); + if (parent) { + lyd_insert_node(parent, NULL, ret, 1); + } + + if (node) { + *node = ret; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_attr(struct lyd_node *parent, const char *module_name, const char *name, const char *value, + struct lyd_attr **attr) +{ + struct lyd_attr *ret = NULL; + const struct ly_ctx *ctx; + const char *prefix, *tmp; + size_t pref_len, name_len, mod_len; + + LY_CHECK_ARG_RET(NULL, parent, !parent->schema, name, LY_EINVAL); + + ctx = LYD_CTX(parent); + + /* parse the name */ + tmp = name; + if (ly_parse_nodeid(&tmp, &prefix, &pref_len, &name, &name_len) || tmp[0]) { + LOGERR(ctx, LY_EINVAL, "Attribute name \"%s\" is not valid.", name); + return LY_EVALID; + } + + if ((pref_len == 3) && !strncmp(prefix, "xml", 3)) { + /* not a prefix but special name */ + name = prefix; + name_len += 1 + pref_len; + prefix = NULL; + pref_len = 0; + } + + /* get the module */ + if (module_name) { + mod_len = strlen(module_name); + } else { + module_name = prefix; + mod_len = pref_len; + } + + /* set value if none */ + if (!value) { + value = ""; + } + + LY_CHECK_RET(lyd_create_attr(parent, &ret, ctx, name, name_len, prefix, pref_len, module_name, mod_len, value, + strlen(value), NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA)); + + if (attr) { + *attr = ret; + } + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_attr2(struct lyd_node *parent, const char *module_ns, const char *name, const char *value, + struct lyd_attr **attr) +{ + struct lyd_attr *ret = NULL; + const struct ly_ctx *ctx; + const char *prefix, *tmp; + size_t pref_len, name_len; + + LY_CHECK_ARG_RET(NULL, parent, !parent->schema, name, LY_EINVAL); + + ctx = LYD_CTX(parent); + + /* parse the name */ + tmp = name; + if (ly_parse_nodeid(&tmp, &prefix, &pref_len, &name, &name_len) || tmp[0]) { + LOGERR(ctx, LY_EINVAL, "Attribute name \"%s\" is not valid.", name); + return LY_EVALID; + } + + if ((pref_len == 3) && !strncmp(prefix, "xml", 3)) { + /* not a prefix but special name */ + name = prefix; + name_len += 1 + pref_len; + prefix = NULL; + pref_len = 0; + } + + /* set value if none */ + if (!value) { + value = ""; + } + if (strchr(value, ':')) { + LOGWRN(ctx, "Value \"%s\" prefix will never be interpreted as an XML prefix.", value); + } + + LY_CHECK_RET(lyd_create_attr(parent, &ret, ctx, name, name_len, prefix, pref_len, module_ns, + module_ns ? strlen(module_ns) : 0, value, strlen(value), NULL, LY_VALUE_XML, NULL, LYD_HINT_DATA)); + + if (attr) { + *attr = ret; + } + return LY_SUCCESS; +} + +/** + * @brief Change the value of a term (leaf or leaf-list) node. + * + * Node changed this way is always considered explicitly set, meaning its default flag + * is always cleared. + * + * @param[in] term Term node to change. + * @param[in] value New value to set. + * @param[in] value_len Length of @p value. + * @param[in] format Format of @p value. + * @return LY_SUCCESS if value was changed, + * @return LY_EEXIST if value was the same and only the default flag was cleared, + * @return LY_ENOT if the values were equal and no change occured, + * @return LY_ERR value on other errors. + */ +static LY_ERR +_lyd_change_term(struct lyd_node *term, const void *value, size_t value_len, LY_VALUE_FORMAT format) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type *type; + struct lyd_node_term *t; + struct lyd_node *parent; + struct lyd_value val; + ly_bool dflt_change, val_change; + + assert(term && term->schema && (term->schema->nodetype & LYD_NODE_TERM)); + + t = (struct lyd_node_term *)term; + type = ((struct lysc_node_leaf *)term->schema)->type; + + /* parse the new value */ + LOG_LOCSET(term->schema, term, NULL, NULL); + ret = lyd_value_store(LYD_CTX(term), &val, type, value, value_len, NULL, format, NULL, LYD_HINT_DATA, term->schema, NULL); + LOG_LOCBACK(term->schema ? 1 : 0, 1, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + /* compare original and new value */ + if (type->plugin->compare(&t->value, &val)) { + /* values differ, switch them */ + type->plugin->free(LYD_CTX(term), &t->value); + t->value = val; + val_change = 1; + } else { + /* same values, free the new stored one */ + type->plugin->free(LYD_CTX(term), &val); + val_change = 0; + } + + /* always clear the default flag */ + if (term->flags & LYD_DEFAULT) { + for (parent = term; parent; parent = lyd_parent(parent)) { + parent->flags &= ~LYD_DEFAULT; + } + dflt_change = 1; + } else { + dflt_change = 0; + } + + if (val_change || dflt_change) { + /* make the node non-validated */ + term->flags &= LYD_NEW; + } + + if (val_change) { + if (term->schema->nodetype == LYS_LEAFLIST) { + /* leaf-list needs to be hashed again and re-inserted into parent */ + lyd_unlink_hash(term); + lyd_hash(term); + LY_CHECK_GOTO(ret = lyd_insert_hash(term), cleanup); + } else if ((term->schema->flags & LYS_KEY) && term->parent) { + /* list needs to be updated if its key was changed */ + assert(term->parent->schema->nodetype == LYS_LIST); + lyd_unlink_hash(lyd_parent(term)); + lyd_hash(lyd_parent(term)); + LY_CHECK_GOTO(ret = lyd_insert_hash(lyd_parent(term)), cleanup); + } /* else leaf that is not a key, its value is not used for its hash so it does not change */ + } + + /* retrun value */ + if (!val_change) { + if (dflt_change) { + /* only default flag change */ + ret = LY_EEXIST; + } else { + /* no change */ + ret = LY_ENOT; + } + } /* else value changed, LY_SUCCESS */ + +cleanup: + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_change_term(struct lyd_node *term, const char *val_str) +{ + LY_CHECK_ARG_RET(NULL, term, term->schema, term->schema->nodetype & LYD_NODE_TERM, LY_EINVAL); + + return _lyd_change_term(term, val_str, val_str ? strlen(val_str) : 0, LY_VALUE_JSON); +} + +LIBYANG_API_DEF LY_ERR +lyd_change_term_bin(struct lyd_node *term, const void *value, size_t value_len) +{ + LY_CHECK_ARG_RET(NULL, term, term->schema, term->schema->nodetype & LYD_NODE_TERM, LY_EINVAL); + + return _lyd_change_term(term, value, value_len, LY_VALUE_LYB); +} + +LIBYANG_API_DEF LY_ERR +lyd_change_term_canon(struct lyd_node *term, const char *val_str) +{ + LY_CHECK_ARG_RET(NULL, term, term->schema, term->schema->nodetype & LYD_NODE_TERM, LY_EINVAL); + + return _lyd_change_term(term, val_str, val_str ? strlen(val_str) : 0, LY_VALUE_CANON); +} + +LIBYANG_API_DEF LY_ERR +lyd_change_meta(struct lyd_meta *meta, const char *val_str) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_meta *m2 = NULL; + struct lyd_value val; + ly_bool val_change; + + LY_CHECK_ARG_RET(NULL, meta, LY_EINVAL); + + if (!val_str) { + val_str = ""; + } + + /* parse the new value into a new meta structure */ + ret = lyd_create_meta(NULL, &m2, meta->annotation->module, meta->name, strlen(meta->name), val_str, strlen(val_str), + NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, meta->parent ? meta->parent->schema : NULL, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* compare original and new value */ + if (lyd_compare_meta(meta, m2)) { + /* values differ, switch them */ + val = meta->value; + meta->value = m2->value; + m2->value = val; + val_change = 1; + } else { + val_change = 0; + } + + /* retrun value */ + if (!val_change) { + /* no change */ + ret = LY_ENOT; + } /* else value changed, LY_SUCCESS */ + +cleanup: + lyd_free_meta_single(m2); + return ret; +} + +/** + * @brief Update node value. + * + * @param[in] node Node to update. + * @param[in] value New value to set. + * @param[in] value_len Length of @p value. + * @param[in] value_type Type of @p value for anydata/anyxml node. + * @param[in] format Format of @p value. + * @param[out] new_parent Set to @p node if the value was updated, otherwise set to NULL. + * @param[out] new_node Set to @p node if the value was updated, otherwise set to NULL. + * @return LY_ERR value. + */ +static LY_ERR +lyd_new_path_update(struct lyd_node *node, const void *value, size_t value_len, LYD_ANYDATA_VALUETYPE value_type, + LY_VALUE_FORMAT format, struct lyd_node **new_parent, struct lyd_node **new_node) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *new_any; + + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_NOTIF: + case LYS_RPC: + case LYS_ACTION: + case LYS_LIST: + /* if it exists, there is nothing to update */ + *new_parent = NULL; + *new_node = NULL; + break; + case LYS_LEAFLIST: + if (!lysc_is_dup_inst_list(node->schema)) { + /* if it exists, there is nothing to update */ + *new_parent = NULL; + *new_node = NULL; + break; + } + /* fallthrough */ + case LYS_LEAF: + ret = _lyd_change_term(node, value, value_len, format); + if ((ret == LY_SUCCESS) || (ret == LY_EEXIST)) { + /* there was an actual change (at least of the default flag) */ + *new_parent = node; + *new_node = node; + ret = LY_SUCCESS; + } else if (ret == LY_ENOT) { + /* no change */ + *new_parent = NULL; + *new_node = NULL; + ret = LY_SUCCESS; + } /* else error */ + break; + case LYS_ANYDATA: + case LYS_ANYXML: + /* create a new any node */ + LY_CHECK_RET(lyd_create_any(node->schema, value, value_type, 0, &new_any)); + + /* compare with the existing one */ + if (lyd_compare_single(node, new_any, 0)) { + /* not equal, switch values (so that we can use generic node free) */ + ((struct lyd_node_any *)new_any)->value = ((struct lyd_node_any *)node)->value; + ((struct lyd_node_any *)new_any)->value_type = ((struct lyd_node_any *)node)->value_type; + ((struct lyd_node_any *)node)->value.str = value; + ((struct lyd_node_any *)node)->value_type = value_type; + + *new_parent = node; + *new_node = node; + } else { + /* they are equal */ + *new_parent = NULL; + *new_node = NULL; + } + lyd_free_tree(new_any); + break; + default: + LOGINT(LYD_CTX(node)); + ret = LY_EINT; + break; + } + + return ret; +} + +static LY_ERR +lyd_new_path_check_find_lypath(struct ly_path *path, const char *str_path, const char *value, size_t value_len, + LY_VALUE_FORMAT format, uint32_t options) +{ + LY_ERR r; + struct ly_path_predicate *pred; + struct lyd_value val; + const struct lysc_node *schema = NULL; + LY_ARRAY_COUNT_TYPE u, new_count; + int create = 0; + + assert(path); + + /* go through all the compiled nodes */ + LY_ARRAY_FOR(path, u) { + schema = path[u].node; + + if (lysc_is_dup_inst_list(schema)) { + if (path[u].pred_type == LY_PATH_PREDTYPE_NONE) { + /* creating a new key-less list or state leaf-list instance */ + create = 1; + new_count = u; + } else if (path[u].pred_type != LY_PATH_PREDTYPE_POSITION) { + LOG_LOCSET(schema, NULL, NULL, NULL); + LOGVAL(schema->module->ctx, LYVE_XPATH, "Invalid predicate for %s \"%s\" in path \"%s\".", + lys_nodetype2str(schema->nodetype), schema->name, str_path); + LOG_LOCBACK(1, 0, 0, 0); + return LY_EINVAL; + } + } else if ((schema->nodetype == LYS_LIST) && (path[u].pred_type != LY_PATH_PREDTYPE_LIST)) { + if ((u < LY_ARRAY_COUNT(path) - 1) || !(options & LYD_NEW_PATH_OPAQ)) { + LOG_LOCSET(schema, NULL, NULL, NULL); + LOGVAL(schema->module->ctx, LYVE_XPATH, "Predicate missing for %s \"%s\" in path \"%s\".", + lys_nodetype2str(schema->nodetype), schema->name, str_path); + LOG_LOCBACK(1, 0, 0, 0); + return LY_EINVAL; + } /* else creating an opaque list */ + } else if ((schema->nodetype == LYS_LEAFLIST) && (path[u].pred_type != LY_PATH_PREDTYPE_LEAFLIST)) { + r = LY_SUCCESS; + if (options & LYD_NEW_PATH_OPAQ) { + r = lyd_value_validate(NULL, schema, value, value_len, NULL, NULL, NULL); + } + if (!r) { + /* try to store the value */ + LY_CHECK_RET(lyd_value_store(schema->module->ctx, &val, ((struct lysc_node_leaflist *)schema)->type, + value, value_len, NULL, format, NULL, LYD_HINT_DATA, schema, NULL)); + ++((struct lysc_type *)val.realtype)->refcount; + + /* store the new predicate so that it is used when searching for this instance */ + path[u].pred_type = LY_PATH_PREDTYPE_LEAFLIST; + LY_ARRAY_NEW_RET(schema->module->ctx, path[u].predicates, pred, LY_EMEM); + pred->value = val; + } /* else we have opaq flag and the value is not valid, leave no predicate and then create an opaque node */ + } + } + + if (create) { + /* hide the nodes that should always be created so they are not found */ + while (new_count < LY_ARRAY_COUNT(path)) { + LY_ARRAY_DECREMENT(path); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Create a new node in the data tree based on a path. All node types can be created. + * + * If @p path points to a list key, the key value from the predicate is used and @p value is ignored. + * Also, if a leaf-list is being created and both a predicate is defined in @p path + * and @p value is set, the predicate is preferred. + * + * For key-less lists and state leaf-lists, positional predicates can be used. If no preciate is used for these + * nodes, they are always created. + * + * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, + * it may no longer be first if @p path is absolute and starts with a non-existing top-level node inserted + * before @p parent. Use ::lyd_first_sibling() to adjust @p parent in these cases. + * @param[in] ctx libyang context, must be set if @p parent is NULL. + * @param[in] ext Extension instance where the node being created is defined. This argument takes effect only for absolute + * path or when the relative paths touches document root (top-level). In such cases the present extension instance replaces + * searching for the appropriate module. + * @param[in] path [Path](@ref howtoXPath) to create. + * @param[in] value Value of the new leaf/leaf-list (const char *) in ::LY_VALUE_JSON format. If creating an + * anyxml/anydata node, the expected type depends on @p value_type. For other node types, it should be NULL. + * @param[in] value_len Length of @p value in bytes. May be 0 if @p value is a zero-terminated string. Ignored when + * creating anyxml/anydata nodes. + * @param[in] value_type Anyxml/anydata node @p value type. + * @param[in] options Bitmask of options, see @ref pathoptions. + * @param[out] new_parent Optional first parent node created. If only one node was created, equals to @p new_node. + * @param[out] new_node Optional last node created. + * @return LY_ERR value. + */ +static LY_ERR +lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, const char *path, + const void *value, size_t value_len, LYD_ANYDATA_VALUETYPE value_type, uint32_t options, + struct lyd_node **new_parent, struct lyd_node **new_node) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lyxp_expr *exp = NULL; + struct ly_path *p = NULL; + struct lyd_node *nparent = NULL, *nnode = NULL, *node = NULL, *cur_parent; + const struct lysc_node *schema; + const struct lyd_value *val = NULL; + LY_ARRAY_COUNT_TYPE path_idx = 0, orig_count = 0; + LY_VALUE_FORMAT format; + + assert(parent || ctx); + assert(path && ((path[0] == '/') || parent)); + assert(!(options & LYD_NEW_PATH_BIN_VALUE) || !(options & LYD_NEW_PATH_CANON_VALUE)); + + if (!ctx) { + ctx = LYD_CTX(parent); + } + if (value && !value_len) { + value_len = strlen(value); + } + if (options & LYD_NEW_PATH_BIN_VALUE) { + format = LY_VALUE_LYB; + } else if (options & LYD_NEW_PATH_CANON_VALUE) { + format = LY_VALUE_CANON; + } else { + format = LY_VALUE_JSON; + } + + /* parse path */ + LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, + LY_PATH_PRED_SIMPLE, &exp), cleanup); + + /* compile path */ + LY_CHECK_GOTO(ret = ly_path_compile(ctx, NULL, lyd_node_schema(parent), ext, exp, options & LYD_NEW_PATH_OUTPUT ? + LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p), cleanup); + + /* check the compiled path before searching existing nodes, it may be shortened */ + orig_count = LY_ARRAY_COUNT(p); + LY_CHECK_GOTO(ret = lyd_new_path_check_find_lypath(p, path, value, value_len, format, options), cleanup); + + /* try to find any existing nodes in the path */ + if (parent) { + ret = ly_path_eval_partial(p, parent, &path_idx, &node); + if (ret == LY_SUCCESS) { + if (orig_count == LY_ARRAY_COUNT(p)) { + /* the node exists, are we supposed to update it or is it just a default? */ + if (!(options & LYD_NEW_PATH_UPDATE) && !(node->flags & LYD_DEFAULT)) { + LOG_LOCSET(NULL, node, NULL, NULL); + LOGVAL(ctx, LYVE_REFERENCE, "Path \"%s\" already exists", path); + LOG_LOCBACK(0, 1, 0, 0); + ret = LY_EEXIST; + goto cleanup; + } + + /* update the existing node */ + ret = lyd_new_path_update(node, value, value_len, value_type, format, &nparent, &nnode); + goto cleanup; + } /* else we were not searching for the whole path */ + } else if (ret == LY_EINCOMPLETE) { + /* some nodes were found, adjust the iterator to the next segment */ + ++path_idx; + } else if (ret == LY_ENOTFOUND) { + /* we will create the nodes from top-level, default behavior (absolute path), or from the parent (relative path) */ + if (lysc_data_parent(p[0].node)) { + node = parent; + } + } else { + /* error */ + goto cleanup; + } + } + + /* restore the full path for creating new nodes */ + while (orig_count > LY_ARRAY_COUNT(p)) { + LY_ARRAY_INCREMENT(p); + } + + /* create all the non-existing nodes in a loop */ + for ( ; path_idx < LY_ARRAY_COUNT(p); ++path_idx) { + cur_parent = node; + schema = p[path_idx].node; + + switch (schema->nodetype) { + case LYS_LIST: + if (lysc_is_dup_inst_list(schema)) { + /* create key-less list instance */ + LY_CHECK_GOTO(ret = lyd_create_inner(schema, &node), cleanup); + } else if ((options & LYD_NEW_PATH_OPAQ) && (p[path_idx].pred_type == LY_PATH_PREDTYPE_NONE)) { + /* creating opaque list without keys */ + LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), NULL, 0, + schema->module->name, strlen(schema->module->name), NULL, 0, NULL, LY_VALUE_JSON, NULL, + LYD_NODEHINT_LIST, &node), cleanup); + } else { + /* create standard list instance */ + assert(p[path_idx].pred_type == LY_PATH_PREDTYPE_LIST); + LY_CHECK_GOTO(ret = lyd_create_list(schema, p[path_idx].predicates, &node), cleanup); + } + break; + case LYS_CONTAINER: + case LYS_NOTIF: + case LYS_RPC: + case LYS_ACTION: + LY_CHECK_GOTO(ret = lyd_create_inner(schema, &node), cleanup); + break; + case LYS_LEAFLIST: + if ((options & LYD_NEW_PATH_OPAQ) && (p[path_idx].pred_type != LY_PATH_PREDTYPE_LEAFLIST)) { + /* we have not checked this only for dup-inst lists, otherwise it must be opaque */ + r = LY_EVALID; + if (lysc_is_dup_inst_list(schema)) { + /* validate value */ + r = lyd_value_validate(NULL, schema, value ? value : "", value_len, NULL, NULL, NULL); + } + if (r && (r != LY_EINCOMPLETE)) { + /* creating opaque leaf-list */ + LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), value, value_len, + schema->module->name, strlen(schema->module->name), NULL, 0, NULL, format, NULL, + LYD_NODEHINT_LEAFLIST, &node), cleanup); + break; + } + } + + /* get value to set */ + if (p[path_idx].pred_type == LY_PATH_PREDTYPE_LEAFLIST) { + val = &p[path_idx].predicates[0].value; + } + + /* create a leaf-list instance */ + if (val) { + LY_CHECK_GOTO(ret = lyd_create_term2(schema, &p[path_idx].predicates[0].value, &node), cleanup); + } else { + LY_CHECK_GOTO(ret = lyd_create_term(schema, value, value_len, NULL, format, NULL, LYD_HINT_DATA, + NULL, &node), cleanup); + } + break; + case LYS_LEAF: + if (lysc_is_key(schema) && cur_parent->schema) { + /* it must have been already created or some error will occur later */ + lyd_find_sibling_schema(lyd_child(cur_parent), schema, &node); + assert(node); + goto next_iter; + } + + if (options & LYD_NEW_PATH_OPAQ) { + if (cur_parent && !cur_parent->schema) { + /* always create opaque nodes for opaque parents */ + r = LY_ENOT; + } else { + /* validate value */ + r = lyd_value_validate(NULL, schema, value ? value : "", value_len, NULL, NULL, NULL); + } + if (r && (r != LY_EINCOMPLETE)) { + /* creating opaque leaf */ + LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), value, value_len, + schema->module->name, strlen(schema->module->name), NULL, 0, NULL, format, NULL, 0, &node), + cleanup); + break; + } + } + + /* create a leaf instance */ + LY_CHECK_GOTO(ret = lyd_create_term(schema, value, value_len, NULL, format, NULL, LYD_HINT_DATA, NULL, + &node), cleanup); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + LY_CHECK_GOTO(ret = lyd_create_any(schema, value, value_type, 0, &node), cleanup); + break; + default: + LOGINT(ctx); + ret = LY_EINT; + goto cleanup; + } + + if (p[path_idx].ext) { + node->flags |= LYD_EXT; + } + if (cur_parent) { + /* connect to the parent */ + lyd_insert_node(cur_parent, NULL, node, 0); + } else if (parent) { + /* connect to top-level siblings */ + lyd_insert_node(NULL, &parent, node, 0); + } + +next_iter: + /* update remembered nodes */ + if (!nparent) { + nparent = node; + } + nnode = node; + } + +cleanup: + lyxp_expr_free(ctx, exp); + if (p) { + while (orig_count > LY_ARRAY_COUNT(p)) { + LY_ARRAY_INCREMENT(p); + } + } + ly_path_free(ctx, p); + if (!ret) { + /* set out params only on success */ + if (new_parent) { + *new_parent = nparent; + } + if (new_node) { + *new_node = nnode; + } + } else { + lyd_free_tree(nparent); + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_path(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const char *value, uint32_t options, + struct lyd_node **node) +{ + LY_CHECK_ARG_RET(ctx, parent || ctx, path, (path[0] == '/') || parent, + !(options & LYD_NEW_PATH_BIN_VALUE) || !(options & LYD_NEW_PATH_CANON_VALUE), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, ctx, LY_EINVAL); + + return lyd_new_path_(parent, ctx, NULL, path, value, 0, LYD_ANYDATA_STRING, options, node, NULL); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_path2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value, + size_t value_len, LYD_ANYDATA_VALUETYPE value_type, uint32_t options, struct lyd_node **new_parent, + struct lyd_node **new_node) +{ + LY_CHECK_ARG_RET(ctx, parent || ctx, path, (path[0] == '/') || parent, + !(options & LYD_NEW_PATH_BIN_VALUE) || !(options & LYD_NEW_PATH_CANON_VALUE), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, ctx, LY_EINVAL); + + return lyd_new_path_(parent, ctx, NULL, path, value, value_len, value_type, options, new_parent, new_node); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_ext_path(struct lyd_node *parent, const struct lysc_ext_instance *ext, const char *path, const void *value, + uint32_t options, struct lyd_node **node) +{ + const struct ly_ctx *ctx = ext ? ext->module->ctx : NULL; + + LY_CHECK_ARG_RET(ctx, ext, path, (path[0] == '/') || parent, + !(options & LYD_NEW_PATH_BIN_VALUE) || !(options & LYD_NEW_PATH_CANON_VALUE), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, ctx, LY_EINVAL); + + return lyd_new_path_(parent, ctx, ext, path, value, 0, LYD_ANYDATA_STRING, options, node, NULL); +} + +LY_ERR +lyd_new_implicit_r(struct lyd_node *parent, struct lyd_node **first, const struct lysc_node *sparent, + const struct lys_module *mod, struct ly_set *node_when, struct ly_set *node_types, struct ly_set *ext_node, + uint32_t impl_opts, struct lyd_node **diff) +{ + LY_ERR ret; + const struct lysc_node *iter = NULL; + struct lyd_node *node = NULL; + struct lyd_value **dflts; + LY_ARRAY_COUNT_TYPE u; + uint32_t getnext_opts; + + assert(first && (parent || sparent || mod)); + + if (!sparent && parent) { + sparent = parent->schema; + } + + getnext_opts = LYS_GETNEXT_WITHCHOICE; + if (impl_opts & LYD_IMPLICIT_OUTPUT) { + getnext_opts |= LYS_GETNEXT_OUTPUT; + } + + while ((iter = lys_getnext(iter, sparent, mod ? mod->compiled : NULL, getnext_opts))) { + if ((impl_opts & LYD_IMPLICIT_NO_STATE) && (iter->flags & LYS_CONFIG_R)) { + continue; + } else if ((impl_opts & LYD_IMPLICIT_NO_CONFIG) && (iter->flags & LYS_CONFIG_W)) { + continue; + } + + switch (iter->nodetype) { + case LYS_CHOICE: + node = lys_getnext_data(NULL, *first, NULL, iter, NULL); + if (!node && ((struct lysc_node_choice *)iter)->dflt) { + /* create default case data */ + LY_CHECK_RET(lyd_new_implicit_r(parent, first, &((struct lysc_node_choice *)iter)->dflt->node, + NULL, node_when, node_types, ext_node, impl_opts, diff)); + } else if (node) { + /* create any default data in the existing case */ + assert(node->schema->parent->nodetype == LYS_CASE); + LY_CHECK_RET(lyd_new_implicit_r(parent, first, node->schema->parent, NULL, node_when, node_types, + ext_node, impl_opts, diff)); + } + break; + case LYS_CONTAINER: + if (!(iter->flags & LYS_PRESENCE) && lyd_find_sibling_val(*first, iter, NULL, 0, NULL)) { + /* create default NP container */ + LY_CHECK_RET(lyd_create_inner(iter, &node)); + node->flags = LYD_DEFAULT | (lysc_has_when(iter) ? LYD_WHEN_TRUE : 0); + lyd_insert_node(parent, first, node, 0); + + if (lysc_has_when(iter) && node_when) { + /* remember to resolve when */ + LY_CHECK_RET(ly_set_add(node_when, node, 1, NULL)); + } + if (ext_node) { + /* store for ext instance node validation, if needed */ + LY_CHECK_RET(lyd_validate_node_ext(node, ext_node)); + } + if (diff) { + /* add into diff */ + LY_CHECK_RET(lyd_val_diff_add(node, LYD_DIFF_OP_CREATE, diff)); + } + + /* create any default children */ + LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, node_when, node_types, + ext_node, impl_opts, diff)); + } + break; + case LYS_LEAF: + if (!(impl_opts & LYD_IMPLICIT_NO_DEFAULTS) && ((struct lysc_node_leaf *)iter)->dflt && + lyd_find_sibling_val(*first, iter, NULL, 0, NULL)) { + /* create default leaf */ + ret = lyd_create_term2(iter, ((struct lysc_node_leaf *)iter)->dflt, &node); + if (ret == LY_EINCOMPLETE) { + if (node_types) { + /* remember to resolve type */ + LY_CHECK_RET(ly_set_add(node_types, node, 1, NULL)); + } + } else if (ret) { + return ret; + } + node->flags = LYD_DEFAULT | (lysc_has_when(iter) ? LYD_WHEN_TRUE : 0); + lyd_insert_node(parent, first, node, 0); + + if (lysc_has_when(iter) && node_when) { + /* remember to resolve when */ + LY_CHECK_RET(ly_set_add(node_when, node, 1, NULL)); + } + if (ext_node) { + /* store for ext instance node validation, if needed */ + LY_CHECK_RET(lyd_validate_node_ext(node, ext_node)); + } + if (diff) { + /* add into diff */ + LY_CHECK_RET(lyd_val_diff_add(node, LYD_DIFF_OP_CREATE, diff)); + } + } + break; + case LYS_LEAFLIST: + if (!(impl_opts & LYD_IMPLICIT_NO_DEFAULTS) && ((struct lysc_node_leaflist *)iter)->dflts && + lyd_find_sibling_val(*first, iter, NULL, 0, NULL)) { + /* create all default leaf-lists */ + dflts = ((struct lysc_node_leaflist *)iter)->dflts; + LY_ARRAY_FOR(dflts, u) { + ret = lyd_create_term2(iter, dflts[u], &node); + if (ret == LY_EINCOMPLETE) { + if (node_types) { + /* remember to resolve type */ + LY_CHECK_RET(ly_set_add(node_types, node, 1, NULL)); + } + } else if (ret) { + return ret; + } + node->flags = LYD_DEFAULT | (lysc_has_when(iter) ? LYD_WHEN_TRUE : 0); + lyd_insert_node(parent, first, node, 0); + + if (lysc_has_when(iter) && node_when) { + /* remember to resolve when */ + LY_CHECK_RET(ly_set_add(node_when, node, 1, NULL)); + } + if (ext_node) { + /* store for ext instance node validation, if needed */ + LY_CHECK_RET(lyd_validate_node_ext(node, ext_node)); + } + if (diff) { + /* add into diff */ + LY_CHECK_RET(lyd_val_diff_add(node, LYD_DIFF_OP_CREATE, diff)); + } + } + } + break; + default: + /* without defaults */ + break; + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_implicit_tree(struct lyd_node *tree, uint32_t implicit_options, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *node; + struct ly_set node_when = {0}; + + LY_CHECK_ARG_RET(NULL, tree, LY_EINVAL); + if (diff) { + *diff = NULL; + } + + LYD_TREE_DFS_BEGIN(tree, node) { + /* skip added default nodes */ + if (((node->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) && + (node->schema->nodetype & LYD_NODE_INNER)) { + LY_CHECK_GOTO(ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &node_when, NULL, + NULL, implicit_options, diff), cleanup); + } + + LYD_TREE_DFS_END(tree, node); + } + + /* resolve when and remove any invalid defaults */ + ret = lyd_validate_unres(&tree, NULL, 0, &node_when, LYXP_IGNORE_WHEN, NULL, NULL, NULL, NULL, 0, diff); + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + ly_set_erase(&node_when, NULL); + if (ret && diff) { + lyd_free_all(*diff); + *diff = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_implicit_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t implicit_options, struct lyd_node **diff) +{ + const struct lys_module *mod; + struct lyd_node *d = NULL; + uint32_t i = 0; + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_ARG_RET(ctx, tree, *tree || ctx, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(*tree ? LYD_CTX(*tree) : NULL, ctx, LY_EINVAL); + if (diff) { + *diff = NULL; + } + if (!ctx) { + ctx = LYD_CTX(*tree); + } + + /* add nodes for each module one-by-one */ + while ((mod = ly_ctx_get_module_iter(ctx, &i))) { + if (!mod->implemented) { + continue; + } + + LY_CHECK_GOTO(ret = lyd_new_implicit_module(tree, mod, implicit_options, diff ? &d : NULL), cleanup); + if (d) { + /* merge into one diff */ + lyd_insert_sibling(*diff, d, diff); + + d = NULL; + } + } + +cleanup: + if (ret && diff) { + lyd_free_all(*diff); + *diff = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_implicit_module(struct lyd_node **tree, const struct lys_module *module, uint32_t implicit_options, + struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *root, *d = NULL; + struct ly_set node_when = {0}; + + LY_CHECK_ARG_RET(NULL, tree, module, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(*tree ? LYD_CTX(*tree) : NULL, module ? module->ctx : NULL, LY_EINVAL); + if (diff) { + *diff = NULL; + } + + /* add all top-level defaults for this module */ + LY_CHECK_GOTO(ret = lyd_new_implicit_r(NULL, tree, NULL, module, &node_when, NULL, NULL, implicit_options, diff), + cleanup); + + /* resolve when and remove any invalid defaults */ + LY_CHECK_GOTO(ret = lyd_validate_unres(tree, module, 0, &node_when, LYXP_IGNORE_WHEN, NULL, NULL, NULL, NULL, + 0, diff), cleanup); + + /* process nested nodes */ + LY_LIST_FOR(*tree, root) { + /* skip added default nodes */ + if ((root->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) { + LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup); + + if (d) { + /* merge into one diff */ + lyd_insert_sibling(*diff, d, diff); + + d = NULL; + } + } + } + +cleanup: + ly_set_erase(&node_when, NULL); + if (ret && diff) { + lyd_free_all(*diff); + *diff = NULL; + } + return ret; +} diff --git a/src/tree_edit.h b/src/tree_edit.h new file mode 100644 index 0000000..951d95d --- /dev/null +++ b/src/tree_edit.h @@ -0,0 +1,306 @@ +/** + * @file tree_edit.h + * @author Radek Krejci + * @brief libyang generic macros and functions to modify YANG schema or data trees. Intended for internal use and libyang + * plugins. + * + * Copyright (c) 2019-2021 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_TREE_EDIT_H_ +#define LY_TREE_EDIT_H_ + +#include + +#ifndef LOGMEM +#define LOGMEM(CTX) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Wrapper for realloc() call. The only difference is that if it fails to + * allocate the requested memory, the original memory is freed as well. + * + * @param[in] ptr Memory to reallocate. + * @param[in] size New size of the memory block. + * + * @return Pointer to the new memory, NULL on error. + */ +void *ly_realloc(void *ptr, size_t size); + +/** + * @defgroup trees_edit Trees - modification + * @ingroup trees + * + * Generic macros, functions, etc. to modify [schema](@ref schematree) and [data](@ref datatree) trees. + * @{ + */ + +/** + * @brief (Re-)Allocation of a ([sized array](@ref sizedarrays)). + * + * Increases the size information. + * + * This is a generic macro for ::LY_ARRAY_NEW_RET and ::LY_ARRAY_NEW_GOTO. + * + * @param[in] CTX libyang context for logging. + * @param[in,out] ARRAY Pointer to the array to allocate/resize. The size of the allocated + * space is counted from the type of the ARRAY, so do not provide placeholder void pointers. + * @param[in] EACTION Action to perform in case of error (memory allocation failure). + */ +#define LY_ARRAY_NEW(CTX, ARRAY, EACTION) \ + { \ + char *p__; \ + if (ARRAY) { \ + ++(*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)); \ + p__ = (char *)realloc(((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1), \ + sizeof(LY_ARRAY_COUNT_TYPE) + (*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1) * sizeof *(ARRAY))); \ + if (!p__) { \ + --(*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)); \ + LOGMEM(CTX); \ + EACTION; \ + } \ + } else { \ + p__ = (char *)malloc(sizeof(LY_ARRAY_COUNT_TYPE) + sizeof *(ARRAY)); \ + if (!p__) { \ + LOGMEM(CTX); \ + EACTION; \ + } \ + *((LY_ARRAY_COUNT_TYPE*)(p__)) = 1; \ + } \ + p__ = (char *)((LY_ARRAY_COUNT_TYPE*)(p__) + 1); \ + memcpy(&(ARRAY), &p__, sizeof p__); \ + } + +/** + * @brief (Re-)Allocation of a ([sized array](@ref sizedarrays)). + * + * Increases the size information. + * + * @param[in] CTX libyang context for logging. + * @param[in,out] ARRAY Pointer to the array to allocate/resize. The size of the allocated + * space is counted from the type of the ARRAY, so do not provide placeholder void pointers. + * @param[out] NEW_ITEM Returning pointer to the newly allocated record in the ARRAY. + * @param[in] RETVAL Return value for the case of error (memory allocation failure). + */ +#define LY_ARRAY_NEW_RET(CTX, ARRAY, NEW_ITEM, RETVAL) \ + LY_ARRAY_NEW(CTX, ARRAY, return RETVAL); \ + (NEW_ITEM) = &(ARRAY)[*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1) - 1]; \ + memset(NEW_ITEM, 0, sizeof *(NEW_ITEM)) + +/** + * @brief (Re-)Allocation of a ([sized array](@ref sizedarrays)). + * + * Increases the size information. + * + * @param[in] CTX libyang context for logging. + * @param[in,out] ARRAY Pointer to the array to allocate/resize. The size of the allocated + * space is counted from the type of the ARRAY, so do not provide placeholder void pointers. + * @param[out] NEW_ITEM Returning pointer to the newly allocated record in the ARRAY. + * @param[out] RET Variable to store error code. + * @param[in] GOTO Label to go in case of error (memory allocation failure). + */ +#define LY_ARRAY_NEW_GOTO(CTX, ARRAY, NEW_ITEM, RET, GOTO) \ + LY_ARRAY_NEW(CTX, ARRAY, RET = LY_EMEM; goto GOTO); \ + (NEW_ITEM) = &(ARRAY)[*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1) - 1]; \ + memset(NEW_ITEM, 0, sizeof *(NEW_ITEM)) + +/** + * @brief Allocate a ([sized array](@ref sizedarrays)) for the specified number of items. + * If the ARRAY already exists, it is resized (space for SIZE items is added and zeroed). + * + * Does not set the size information, it is supposed to be incremented via ::LY_ARRAY_INCREMENT + * when the items are filled. + * + * This is a generic macro for ::LY_ARRAY_CREATE_RET and ::LY_ARRAY_CREATE_GOTO. + * + * @param[in] CTX libyang context for logging. + * @param[in,out] ARRAY Pointer to the array to create. + * @param[in] SIZE Number of the new items the array is supposed to hold. The size of the allocated + * space is then counted from the type of the ARRAY, so do not provide placeholder void pointers. + * @param[in] EACTION Action to perform in case of error (memory allocation failure). + */ +#define LY_ARRAY_CREATE(CTX, ARRAY, SIZE, EACTION) \ + { \ + char *p__; \ + if (ARRAY) { \ + p__ = (char *)realloc(((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1), \ + sizeof(LY_ARRAY_COUNT_TYPE) + ((*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1) + (SIZE)) * sizeof *(ARRAY))); \ + if (!p__) { \ + LOGMEM(CTX); \ + EACTION; \ + } \ + } else { \ + p__ = (char *)calloc(1, sizeof(LY_ARRAY_COUNT_TYPE) + (SIZE) * sizeof *(ARRAY)); \ + if (!p__) { \ + LOGMEM(CTX); \ + EACTION; \ + } \ + } \ + p__ = (char *)((LY_ARRAY_COUNT_TYPE*)(p__) + 1); \ + memcpy(&(ARRAY), &p__, sizeof p__); \ + if (ARRAY) { \ + memset(&(ARRAY)[*((LY_ARRAY_COUNT_TYPE*)(p__) - 1)], 0, (SIZE) * sizeof *(ARRAY)); \ + } \ + } + +/** + * @brief Allocate a ([sized array](@ref sizedarrays)) for the specified number of items. + * If the ARRAY already exists, it is resized (space for SIZE items is added and zeroed). + * + * Does not set the size information, it is supposed to be incremented via ::LY_ARRAY_INCREMENT + * when the items are filled. + * + * @param[in] CTX libyang context for logging. + * @param[in,out] ARRAY Pointer to the array to create. + * @param[in] SIZE Number of the new items the array is supposed to hold. The size of the allocated + * space is then counted from the type of the ARRAY, so do not provide placeholder void pointers. + * @param[in] RETVAL Return value for the case of error (memory allocation failure). + */ +#define LY_ARRAY_CREATE_RET(CTX, ARRAY, SIZE, RETVAL) \ + LY_ARRAY_CREATE(CTX, ARRAY, SIZE, return RETVAL) + +/** + * @brief Allocate a ([sized array](@ref sizedarrays)) for the specified number of items. + * If the ARRAY already exists, it is resized (space for SIZE items is added). + * + * Does not set the count information, it is supposed to be incremented via ::LY_ARRAY_INCREMENT + * when the items are filled. + * + * @param[in] CTX libyang context for logging. + * @param[in,out] ARRAY Pointer to the array to create. + * @param[in] SIZE Number of the new items the array is supposed to hold. The size of the allocated + * space is then counted from the type of the ARRAY, so do not provide placeholder void pointers. + * @param[out] RET Variable to store error code. + * @param[in] GOTO Label to go in case of error (memory allocation failure). + */ +#define LY_ARRAY_CREATE_GOTO(CTX, ARRAY, SIZE, RET, GOTO) \ + LY_ARRAY_CREATE(CTX, ARRAY, SIZE, RET = LY_EMEM; goto GOTO) + +/** + * @brief Increment the items counter in a ([sized array](@ref sizedarrays)). + * + * Does not change the allocated memory used by the ARRAY. To do so, use LY_ARRAY_CREATE_RET, + * LY_ARRAY_CREATE_GOTO or LY_ARRAY_RESIZE_ERR_RET. + * + * @param[in] ARRAY Pointer to the array to affect. + */ +#define LY_ARRAY_INCREMENT(ARRAY) \ + ++(*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)) + +/** + * @brief Decrement the items counter in a ([sized array](@ref sizedarrays)). + * + * Does not change the allocated memory used by the ARRAY. To do so, use LY_ARRAY_CREATE_RET, + * LY_ARRAY_CREATE_GOTO or LY_ARRAY_RESIZE_ERR_RET. + * + * @param[in] ARRAY Pointer to the array to affect. + */ +#define LY_ARRAY_DECREMENT(ARRAY) \ + --(*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)) + +/** + * @brief Decrement the items counter in a ([sized array](@ref sizedarrays)) and free the whole array + * in case it was decremented to 0. + * + * @param[in] ARRAY Pointer to the array to affect. + */ +#define LY_ARRAY_DECREMENT_FREE(ARRAY) \ + --(*((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1)); \ + if (!LY_ARRAY_COUNT(ARRAY)) { \ + LY_ARRAY_FREE(ARRAY); \ + (ARRAY) = NULL; \ + } + +/** + * @brief Free the space allocated for the ([sized array](@ref sizedarrays)). + * + * The items inside the array are not freed. + * + * @param[in] ARRAY A ([sized array](@ref sizedarrays)) to be freed. + */ +#define LY_ARRAY_FREE(ARRAY) \ + if (ARRAY){free((LY_ARRAY_COUNT_TYPE*)(ARRAY) - 1);} + +/** + * @brief Insert item into linked list. + * + * @param[in,out] LIST Linked list to add to. + * @param[in] NEW_ITEM New item, that will be appended to the list, must be already allocated. + * @param[in] LINKER name of structuring member that is used to connect items together. + */ +#define LY_LIST_INSERT(LIST, NEW_ITEM, LINKER)\ + if (!(*LIST)) { \ + memcpy(LIST, &(NEW_ITEM), sizeof NEW_ITEM); \ + } else { \ + size_t offset__ = (char *)&(*LIST)->LINKER - (char *)(*LIST); \ + char **iter__ = (char **)((size_t)(*LIST) + offset__); \ + while (*iter__) { \ + iter__ = (char **)((size_t)(*iter__) + offset__); \ + } \ + memcpy(iter__, &(NEW_ITEM), sizeof NEW_ITEM); \ + } + +/** + * @brief Allocate and insert new item into linked list, return in case of error. + * + * This is a generic macro for ::LY_LIST_NEW_RET and ::LY_LIST_NEW_GOTO. + * + * @param[in] CTX used for logging. + * @param[in,out] LIST Linked list to add to. + * @param[out] NEW_ITEM New item that is appended to the list. + * @param[in] LINKER name of structure member that is used to connect items together. + * @param[in] EACTION Action to perform in case of error (memory allocation failure). + */ +#define LY_LIST_NEW(CTX, LIST, NEW_ITEM, LINKER, EACTION) \ + { \ + char *p__ = (char *)calloc(1, sizeof *NEW_ITEM); \ + if (!p__) { \ + LOGMEM(CTX); \ + EACTION; \ + } \ + memcpy(&(NEW_ITEM), &p__, sizeof p__); \ + LY_LIST_INSERT(LIST, NEW_ITEM, LINKER); \ + } + +/** + * @brief Allocate and insert new item into linked list, return in case of error. + * + * @param[in] CTX used for logging. + * @param[in,out] LIST Linked list to add to. + * @param[out] NEW_ITEM New item that is appended to the list. + * @param[in] LINKER name of structure member that is used to connect items together. + * @param[in] RETVAL Return value for the case of error (memory allocation failure). + */ +#define LY_LIST_NEW_RET(CTX, LIST, NEW_ITEM, LINKER, RETVAL) \ + LY_LIST_NEW(CTX, LIST, NEW_ITEM, LINKER, return RETVAL) + +/** + * @brief Allocate and insert new item into linked list, goto specified label in case of error. + * + * @param[in] CTX used for logging. + * @param[in,out] LIST Linked list to add to. + * @param[out] NEW_ITEM New item that is appended to the list. + * @param[in] LINKER name of structure member that is used to connect items together. + * @param[in] RET variable to store returned error type. + * @param[in] LABEL label to goto in case of error. + */ +#define LY_LIST_NEW_GOTO(CTX, LIST, NEW_ITEM, LINKER, RET, LABEL) \ + LY_LIST_NEW(CTX, LIST, NEW_ITEM, LINKER, RET = LY_EMEM; goto LABEL) + +/** @} trees_edit */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_TREE_EDIT_H_ */ diff --git a/src/tree_schema.c b/src/tree_schema.c new file mode 100644 index 0000000..5c897bf --- /dev/null +++ b/src/tree_schema.c @@ -0,0 +1,2178 @@ +/** + * @file tree_schema.c + * @author Radek Krejci + * @brief Schema tree implementation + * + * Copyright (c) 2015 - 2018 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 /* asprintf, strdup */ + +#include "tree_schema.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "in.h" +#include "in_internal.h" +#include "log.h" +#include "parser_internal.h" +#include "parser_schema.h" +#include "path.h" +#include "plugins_internal.h" +#include "schema_compile.h" +#include "schema_compile_amend.h" +#include "schema_features.h" +#include "set.h" +#include "tree.h" +#include "tree_edit.h" +#include "tree_schema_free.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +const char * const ly_devmod_list[] = { + [LYS_DEV_NOT_SUPPORTED] = "not-supported", + [LYS_DEV_ADD] = "add", + [LYS_DEV_DELETE] = "delete", + [LYS_DEV_REPLACE] = "replace", +}; + +LIBYANG_API_DEF LY_ERR +lysc_tree_dfs_full(const struct lysc_node *root, lysc_dfs_clb dfs_clb, void *data) +{ + struct lysc_node *elem, *elem2; + const struct lysc_node_action *action; + const struct lysc_node_notif *notif; + + LY_CHECK_ARG_RET(NULL, root, dfs_clb, LY_EINVAL); + + LYSC_TREE_DFS_BEGIN(root, elem) { + /* schema node */ + LY_CHECK_RET(dfs_clb(elem, data, &LYSC_TREE_DFS_continue)); + + LY_LIST_FOR(lysc_node_actions(elem), action) { + LYSC_TREE_DFS_BEGIN(action, elem2) { + /* action subtree */ + LY_CHECK_RET(dfs_clb(elem2, data, &LYSC_TREE_DFS_continue)); + + LYSC_TREE_DFS_END(action, elem2); + } + } + + LY_LIST_FOR(lysc_node_notifs(elem), notif) { + LYSC_TREE_DFS_BEGIN(notif, elem2) { + /* notification subtree */ + LY_CHECK_RET(dfs_clb(elem2, data, &LYSC_TREE_DFS_continue)); + + LYSC_TREE_DFS_END(notif, elem2); + } + } + + LYSC_TREE_DFS_END(root, elem); + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF LY_ERR +lysc_module_dfs_full(const struct lys_module *mod, lysc_dfs_clb dfs_clb, void *data) +{ + const struct lysc_node *root; + + LY_CHECK_ARG_RET(NULL, mod, mod->compiled, dfs_clb, LY_EINVAL); + + /* schema nodes */ + LY_LIST_FOR(mod->compiled->data, root) { + LY_CHECK_RET(lysc_tree_dfs_full(root, dfs_clb, data)); + } + + /* RPCs */ + LY_LIST_FOR((const struct lysc_node *)mod->compiled->rpcs, root) { + LY_CHECK_RET(lysc_tree_dfs_full(root, dfs_clb, data)); + } + + /* notifications */ + LY_LIST_FOR((const struct lysc_node *)mod->compiled->notifs, root) { + LY_CHECK_RET(lysc_tree_dfs_full(root, dfs_clb, data)); + } + + return LY_SUCCESS; +} + +static void +lys_getnext_into_case(const struct lysc_node_case *first_case, const struct lysc_node **last, const struct lysc_node **next) +{ + for ( ; first_case; first_case = (const struct lysc_node_case *)first_case->next) { + if (first_case->child) { + /* there is something to return */ + (*next) = first_case->child; + return; + } + } + + /* no children in choice's cases, so go to the choice's sibling instead of into it */ + (*last) = (*next); + (*next) = (*next)->next; +} + +/** + * @brief Generic getnext function for ::lys_getnext() and ::lys_getnext_ext(). + * + * Gets next schema tree (sibling) node element that can be instantiated in a data tree. Returned node can + * be from an augment. If the @p ext is provided, the function is locked inside the schema tree defined in the + * extension instance. + * + * ::lys_getnext_() is supposed to be called sequentially. In the first call, the @p last parameter is usually NULL + * and function starts returning i) the first @p parent's child or ii) the first top level element specified in the + * given extension (if provided) or iii) the first top level element of the @p module. + * Consequent calls suppose to provide the previously returned node as the @p last parameter and still the same + * @p parent and @p module parameters. + * + * Without options, the function is used to traverse only the schema nodes that can be paired with corresponding + * data nodes in a data tree. By setting some @p options the behavior can be modified to the extent that + * all the schema nodes are iteratively returned. + * + * @param[in] last Previously returned schema tree node, or NULL in case of the first call. + * @param[in] parent Parent of the subtree where the function starts processing. + * @param[in] module In case of iterating on top level elements, the @p parent is NULL and + * module must be specified. + * @param[in] ext The extension instance to provide a separate schema tree. To consider the top level elements in the tree, + * the @p parent must be NULL. Anyway, at least one of @p parent, @p module and @p ext parameters must be specified. + * @param[in] options [ORed options](@ref sgetnextflags). + * @return Next schema tree node that can be instantiated in a data tree, NULL in case there is no such element. + */ +static const struct lysc_node * +lys_getnext_(const struct lysc_node *last, const struct lysc_node *parent, const struct lysc_module *module, + const struct lysc_ext_instance *ext, uint32_t options) +{ + const struct lysc_node *next = NULL; + ly_bool action_flag = 0, notif_flag = 0; + + LY_CHECK_ARG_RET(NULL, parent || module || ext, NULL); + +next: + if (!last) { + /* first call */ + + /* get know where to start */ + if (parent) { + /* schema subtree */ + next = last = lysc_node_child(parent); + } else { + /* top level data */ + if (ext) { + lyplg_ext_get_storage(ext, LY_STMT_DATA_NODE_MASK, sizeof last, (const void **)&last); + next = last; + } else { + next = last = module->data; + } + } + if (!next) { + /* try to get action or notification */ + goto repeat; + } + /* test if the next can be returned */ + goto check; + + } else if (last->nodetype & (LYS_RPC | LYS_ACTION)) { + action_flag = 1; + next = last->next; + } else if (last->nodetype == LYS_NOTIF) { + action_flag = notif_flag = 1; + next = last->next; + } else { + next = last->next; + } + +repeat: + if (!next) { + /* possibly go back to parent */ + if (last && (last->parent != parent)) { + last = last->parent; + goto next; + } else if (!action_flag) { + action_flag = 1; + if (ext) { + lyplg_ext_get_storage(ext, LY_STMT_OP_MASK, sizeof next, (const void **)&next); + } else if (parent) { + next = (struct lysc_node *)lysc_node_actions(parent); + } else { + next = (struct lysc_node *)module->rpcs; + } + } else if (!notif_flag) { + notif_flag = 1; + if (ext) { + lyplg_ext_get_storage(ext, LY_STMT_NOTIFICATION, sizeof next, (const void **)&next); + } else if (parent) { + next = (struct lysc_node *)lysc_node_notifs(parent); + } else { + next = (struct lysc_node *)module->notifs; + } + } else { + return NULL; + } + goto repeat; + } +check: + switch (next->nodetype) { + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + case LYS_LEAF: + case LYS_ANYXML: + case LYS_ANYDATA: + case LYS_LIST: + case LYS_LEAFLIST: + break; + case LYS_CASE: + if (options & LYS_GETNEXT_WITHCASE) { + break; + } else { + /* go into */ + lys_getnext_into_case((const struct lysc_node_case *)next, &last, &next); + } + goto repeat; + case LYS_CONTAINER: + if (!(next->flags & LYS_PRESENCE) && (options & LYS_GETNEXT_INTONPCONT)) { + if (lysc_node_child(next)) { + /* go into */ + next = lysc_node_child(next); + } else { + last = next; + next = next->next; + } + goto repeat; + } + break; + case LYS_CHOICE: + if (options & LYS_GETNEXT_WITHCHOICE) { + break; + } else if ((options & LYS_GETNEXT_NOCHOICE) || !lysc_node_child(next)) { + next = next->next; + } else { + if (options & LYS_GETNEXT_WITHCASE) { + next = lysc_node_child(next); + } else { + /* go into */ + lys_getnext_into_case(((struct lysc_node_choice *)next)->cases, &last, &next); + } + } + goto repeat; + case LYS_INPUT: + if (options & LYS_GETNEXT_OUTPUT) { + /* skip */ + next = next->next; + } else { + /* go into */ + next = lysc_node_child(next); + } + goto repeat; + case LYS_OUTPUT: + if (!(options & LYS_GETNEXT_OUTPUT)) { + /* skip */ + next = next->next; + } else { + /* go into */ + next = lysc_node_child(next); + } + goto repeat; + default: + /* we should not be here */ + LOGINT(module ? module->mod->ctx : parent ? parent->module->ctx : ext->module->ctx); + return NULL; + } + + return next; +} + +LIBYANG_API_DEF const struct lysc_node * +lys_getnext(const struct lysc_node *last, const struct lysc_node *parent, const struct lysc_module *module, uint32_t options) +{ + return lys_getnext_(last, parent, module, NULL, options); +} + +LIBYANG_API_DEF const struct lysc_node * +lys_getnext_ext(const struct lysc_node *last, const struct lysc_node *parent, const struct lysc_ext_instance *ext, uint32_t options) +{ + return lys_getnext_(last, parent, NULL, ext, options); +} + +const struct lysc_node * +lysc_ext_find_node(const struct lysc_ext_instance *ext, const struct lys_module *module, const char *name, size_t name_len, + uint16_t nodetype, uint32_t options) +{ + const struct lysc_node *node = NULL; + + LY_CHECK_ARG_RET(NULL, ext, name, NULL); + if (!nodetype) { + nodetype = LYS_NODETYPE_MASK; + } + + if (module && (module != ext->module)) { + return NULL; + } + + while ((node = lys_getnext_ext(node, NULL, ext, options))) { + if (!(node->nodetype & nodetype)) { + continue; + } + + if (name_len) { + if (!ly_strncmp(node->name, name, name_len)) { + return node; + } + } else { + if (!strcmp(node->name, name)) { + return node; + } + } + } + return NULL; +} + +LIBYANG_API_DEF const struct lysc_node * +lys_find_child(const struct lysc_node *parent, const struct lys_module *module, const char *name, size_t name_len, + uint16_t nodetype, uint32_t options) +{ + const struct lysc_node *node = NULL; + + LY_CHECK_ARG_RET(NULL, module, name, NULL); + LY_CHECK_CTX_EQUAL_RET(parent ? parent->module->ctx : NULL, module->ctx, NULL); + if (!nodetype) { + nodetype = LYS_NODETYPE_MASK; + } + + while ((node = lys_getnext(node, parent, module->compiled, options))) { + if (!(node->nodetype & nodetype)) { + continue; + } + if (node->module != module) { + continue; + } + + if (name_len) { + if (!ly_strncmp(node->name, name, name_len)) { + return node; + } + } else { + if (!strcmp(node->name, name)) { + return node; + } + } + } + + return NULL; +} + +LIBYANG_API_DEF LY_ERR +lys_find_xpath_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *xpath, uint32_t options, + struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_set xp_set; + struct lyxp_expr *exp = NULL; + uint32_t i; + + LY_CHECK_ARG_RET(NULL, ctx || ctx_node, xpath, set, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, ctx_node ? ctx_node->module->ctx : NULL, LY_EINVAL); + if (!(options & LYXP_SCNODE_ALL)) { + options |= LYXP_SCNODE; + } + if (!ctx) { + ctx = ctx_node->module->ctx; + } + + memset(&xp_set, 0, sizeof xp_set); + + /* compile expression */ + ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp); + LY_CHECK_GOTO(ret, cleanup); + + /* atomize expression */ + ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options); + LY_CHECK_GOTO(ret, cleanup); + + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); + + /* transform into ly_set */ + (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); + LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup); + (*set)->size = xp_set.used; + + for (i = 0; i < xp_set.used; ++i) { + if (xp_set.val.scnodes[i].type == LYXP_NODE_ELEM) { + ret = ly_set_add(*set, xp_set.val.scnodes[i].scnode, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&xp_set); + lyxp_expr_free(ctx, exp); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_find_expr_atoms(const struct lysc_node *ctx_node, const struct lys_module *cur_mod, const struct lyxp_expr *expr, + const struct lysc_prefix *prefixes, uint32_t options, struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_set xp_set = {0}; + uint32_t i; + + LY_CHECK_ARG_RET(NULL, cur_mod, expr, prefixes, set, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx_node ? ctx_node->module->ctx : NULL, cur_mod->ctx, LY_EINVAL); + if (!(options & LYXP_SCNODE_ALL)) { + options = LYXP_SCNODE; + } + + /* atomize expression */ + ret = lyxp_atomize(cur_mod->ctx, expr, cur_mod, LY_VALUE_SCHEMA_RESOLVED, (void *)prefixes, ctx_node, ctx_node, + &xp_set, options); + LY_CHECK_GOTO(ret, cleanup); + + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); + + /* transform into ly_set */ + (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); + LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(cur_mod->ctx); ret = LY_EMEM, cleanup); + (*set)->size = xp_set.used; + + for (i = 0; i < xp_set.used; ++i) { + if ((xp_set.val.scnodes[i].type == LYXP_NODE_ELEM) && (xp_set.val.scnodes[i].in_ctx >= LYXP_SET_SCNODE_ATOM_NODE)) { + assert((xp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_NODE) || + (xp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL) || + (xp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX)); + ret = ly_set_add(*set, xp_set.val.scnodes[i].scnode, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&xp_set); + if (ret) { + ly_set_free(*set, NULL); + *set = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_find_xpath(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *xpath, uint32_t options, + struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_set xp_set = {0}; + struct lyxp_expr *exp = NULL; + uint32_t i; + + LY_CHECK_ARG_RET(NULL, ctx || ctx_node, xpath, set, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, ctx_node ? ctx_node->module->ctx : NULL, LY_EINVAL); + if (!(options & LYXP_SCNODE_ALL)) { + options = LYXP_SCNODE; + } + if (!ctx) { + ctx = ctx_node->module->ctx; + } + + /* compile expression */ + ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp); + LY_CHECK_GOTO(ret, cleanup); + + /* atomize expression */ + ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options); + LY_CHECK_GOTO(ret, cleanup); + + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); + + /* transform into ly_set */ + (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); + LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup); + (*set)->size = xp_set.used; + + for (i = 0; i < xp_set.used; ++i) { + if ((xp_set.val.scnodes[i].type == LYXP_NODE_ELEM) && (xp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX)) { + ret = ly_set_add(*set, xp_set.val.scnodes[i].scnode, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&xp_set); + lyxp_expr_free(ctx, exp); + if (ret) { + ly_set_free(*set, NULL); + *set = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_find_lypath_atoms(const struct ly_path *path, struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u, v; + + LY_CHECK_ARG_RET(NULL, path, set, LY_EINVAL); + + /* allocate return set */ + LY_CHECK_RET(ly_set_new(set)); + + LY_ARRAY_FOR(path, u) { + /* add nodes from the path */ + LY_CHECK_GOTO(ret = ly_set_add(*set, (void *)path[u].node, 0, NULL), cleanup); + if (path[u].pred_type == LY_PATH_PREDTYPE_LIST) { + LY_ARRAY_FOR(path[u].predicates, v) { + /* add all the keys in a predicate */ + LY_CHECK_GOTO(ret = ly_set_add(*set, (void *)path[u].predicates[v].key, 0, NULL), cleanup); + } + } + } + +cleanup: + if (ret) { + ly_set_free(*set, NULL); + *set = NULL; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_find_path_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *path, ly_bool output, + struct ly_set **set) +{ + LY_ERR ret = LY_SUCCESS; + uint8_t oper; + struct lyxp_expr *expr = NULL; + struct ly_path *p = NULL; + + LY_CHECK_ARG_RET(ctx, ctx || ctx_node, path, set, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(ctx, ctx_node ? ctx_node->module->ctx : NULL, LY_EINVAL); + + if (!ctx) { + ctx = ctx_node->module->ctx; + } + + /* parse */ + ret = lyxp_expr_parse(ctx, path, strlen(path), 0, &expr); + LY_CHECK_GOTO(ret, cleanup); + + /* compile */ + oper = output ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT; + ret = ly_path_compile(ctx, NULL, ctx_node, NULL, expr, oper, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p); + LY_CHECK_GOTO(ret, cleanup); + + /* resolve */ + ret = lys_find_lypath_atoms(p, set); + +cleanup: + ly_path_free(ctx, p); + lyxp_expr_free(ctx, expr); + return ret; +} + +LIBYANG_API_DEF const struct lysc_node * +lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *path, ly_bool output) +{ + const struct lysc_node *snode = NULL; + struct lyxp_expr *exp = NULL; + struct ly_path *p = NULL; + LY_ERR ret; + uint8_t oper; + + LY_CHECK_ARG_RET(ctx, ctx || ctx_node, NULL); + LY_CHECK_CTX_EQUAL_RET(ctx, ctx_node ? ctx_node->module->ctx : NULL, NULL); + + if (!ctx) { + ctx = ctx_node->module->ctx; + } + + /* parse */ + ret = lyxp_expr_parse(ctx, path, strlen(path), 0, &exp); + LY_CHECK_GOTO(ret, cleanup); + + /* compile */ + oper = output ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT; + ret = ly_path_compile(ctx, NULL, ctx_node, NULL, exp, oper, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p); + LY_CHECK_GOTO(ret, cleanup); + + /* get last node */ + snode = p[LY_ARRAY_COUNT(p) - 1].node; + +cleanup: + ly_path_free(ctx, p); + lyxp_expr_free(ctx, exp); + return snode; +} + +char * +lysc_path_until(const struct lysc_node *node, const struct lysc_node *parent, LYSC_PATH_TYPE pathtype, char *buffer, + size_t buflen) +{ + const struct lysc_node *iter, *par, *key; + char *path = NULL; + int len = 0; + ly_bool skip_schema; + + if (buffer) { + LY_CHECK_ARG_RET(node->module->ctx, buflen > 1, NULL); + buffer[0] = '\0'; + } + + if ((pathtype == LYSC_PATH_DATA) || (pathtype == LYSC_PATH_DATA_PATTERN)) { + /* skip schema-only nodes */ + skip_schema = 1; + } else { + skip_schema = 0; + } + + for (iter = node; iter && (iter != parent) && (len >= 0); iter = iter->parent) { + char *s; + const char *slash; + + if (skip_schema && (iter->nodetype & (LYS_CHOICE | LYS_CASE | LYS_INPUT | LYS_OUTPUT))) { + /* schema-only node */ + continue; + } + + if ((pathtype == LYSC_PATH_DATA_PATTERN) && (iter->nodetype == LYS_LIST)) { + key = NULL; + while ((key = lys_getnext(key, iter, NULL, 0)) && lysc_is_key(key)) { + s = buffer ? strdup(buffer) : path; + + /* print key predicate */ + if (buffer) { + len = snprintf(buffer, buflen, "[%s='%%s']%s", key->name, s ? s : ""); + } else { + len = asprintf(&path, "[%s='%%s']%s", key->name, s ? s : ""); + } + free(s); + + if (buffer && (buflen <= (size_t)len)) { + /* not enough space in buffer */ + break; + } + } + } + + s = buffer ? strdup(buffer) : path; + if (parent && (iter->parent == parent)) { + slash = ""; + } else { + slash = "/"; + } + + if (skip_schema) { + par = lysc_data_parent(iter); + } else { + par = iter->parent; + } + + if (!par || (par->module != iter->module)) { + /* print prefix */ + if (buffer) { + len = snprintf(buffer, buflen, "%s%s:%s%s", slash, iter->module->name, iter->name, s ? s : ""); + } else { + len = asprintf(&path, "%s%s:%s%s", slash, iter->module->name, iter->name, s ? s : ""); + } + } else { + /* prefix is the same as in parent */ + if (buffer) { + len = snprintf(buffer, buflen, "%s%s%s", slash, iter->name, s ? s : ""); + } else { + len = asprintf(&path, "%s%s%s", slash, iter->name, s ? s : ""); + } + } + free(s); + + if (buffer && (buflen <= (size_t)len)) { + /* not enough space in buffer */ + break; + } + } + + if (len < 0) { + free(path); + path = NULL; + } else if (len == 0) { + if (buffer) { + strcpy(buffer, "/"); + } else { + path = strdup("/"); + } + } + + if (buffer) { + return buffer; + } else { + return path; + } +} + +LIBYANG_API_DEF char * +lysc_path(const struct lysc_node *node, LYSC_PATH_TYPE pathtype, char *buffer, size_t buflen) +{ + return lysc_path_until(node, NULL, pathtype, buffer, buflen); +} + +LY_ERR +_lys_set_implemented(struct lys_module *mod, const char **features, struct lys_glob_unres *unres) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lys_module *mod_iter; + const char **imp_f, *all_f[] = {"*", NULL}; + uint32_t i; + + if (mod->implemented) { + /* mod is already implemented, set the features */ + r = lys_set_features(mod->parsed, features); + if (r == LY_EEXIST) { + /* no changes */ + return LY_SUCCESS; + } else if (!r) { + /* mark the module as changed */ + mod->to_compile = 1; + } + + return r; + } + + /* implement, ignore recompilation because it must always take place later */ + r = lys_implement(mod, features, unres); + LY_CHECK_ERR_GOTO(r && (r != LY_ERECOMPILE), ret = r, cleanup); + + if (mod->ctx->flags & LY_CTX_ALL_IMPLEMENTED) { + /* implement all the imports as well */ + for (i = 0; i < unres->creating.count; ++i) { + mod = unres->creating.objs[i]; + if (mod->implemented) { + continue; + } + + imp_f = (mod->ctx->flags & LY_CTX_ENABLE_IMP_FEATURES) ? all_f : NULL; + r = lys_implement(mod, imp_f, unres); + LY_CHECK_ERR_GOTO(r && (r != LY_ERECOMPILE), ret = r, cleanup); + } + } + + /* Try to find module with LYS_MOD_IMPORTED_REV flag. */ + i = 0; + while ((mod_iter = ly_ctx_get_module_iter(mod->ctx, &i))) { + if (!strcmp(mod_iter->name, mod->name) && (mod_iter != mod) && (mod_iter->latest_revision & LYS_MOD_IMPORTED_REV)) { + LOGVRB("Implemented module \"%s@%s\" was not and will not be imported if the revision-date is missing" + " in the import statement. Instead, the revision \"%s\" is imported.", mod->name, mod->revision, + mod_iter->revision); + break; + } + } + +cleanup: + return ret; +} + +/** + * @brief Check whether it may be needed to (re)compile a module from a particular dependency set + * and if so, add it into its dep set. + * + * Dependency set includes all modules that need to be (re)compiled in case any of the module(s) + * in the dep set are (re)compiled. + * + * The reason for recompilation is possible disabled nodes and updating + * leafref targets to point to the newly compiled modules. Using the import relation, the + * dependency is reflexive because of possible foreign augments and deviations, which are compiled + * during the target module compilation. + * + * - every module must belong to exactly one dep set + * - implement flag must be ignored because it can be changed during dep set compilation + * + * @param[in] mod Module to process. + * @param[in,out] ctx_set Set with all not-yet-processed modules. + * @param[in,out] dep_set Current dependency set to update. + * @param[in] aux_set Set of traversed non-compiled modules, should be empty on first call. + * @return LY_ERR value. + */ +static LY_ERR +lys_unres_dep_sets_create_mod_r(struct lys_module *mod, struct ly_set *ctx_set, struct ly_set *dep_set, + struct ly_set *aux_set) +{ + struct lys_module *mod2; + struct lysp_import *imports; + uint32_t i; + LY_ARRAY_COUNT_TYPE u, v; + ly_bool found; + + if (LYS_IS_SINGLE_DEP_SET(mod)) { + /* is already in a separate dep set */ + if (!lys_has_dep_mods(mod)) { + /* break the dep set here, no modules depend on this one */ + return LY_SUCCESS; + } + + if (ly_set_contains(aux_set, mod, NULL)) { + /* it was traversed */ + return LY_SUCCESS; + } + + /* add a new auxiliary module */ + LY_CHECK_RET(ly_set_add(aux_set, mod, 1, NULL)); + } else { + if (!ly_set_contains(ctx_set, mod, &i)) { + /* it was already processed */ + return LY_SUCCESS; + } + + /* remove it from the set, we are processing it now */ + ly_set_rm_index(ctx_set, i, NULL); + + /* add a new dependent module into the dep set */ + LY_CHECK_RET(ly_set_add(dep_set, mod, 1, NULL)); + } + + /* process imports of the module and submodules */ + imports = mod->parsed->imports; + LY_ARRAY_FOR(imports, u) { + mod2 = imports[u].module; + LY_CHECK_RET(lys_unres_dep_sets_create_mod_r(mod2, ctx_set, dep_set, aux_set)); + } + LY_ARRAY_FOR(mod->parsed->includes, v) { + imports = mod->parsed->includes[v].submodule->imports; + LY_ARRAY_FOR(imports, u) { + mod2 = imports[u].module; + if (LYS_IS_SINGLE_DEP_SET(mod2) && !lys_has_dep_mods(mod2)) { + /* break the dep set here, no modules depend on this one */ + continue; + } + + LY_CHECK_RET(lys_unres_dep_sets_create_mod_r(imports[u].module, ctx_set, dep_set, aux_set)); + } + } + + /* process modules and submodules importing this module */ + for (i = 0; i < mod->ctx->list.count; ++i) { + mod2 = mod->ctx->list.objs[i]; + found = 0; + + imports = mod2->parsed->imports; + LY_ARRAY_FOR(imports, u) { + if (imports[u].module == mod) { + found = 1; + break; + } + } + + if (!found) { + LY_ARRAY_FOR(mod2->parsed->includes, v) { + imports = mod2->parsed->includes[v].submodule->imports; + LY_ARRAY_FOR(imports, u) { + if (imports[u].module == mod) { + found = 1; + break; + } + } + + if (found) { + break; + } + } + } + + if (found) { + LY_CHECK_RET(lys_unres_dep_sets_create_mod_r(mod2, ctx_set, dep_set, aux_set)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Add all simple modules (that have nothing to (re)compile) into separate dep sets. + * + * @param[in,out] ctx_set Set with all not-yet-processed modules. + * @param[in,out] main_set Set of dependency module sets. + * @return LY_ERR value. + */ +static LY_ERR +lys_unres_dep_sets_create_single(struct ly_set *ctx_set, struct ly_set *main_set) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_module *m; + uint32_t i = 0; + struct ly_set *dep_set = NULL; + + while (i < ctx_set->count) { + m = ctx_set->objs[i]; + if (LYS_IS_SINGLE_DEP_SET(m)) { + /* remove it from the set, we are processing it now */ + ly_set_rm_index(ctx_set, i, NULL); + + /* this module can be in a separate dep set (but there still may be modules importing this one + * that depend on imports of this one in case it defines groupings) */ + LY_CHECK_GOTO(ret = ly_set_new(&dep_set), cleanup); + LY_CHECK_GOTO(ret = ly_set_add(dep_set, m, 1, NULL), cleanup); + LY_CHECK_GOTO(ret = ly_set_add(main_set, dep_set, 1, NULL), cleanup); + dep_set = NULL; + } else { + ++i; + } + } + +cleanup: + ly_set_free(dep_set, NULL); + return ret; +} + +LY_ERR +lys_unres_dep_sets_create(struct ly_ctx *ctx, struct ly_set *main_set, struct lys_module *mod) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_module *m; + struct ly_set *dep_set = NULL, *ctx_set = NULL, aux_set = {0}; + uint32_t i; + ly_bool found; + + assert(!main_set->count); + + /* start with a duplicate set of modules that we will remove from */ + LY_CHECK_GOTO(ret = ly_set_dup(&ctx->list, NULL, &ctx_set), cleanup); + + /* first create all dep sets with single modules */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create_single(ctx_set, main_set), cleanup); + + if (mod && !ly_set_contains(ctx_set, mod, NULL)) { + /* dep set for this module has already been created, nothing else to do */ + goto cleanup; + } + + while (ctx_set->count) { + /* create new dep set */ + LY_CHECK_GOTO(ret = ly_set_new(&dep_set), cleanup); + + if (mod) { + /* use the module create a dep set with the rest of its dependent modules */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create_mod_r(mod, ctx_set, dep_set, &aux_set), cleanup); + } else { + /* use first ctx mod to create a dep set with the rest of its dependent modules */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create_mod_r(ctx_set->objs[0], ctx_set, dep_set, &aux_set), cleanup); + } + ly_set_erase(&aux_set, NULL); + assert(dep_set->count); + + /* check whether there is any module that will be (re)compiled */ + found = 0; + for (i = 0; i < dep_set->count; ++i) { + m = dep_set->objs[i]; + if (m->to_compile) { + found = 1; + break; + } + } + + if (found) { + /* if there is, all the implemented modules need to be recompiled */ + for (i = 0; i < dep_set->count; ++i) { + m = dep_set->objs[i]; + if (m->implemented) { + m->to_compile = 1; + } + } + } + + /* add the dep set into main set */ + LY_CHECK_GOTO(ret = ly_set_add(main_set, dep_set, 1, NULL), cleanup); + dep_set = NULL; + + if (mod) { + /* we need dep set only for this module */ + break; + } + } + +#ifndef NDEBUG + LOGDBG(LY_LDGDEPSETS, "dep sets created (%" PRIu32 "):", main_set->count); + for (i = 0; i < main_set->count; ++i) { + struct ly_set *iter_set = main_set->objs[i]; + + LOGDBG(LY_LDGDEPSETS, "dep set #%" PRIu32 ":", i); + for (uint32_t j = 0; j < iter_set->count; ++j) { + m = iter_set->objs[j]; + LOGDBG(LY_LDGDEPSETS, "\t%s", m->name); + } + } +#endif + +cleanup: + assert(ret || main_set->objs); + ly_set_erase(&aux_set, NULL); + ly_set_free(dep_set, NULL); + ly_set_free(ctx_set, NULL); + return ret; +} + +void +lys_unres_glob_revert(struct ly_ctx *ctx, struct lys_glob_unres *unres) +{ + uint32_t i, j, idx, temp_lo = 0; + struct lysf_ctx fctx = {.ctx = ctx}; + struct ly_set *dep_set; + LY_ERR ret; + + for (i = 0; i < unres->implementing.count; ++i) { + fctx.mod = unres->implementing.objs[i]; + assert(fctx.mod->implemented); + + /* make the module correctly non-implemented again */ + fctx.mod->implemented = 0; + lys_precompile_augments_deviations_revert(ctx, fctx.mod); + lysc_module_free(&fctx, fctx.mod->compiled); + fctx.mod->compiled = NULL; + + /* should not be made implemented */ + fctx.mod->to_compile = 0; + } + + for (i = 0; i < unres->creating.count; ++i) { + fctx.mod = unres->creating.objs[i]; + + /* remove the module from the context */ + ly_set_rm(&ctx->list, fctx.mod, NULL); + + /* remove it also from dep sets */ + for (j = 0; j < unres->dep_sets.count; ++j) { + dep_set = unres->dep_sets.objs[j]; + if (ly_set_contains(dep_set, fctx.mod, &idx)) { + ly_set_rm_index(dep_set, idx, NULL); + break; + } + } + + /* free the module */ + lys_module_free(&fctx, fctx.mod, 1); + } + + /* remove the extensions as well */ + lysf_ctx_erase(&fctx); + + if (unres->implementing.count) { + /* recompile previous context because some implemented modules are no longer implemented, + * we can reuse the current to_compile flags */ + ly_temp_log_options(&temp_lo); + ret = lys_compile_depset_all(ctx, &ctx->unres); + ly_temp_log_options(NULL); + if (ret) { + LOGINT(ctx); + } + } +} + +void +lys_unres_glob_erase(struct lys_glob_unres *unres) +{ + uint32_t i; + + for (i = 0; i < unres->dep_sets.count; ++i) { + ly_set_free(unres->dep_sets.objs[i], NULL); + } + ly_set_erase(&unres->dep_sets, NULL); + ly_set_erase(&unres->implementing, NULL); + ly_set_erase(&unres->creating, NULL); + + assert(!unres->ds_unres.whens.count); + assert(!unres->ds_unres.musts.count); + assert(!unres->ds_unres.leafrefs.count); + assert(!unres->ds_unres.disabled_leafrefs.count); + assert(!unres->ds_unres.dflts.count); + assert(!unres->ds_unres.disabled.count); +} + +LIBYANG_API_DEF LY_ERR +lys_set_implemented(struct lys_module *mod, const char **features) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_glob_unres *unres = &mod->ctx->unres; + + LY_CHECK_ARG_RET(NULL, mod, LY_EINVAL); + + /* implement */ + ret = _lys_set_implemented(mod, features, unres); + LY_CHECK_GOTO(ret, cleanup); + + if (!(mod->ctx->flags & LY_CTX_EXPLICIT_COMPILE)) { + /* create dep set for the module and mark all the modules that will be (re)compiled */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create(mod->ctx, &unres->dep_sets, mod), cleanup); + + /* (re)compile the whole dep set (other dep sets will have no modules marked for compilation) */ + LY_CHECK_GOTO(ret = lys_compile_depset_all(mod->ctx, unres), cleanup); + + /* unres resolved */ + lys_unres_glob_erase(unres); + } + +cleanup: + if (ret) { + lys_unres_glob_revert(mod->ctx, unres); + lys_unres_glob_erase(unres); + } + return ret; +} + +/** + * @brief Resolve (find) all imported and included modules. + * + * @param[in] pctx Parser context. + * @param[in] pmod Parsed module to resolve. + * @param[out] new_mods Set with all the newly loaded modules. + * @return LY_ERR value. + */ +static LY_ERR +lysp_resolve_import_include(struct lysp_ctx *pctx, struct lysp_module *pmod, struct ly_set *new_mods) +{ + struct lysp_import *imp; + LY_ARRAY_COUNT_TYPE u, v; + + pmod->parsing = 1; + LY_ARRAY_FOR(pmod->imports, u) { + imp = &pmod->imports[u]; + if (!imp->module) { + LY_CHECK_RET(lys_parse_load(PARSER_CTX(pctx), imp->name, imp->rev[0] ? imp->rev : NULL, new_mods, &imp->module)); + + if (!imp->rev[0]) { + /* This module must be selected for the next similar + * import without revision-date to avoid incorrect + * derived identities in the ::lys_module.identities. + */ + imp->module->latest_revision |= LYS_MOD_IMPORTED_REV; + } + } + /* check for importing the same module twice */ + for (v = 0; v < u; ++v) { + if (imp->module == pmod->imports[v].module) { + LOGWRN(PARSER_CTX(pctx), "Single revision of the module \"%s\" imported twice.", imp->name); + } + } + } + LY_CHECK_RET(lysp_load_submodules(pctx, pmod, new_mods)); + + pmod->parsing = 0; + + return LY_SUCCESS; +} + +/** + * @brief Generate path of the given paresed node. + * + * @param[in] node Schema path of this node will be generated. + * @param[in] parent Build relative path only until this parent is found. If NULL, the full absolute path is printed. + * @return NULL in case of memory allocation error, path of the node otherwise. + * In case the @p buffer is NULL, the returned string is dynamically allocated and caller is responsible to free it. + */ +static char * +lysp_path_until(const struct lysp_node *node, const struct lysp_node *parent, const struct lysp_module *pmod) +{ + const struct lysp_node *iter, *par; + char *path = NULL, *s; + const char *slash; + int len = 0; + + for (iter = node; iter && (iter != parent) && (len >= 0); iter = iter->parent) { + if (parent && (iter->parent == parent)) { + slash = ""; + } else { + slash = "/"; + } + + s = path; + par = iter->parent; + if (!par) { + /* print prefix */ + len = asprintf(&path, "%s%s:%s%s", slash, pmod->mod->name, iter->name, s ? s : ""); + } else { + /* prefix is the same as in parent */ + len = asprintf(&path, "%s%s%s", slash, iter->name, s ? s : ""); + } + free(s); + } + + if (len < 0) { + free(path); + path = NULL; + } else if (len == 0) { + path = strdup("/"); + } + + return path; +} + +/** + * @brief Build log path for a parsed extension instance. + * + * @param[in] pcxt Parse context. + * @param[in] ext Parsed extension instance. + * @param[out] path Generated path. + * @return LY_ERR value. + */ +static LY_ERR +lysp_resolve_ext_instance_log_path(const struct lysp_ctx *pctx, const struct lysp_ext_instance *ext, char **path) +{ + char *buf = NULL; + uint32_t used = 0, size = 0; + + if (ext->parent_stmt & LY_STMT_NODE_MASK) { + /* parsed node path */ + buf = lysp_path_until(ext->parent, NULL, PARSER_CUR_PMOD(pctx)); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + size = used = strlen(buf); + + /* slash */ + size += 1; + buf = realloc(buf, size + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + used += sprintf(buf + used, "/"); + } else { + /* module */ + size += 1 + strlen(PARSER_CUR_PMOD(pctx)->mod->name) + 1; + buf = realloc(buf, size + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + used += sprintf(buf + used, "/%s:", PARSER_CUR_PMOD(pctx)->mod->name); + } + + /* extension name */ + size += 12 + strlen(ext->name) + 2; + buf = realloc(buf, size + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + used += sprintf(buf + used, "{extension='%s'}", ext->name); + + /* extension argument */ + if (ext->argument) { + size += 1 + strlen(ext->argument); + buf = realloc(buf, size + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(PARSER_CTX(pctx)), LY_EMEM); + used += sprintf(buf + used, "/%s", ext->argument); + } + + *path = buf; + return LY_SUCCESS; +} + +/** + * @brief Resolve (find) all extension instance records and finish their parsing. + * + * @param[in] pctx Parse context with all the parsed extension instances. + * @return LY_ERR value. + */ +static LY_ERR +lysp_resolve_ext_instance_records(struct lysp_ctx *pctx) +{ + LY_ERR r; + struct lysf_ctx fctx = {.ctx = PARSER_CTX(pctx)}; + struct lysp_ext_instance *exts, *ext; + const struct lys_module *mod; + uint32_t i; + LY_ARRAY_COUNT_TYPE u; + char *path = NULL; + + /* first finish parsing all extension instances ... */ + for (i = 0; i < pctx->ext_inst.count; ++i) { + exts = pctx->ext_inst.objs[i]; + LY_ARRAY_FOR(exts, u) { + ext = &exts[u]; + + /* find the extension definition */ + LY_CHECK_RET(lysp_ext_find_definition(PARSER_CTX(pctx), ext, &mod, &ext->def)); + + /* resolve the argument, if needed */ + LY_CHECK_RET(lysp_ext_instance_resolve_argument(PARSER_CTX(pctx), ext)); + + /* find the extension record, if any */ + ext->record = lyplg_ext_record_find(mod->name, mod->revision, ext->def->name); + } + } + + /* ... then call the parse callback */ + for (i = 0; i < pctx->ext_inst.count; ++i) { + exts = pctx->ext_inst.objs[i]; + u = 0; + while (u < LY_ARRAY_COUNT(exts)) { + ext = &exts[u]; + if (!ext->record || !ext->record->plugin.parse) { + goto next_iter; + } + + /* set up log path */ + if ((r = lysp_resolve_ext_instance_log_path(pctx, ext, &path))) { + return r; + } + LOG_LOCSET(NULL, NULL, path, NULL); + + /* parse */ + r = ext->record->plugin.parse(pctx, ext); + + LOG_LOCBACK(0, 0, 1, 0); + free(path); + + if (r == LY_ENOT) { + /* instance should be ignored, remove it */ + lysp_ext_instance_free(&fctx, ext); + LY_ARRAY_DECREMENT(exts); + if (u < LY_ARRAY_COUNT(exts)) { + /* replace by the last item */ + *ext = exts[LY_ARRAY_COUNT(exts)]; + } /* else if there are no more items, leave the empty array, we are not able to free it */ + continue; + } else if (r) { + /* error */ + return r; + } + +next_iter: + ++u; + } + } + + return LY_SUCCESS; +} + +LY_ERR +lys_parse_submodule(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, struct lysp_ctx *main_ctx, + LY_ERR (*custom_check)(const struct ly_ctx *, struct lysp_module *, struct lysp_submodule *, void *), + void *check_data, struct ly_set *new_mods, struct lysp_submodule **submodule) +{ + LY_ERR ret; + struct lysp_submodule *submod = NULL, *latest_sp; + struct lysp_yang_ctx *yangctx = NULL; + struct lysp_yin_ctx *yinctx = NULL; + struct lysp_ctx *pctx; + struct lysf_ctx fctx = {.ctx = ctx}; + + LY_CHECK_ARG_RET(ctx, ctx, in, LY_EINVAL); + + switch (format) { + case LYS_IN_YIN: + ret = yin_parse_submodule(&yinctx, ctx, main_ctx, in, &submod); + pctx = (struct lysp_ctx *)yinctx; + break; + case LYS_IN_YANG: + ret = yang_parse_submodule(&yangctx, ctx, main_ctx, in, &submod); + pctx = (struct lysp_ctx *)yangctx; + break; + default: + LOGERR(ctx, LY_EINVAL, "Invalid schema input format."); + ret = LY_EINVAL; + break; + } + LY_CHECK_GOTO(ret, error); + assert(submod); + + /* make sure that the newest revision is at position 0 */ + lysp_sort_revisions(submod->revs); + + /* decide the latest revision */ + latest_sp = (struct lysp_submodule *)ly_ctx_get_submodule2_latest(submod->mod, submod->name); + if (latest_sp) { + if (submod->revs) { + if (!latest_sp->revs) { + /* latest has no revision, so mod is anyway newer */ + submod->latest_revision = latest_sp->latest_revision; + /* the latest_sp is zeroed later when the new module is being inserted into the context */ + } else if (strcmp(submod->revs[0].date, latest_sp->revs[0].date) > 0) { + submod->latest_revision = latest_sp->latest_revision; + /* the latest_sp is zeroed later when the new module is being inserted into the context */ + } else { + latest_sp = NULL; + } + } else { + latest_sp = NULL; + } + } else { + submod->latest_revision = 1; + } + + if (custom_check) { + LY_CHECK_GOTO(ret = custom_check(ctx, NULL, submod, check_data), error); + } + + if (latest_sp) { + latest_sp->latest_revision = 0; + } + + lys_parser_fill_filepath(ctx, in, &submod->filepath); + + /* resolve imports and includes */ + LY_CHECK_GOTO(ret = lysp_resolve_import_include(pctx, (struct lysp_module *)submod, new_mods), error); + + if (format == LYS_IN_YANG) { + lysp_yang_ctx_free(yangctx); + } else { + lysp_yin_ctx_free(yinctx); + } + *submodule = submod; + return LY_SUCCESS; + +error: + if (!submod || !submod->name) { + LOGERR(ctx, ret, "Parsing submodule failed."); + } else { + LOGERR(ctx, ret, "Parsing submodule \"%s\" failed.", submod->name); + } + lysp_module_free(&fctx, (struct lysp_module *)submod); + if (format == LYS_IN_YANG) { + lysp_yang_ctx_free(yangctx); + } else { + lysp_yin_ctx_free(yinctx); + } + return ret; +} + +/** + * @brief Add ietf-netconf metadata to the parsed module. Operation, filter, and select are added. + * + * @param[in] pctx Parse context. + * @param[in] mod Parsed module to add to. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +static LY_ERR +lysp_add_internal_ietf_netconf(struct lysp_ctx *pctx, struct lysp_module *mod) +{ + struct lysp_ext_instance *extp; + struct lysp_stmt *stmt; + struct lysp_import *imp; + + /* + * 1) edit-config's operation + */ + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->exts, extp, LY_EMEM); + LY_CHECK_ERR_RET(!extp, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "md_:annotation", 0, &extp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "operation", 0, &extp->argument)); + extp->format = LY_VALUE_SCHEMA; + extp->prefix_data = mod; + extp->parent = mod; + extp->parent_stmt = LY_STMT_MODULE; + extp->flags = LYS_INTERNAL; + + extp->child = stmt = calloc(1, sizeof *extp->child); + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "type", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enumeration", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_TYPE; + + stmt->child = calloc(1, sizeof *stmt->child); + stmt = stmt->child; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "merge", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + stmt->next = calloc(1, sizeof *stmt->child); + stmt = stmt->next; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "replace", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + stmt->next = calloc(1, sizeof *stmt->child); + stmt = stmt->next; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "create", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + stmt->next = calloc(1, sizeof *stmt->child); + stmt = stmt->next; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "delete", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + stmt->next = calloc(1, sizeof *stmt->child); + stmt = stmt->next; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "remove", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + /* + * 2) filter's type + */ + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->exts, extp, LY_EMEM); + LY_CHECK_ERR_RET(!extp, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "md_:annotation", 0, &extp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "type", 0, &extp->argument)); + extp->format = LY_VALUE_SCHEMA; + extp->prefix_data = mod; + extp->parent = mod; + extp->parent_stmt = LY_STMT_MODULE; + extp->flags = LYS_INTERNAL; + + extp->child = stmt = calloc(1, sizeof *extp->child); + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "type", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enumeration", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_TYPE; + + stmt->child = calloc(1, sizeof *stmt->child); + stmt = stmt->child; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "subtree", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + stmt->next = calloc(1, sizeof *stmt->child); + stmt = stmt->next; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "enum", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "xpath", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_ENUM; + + /* if-feature for enum allowed only for YANG 1.1 modules */ + if (mod->version >= LYS_VERSION_1_1) { + stmt->child = calloc(1, sizeof *stmt->child); + stmt = stmt->child; + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "if-feature", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "xpath", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_IF_FEATURE; + } + + /* + * 3) filter's select + */ + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->exts, extp, LY_EMEM); + LY_CHECK_ERR_RET(!extp, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "md_:annotation", 0, &extp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "select", 0, &extp->argument)); + extp->format = LY_VALUE_SCHEMA; + extp->prefix_data = mod; + extp->parent = mod; + extp->parent_stmt = LY_STMT_MODULE; + extp->flags = LYS_INTERNAL; + + extp->child = stmt = calloc(1, sizeof *extp->child); + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "type", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "yang_:xpath1.0", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_TYPE; + + if (LY_ARRAY_COUNT(mod->exts) == 3) { + /* first extension instances */ + assert(pctx->main_ctx == pctx); + LY_CHECK_RET(ly_set_add(&pctx->ext_inst, mod->exts, 1, NULL)); + } + + /* create new imports for the used prefixes */ + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->imports, imp, LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "ietf-yang-metadata", 0, &imp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "md_", 0, &imp->prefix)); + imp->flags = LYS_INTERNAL; + + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->imports, imp, LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "ietf-yang-types", 0, &imp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "yang_", 0, &imp->prefix)); + imp->flags = LYS_INTERNAL; + + return LY_SUCCESS; +} + +/** + * @brief Add ietf-netconf-with-defaults "default" metadata to the parsed module. + * + * @param[in] pctx Parse context. + * @param[in] mod Parsed module to add to. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +static LY_ERR +lysp_add_internal_ietf_netconf_with_defaults(struct lysp_ctx *pctx, struct lysp_module *mod) +{ + struct lysp_ext_instance *extp; + struct lysp_stmt *stmt; + struct lysp_import *imp; + + /* add new extension instance */ + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->exts, extp, LY_EMEM); + + /* fill in the extension instance fields */ + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "md_:annotation", 0, &extp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "default", 0, &extp->argument)); + extp->format = LY_VALUE_SCHEMA; + extp->prefix_data = mod; + extp->parent = mod; + extp->parent_stmt = LY_STMT_MODULE; + extp->flags = LYS_INTERNAL; + + extp->child = stmt = calloc(1, sizeof *extp->child); + LY_CHECK_ERR_RET(!stmt, LOGMEM(mod->mod->ctx), LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "type", 0, &stmt->stmt)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "boolean", 0, &stmt->arg)); + stmt->format = LY_VALUE_SCHEMA; + stmt->prefix_data = mod; + stmt->kw = LY_STMT_TYPE; + + if (LY_ARRAY_COUNT(mod->exts) == 1) { + /* first extension instance */ + assert(pctx->main_ctx == pctx); + LY_CHECK_RET(ly_set_add(&pctx->ext_inst, mod->exts, 1, NULL)); + } + + /* create new import for the used prefix */ + LY_ARRAY_NEW_RET(mod->mod->ctx, mod->imports, imp, LY_EMEM); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "ietf-yang-metadata", 0, &imp->name)); + LY_CHECK_RET(lydict_insert(mod->mod->ctx, "md_", 0, &imp->prefix)); + imp->flags = LYS_INTERNAL; + + return LY_SUCCESS; +} + +LY_ERR +lys_parse_in(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, + LY_ERR (*custom_check)(const struct ly_ctx *ctx, struct lysp_module *mod, struct lysp_submodule *submod, void *data), + void *check_data, struct ly_set *new_mods, struct lys_module **module) +{ + struct lys_module *mod = NULL, *latest, *mod_dup = NULL; + LY_ERR ret; + struct lysp_yang_ctx *yangctx = NULL; + struct lysp_yin_ctx *yinctx = NULL; + struct lysp_ctx *pctx = NULL; + struct lysf_ctx fctx = {.ctx = ctx}; + char *filename, *rev, *dot; + size_t len; + ly_bool module_created = 0; + + assert(ctx && in && new_mods); + + if (module) { + *module = NULL; + } + + mod = calloc(1, sizeof *mod); + LY_CHECK_ERR_RET(!mod, LOGMEM(ctx), LY_EMEM); + mod->ctx = ctx; + + /* parse */ + switch (format) { + case LYS_IN_YIN: + ret = yin_parse_module(&yinctx, in, mod); + pctx = (struct lysp_ctx *)yinctx; + break; + case LYS_IN_YANG: + ret = yang_parse_module(&yangctx, in, mod); + pctx = (struct lysp_ctx *)yangctx; + break; + default: + LOGERR(ctx, LY_EINVAL, "Invalid schema input format."); + ret = LY_EINVAL; + break; + } + LY_CHECK_GOTO(ret, cleanup); + + /* make sure that the newest revision is at position 0 */ + lysp_sort_revisions(mod->parsed->revs); + if (mod->parsed->revs) { + LY_CHECK_GOTO(ret = lydict_insert(ctx, mod->parsed->revs[0].date, 0, &mod->revision), cleanup); + } + + /* decide the latest revision */ + latest = ly_ctx_get_module_latest(ctx, mod->name); + if (latest) { + if (mod->revision) { + if (!latest->revision) { + /* latest has no revision, so mod is anyway newer */ + mod->latest_revision = latest->latest_revision & (LYS_MOD_LATEST_REV | LYS_MOD_LATEST_SEARCHDIRS); + /* the latest is zeroed later when the new module is being inserted into the context */ + } else if (strcmp(mod->revision, latest->revision) > 0) { + mod->latest_revision = latest->latest_revision & (LYS_MOD_LATEST_REV | LYS_MOD_LATEST_SEARCHDIRS); + /* the latest is zeroed later when the new module is being inserted into the context */ + } else { + latest = NULL; + } + } else { + latest = NULL; + } + } else { + mod->latest_revision = LYS_MOD_LATEST_REV; + } + + if (custom_check) { + LY_CHECK_GOTO(ret = custom_check(ctx, mod->parsed, NULL, check_data), cleanup); + } + + /* check whether it is not already in the context in the same revision */ + mod_dup = ly_ctx_get_module(ctx, mod->name, mod->revision); + if (mod_dup) { + /* nothing to do */ + LOGVRB("Module \"%s@%s\" is already present in the context.", mod_dup->name, + mod_dup->revision ? mod_dup->revision : ""); + goto cleanup; + } + + /* check whether there is not a namespace collision */ + mod_dup = ly_ctx_get_module_latest_ns(ctx, mod->ns); + if (mod_dup && (mod_dup->revision == mod->revision)) { + LOGERR(ctx, LY_EINVAL, "Two different modules (\"%s\" and \"%s\") have the same namespace \"%s\".", + mod_dup->name, mod->name, mod->ns); + ret = LY_EINVAL; + goto cleanup; + } + + switch (in->type) { + case LY_IN_FILEPATH: + /* check that name and revision match filename */ + filename = strrchr(in->method.fpath.filepath, '/'); + if (!filename) { + filename = in->method.fpath.filepath; + } else { + filename++; + } + rev = strchr(filename, '@'); + dot = strrchr(filename, '.'); + + /* name */ + len = strlen(mod->name); + if (strncmp(filename, mod->name, len) || + ((rev && (rev != &filename[len])) || (!rev && (dot != &filename[len])))) { + LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, mod->name); + } + if (rev) { + len = dot - ++rev; + if (!mod->parsed->revs || (len != LY_REV_SIZE - 1) || strncmp(mod->parsed->revs[0].date, rev, len)) { + LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename, + mod->parsed->revs ? mod->parsed->revs[0].date : "none"); + } + } + + break; + case LY_IN_FD: + case LY_IN_FILE: + case LY_IN_MEMORY: + /* nothing special to do */ + break; + case LY_IN_ERROR: + LOGINT(ctx); + ret = LY_EINT; + goto cleanup; + } + lys_parser_fill_filepath(ctx, in, &mod->filepath); + + if (latest) { + latest->latest_revision &= ~(LYS_MOD_LATEST_REV | LYS_MOD_LATEST_SEARCHDIRS); + } + + /* add internal data in case specific modules were parsed */ + if (!strcmp(mod->name, "ietf-netconf")) { + LY_CHECK_GOTO(ret = lysp_add_internal_ietf_netconf(pctx, mod->parsed), cleanup); + } else if (!strcmp(mod->name, "ietf-netconf-with-defaults")) { + LY_CHECK_GOTO(ret = lysp_add_internal_ietf_netconf_with_defaults(pctx, mod->parsed), cleanup); + } + + /* add the module into newly created module set, will also be freed from there on any error */ + LY_CHECK_GOTO(ret = ly_set_add(new_mods, mod, 1, NULL), cleanup); + module_created = 1; + + /* add into context */ + ret = ly_set_add(&ctx->list, mod, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + ctx->change_count++; + + /* resolve includes and all imports */ + LY_CHECK_GOTO(ret = lysp_resolve_import_include(pctx, mod->parsed, new_mods), cleanup); + + /* resolve extension instance plugin records */ + LY_CHECK_GOTO(ret = lysp_resolve_ext_instance_records(pctx), cleanup); + + /* check name collisions */ + LY_CHECK_GOTO(ret = lysp_check_dup_typedefs(pctx, mod->parsed), cleanup); + LY_CHECK_GOTO(ret = lysp_check_dup_groupings(pctx, mod->parsed), cleanup); + LY_CHECK_GOTO(ret = lysp_check_dup_features(pctx, mod->parsed), cleanup); + LY_CHECK_GOTO(ret = lysp_check_dup_identities(pctx, mod->parsed), cleanup); + + /* compile features */ + LY_CHECK_GOTO(ret = lys_compile_feature_iffeatures(mod->parsed), cleanup); + + /* compile identities */ + LY_CHECK_GOTO(ret = lys_compile_identities(mod), cleanup); + +cleanup: + if (ret && (ret != LY_EEXIST)) { + if (mod && mod->name) { + /* there are cases when path is not available for parsing error, so this additional + * message tries to add information about the module where the error occurred */ + struct ly_err_item *e = ly_err_last(ctx); + + if (e && (!e->path || !strncmp(e->path, "Line ", ly_strlen_const("Line ")))) { + LOGERR(ctx, ret, "Parsing module \"%s\" failed.", mod->name); + } + } + } + if (!module_created) { + fctx.mod = mod; + lys_module_free(&fctx, mod, 0); + lysf_ctx_erase(&fctx); + + mod = mod_dup; + } + + if (format == LYS_IN_YANG) { + lysp_yang_ctx_free(yangctx); + } else { + lysp_yin_ctx_free(yinctx); + } + + if (!ret && module) { + *module = mod; + } + return ret; +} + +static LYS_INFORMAT +lys_parse_get_format(const struct ly_in *in, LYS_INFORMAT format) +{ + if (!format && (in->type == LY_IN_FILEPATH)) { + /* unknown format - try to detect it from filename's suffix */ + const char *path = in->method.fpath.filepath; + size_t len = strlen(path); + + /* ignore trailing whitespaces */ + for ( ; len > 0 && isspace(path[len - 1]); len--) {} + + if ((len >= LY_YANG_SUFFIX_LEN + 1) && + !strncmp(&path[len - LY_YANG_SUFFIX_LEN], LY_YANG_SUFFIX, LY_YANG_SUFFIX_LEN)) { + format = LYS_IN_YANG; + } else if ((len >= LY_YIN_SUFFIX_LEN + 1) && + !strncmp(&path[len - LY_YIN_SUFFIX_LEN], LY_YIN_SUFFIX, LY_YIN_SUFFIX_LEN)) { + format = LYS_IN_YIN; + } /* else still unknown */ + } + + return format; +} + +LIBYANG_API_DEF LY_ERR +lys_parse(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, const char **features, struct lys_module **module) +{ + LY_ERR ret = LY_SUCCESS; + struct lys_module *mod; + + if (module) { + *module = NULL; + } + LY_CHECK_ARG_RET(NULL, ctx, in, LY_EINVAL); + + format = lys_parse_get_format(in, format); + LY_CHECK_ARG_RET(ctx, format, LY_EINVAL); + + /* remember input position */ + in->func_start = in->current; + + /* parse */ + ret = lys_parse_in(ctx, in, format, NULL, NULL, &ctx->unres.creating, &mod); + LY_CHECK_GOTO(ret, cleanup); + + /* implement */ + ret = _lys_set_implemented(mod, features, &ctx->unres); + LY_CHECK_GOTO(ret, cleanup); + + if (!(ctx->flags & LY_CTX_EXPLICIT_COMPILE)) { + /* create dep set for the module and mark all the modules that will be (re)compiled */ + LY_CHECK_GOTO(ret = lys_unres_dep_sets_create(ctx, &ctx->unres.dep_sets, mod), cleanup); + + /* (re)compile the whole dep set (other dep sets will have no modules marked for compilation) */ + LY_CHECK_GOTO(ret = lys_compile_depset_all(ctx, &ctx->unres), cleanup); + + /* unres resolved */ + lys_unres_glob_erase(&ctx->unres); + } + +cleanup: + if (ret) { + lys_unres_glob_revert(ctx, &ctx->unres); + lys_unres_glob_erase(&ctx->unres); + } else if (module) { + *module = mod; + } + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_parse_mem(struct ly_ctx *ctx, const char *data, LYS_INFORMAT format, struct lys_module **module) +{ + LY_ERR ret; + struct ly_in *in = NULL; + + LY_CHECK_ARG_RET(ctx, data, format != LYS_IN_UNKNOWN, LY_EINVAL); + + LY_CHECK_ERR_RET(ret = ly_in_new_memory(data, &in), LOGERR(ctx, ret, "Unable to create input handler."), ret); + + ret = lys_parse(ctx, in, format, NULL, module); + ly_in_free(in, 0); + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_parse_fd(struct ly_ctx *ctx, int fd, LYS_INFORMAT format, struct lys_module **module) +{ + LY_ERR ret; + struct ly_in *in = NULL; + + LY_CHECK_ARG_RET(ctx, fd > -1, format != LYS_IN_UNKNOWN, LY_EINVAL); + + LY_CHECK_ERR_RET(ret = ly_in_new_fd(fd, &in), LOGERR(ctx, ret, "Unable to create input handler."), ret); + + ret = lys_parse(ctx, in, format, NULL, module); + ly_in_free(in, 0); + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_parse_path(struct ly_ctx *ctx, const char *path, LYS_INFORMAT format, struct lys_module **module) +{ + LY_ERR ret; + struct ly_in *in = NULL; + + LY_CHECK_ARG_RET(ctx, path, format != LYS_IN_UNKNOWN, LY_EINVAL); + + LY_CHECK_ERR_RET(ret = ly_in_new_filepath(path, 0, &in), + LOGERR(ctx, ret, "Unable to create input handler for filepath %s.", path), ret); + + ret = lys_parse(ctx, in, format, NULL, module); + ly_in_free(in, 0); + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lys_search_localfile(const char * const *searchpaths, ly_bool cwd, const char *name, const char *revision, + char **localfile, LYS_INFORMAT *format) +{ + LY_ERR ret = LY_EMEM; + size_t len, flen, match_len = 0, dir_len; + ly_bool implicit_cwd = 0; + char *wd, *wn = NULL; + DIR *dir = NULL; + struct dirent *file; + char *match_name = NULL; + LYS_INFORMAT format_aux, match_format = 0; + struct ly_set *dirs; + struct stat st; + + LY_CHECK_ARG_RET(NULL, localfile, LY_EINVAL); + + /* start to fill the dir fifo with the context's search path (if set) + * and the current working directory */ + LY_CHECK_RET(ly_set_new(&dirs)); + + len = strlen(name); + if (cwd) { + wd = get_current_dir_name(); + if (!wd) { + LOGMEM(NULL); + goto cleanup; + } else { + /* add implicit current working directory (./) to be searched, + * this directory is not searched recursively */ + ret = ly_set_add(dirs, wd, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + implicit_cwd = 1; + } + } + if (searchpaths) { + for (uint64_t i = 0; searchpaths[i]; i++) { + /* check for duplicities with the implicit current working directory */ + if (implicit_cwd && !strcmp(dirs->objs[0], searchpaths[i])) { + implicit_cwd = 0; + continue; + } + wd = strdup(searchpaths[i]); + if (!wd) { + LOGMEM(NULL); + goto cleanup; + } else { + ret = ly_set_add(dirs, wd, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + } + wd = NULL; + + /* start searching */ + while (dirs->count) { + free(wd); + free(wn); wn = NULL; + + dirs->count--; + wd = (char *)dirs->objs[dirs->count]; + dirs->objs[dirs->count] = NULL; + LOGVRB("Searching for \"%s\" in \"%s\".", name, wd); + + if (dir) { + closedir(dir); + } + dir = opendir(wd); + dir_len = strlen(wd); + if (!dir) { + LOGWRN(NULL, "Unable to open directory \"%s\" for searching (sub)modules (%s).", wd, strerror(errno)); + } else { + while ((file = readdir(dir))) { + if (!strcmp(".", file->d_name) || !strcmp("..", file->d_name)) { + /* skip . and .. */ + continue; + } + free(wn); + if (asprintf(&wn, "%s/%s", wd, file->d_name) == -1) { + LOGMEM(NULL); + goto cleanup; + } + if (stat(wn, &st) == -1) { + LOGWRN(NULL, "Unable to get information about \"%s\" file in \"%s\" when searching for (sub)modules (%s)", + file->d_name, wd, strerror(errno)); + continue; + } + if (S_ISDIR(st.st_mode) && (dirs->count || !implicit_cwd)) { + /* we have another subdirectory in searchpath to explore, + * subdirectories are not taken into account in current working dir (dirs->set.g[0]) */ + ret = ly_set_add(dirs, wn, 0, NULL); + LY_CHECK_GOTO(ret, cleanup); + + /* continue with the next item in current directory */ + wn = NULL; + continue; + } else if (!S_ISREG(st.st_mode)) { + /* not a regular file (note that we see the target of symlinks instead of symlinks */ + continue; + } + + /* here we know that the item is a file which can contain a module */ + if (strncmp(name, file->d_name, len) || + ((file->d_name[len] != '.') && (file->d_name[len] != '@'))) { + /* different filename than the module we search for */ + continue; + } + + /* get type according to filename suffix */ + flen = strlen(file->d_name); + if ((flen >= LY_YANG_SUFFIX_LEN + 1) && + !strcmp(&file->d_name[flen - LY_YANG_SUFFIX_LEN], LY_YANG_SUFFIX)) { + format_aux = LYS_IN_YANG; + } else if ((flen >= LY_YIN_SUFFIX_LEN + 1) && + !strcmp(&file->d_name[flen - LY_YIN_SUFFIX_LEN], LY_YIN_SUFFIX)) { + format_aux = LYS_IN_YIN; + } else { + /* not supportde suffix/file format */ + continue; + } + + if (revision) { + /* we look for the specific revision, try to get it from the filename */ + if (file->d_name[len] == '@') { + /* check revision from the filename */ + if (strncmp(revision, &file->d_name[len + 1], strlen(revision))) { + /* another revision */ + continue; + } else { + /* exact revision */ + free(match_name); + match_name = wn; + wn = NULL; + match_len = dir_len + 1 + len; + match_format = format_aux; + goto success; + } + } else { + /* continue trying to find exact revision match, use this only if not found */ + free(match_name); + match_name = wn; + wn = NULL; + match_len = dir_len + 1 + len; + match_format = format_aux; + continue; + } + } else { + /* remember the revision and try to find the newest one */ + if (match_name) { + if ((file->d_name[len] != '@') || + lysp_check_date(NULL, &file->d_name[len + 1], + flen - ((format_aux == LYS_IN_YANG) ? LY_YANG_SUFFIX_LEN : LY_YIN_SUFFIX_LEN) - len - 1, NULL)) { + continue; + } else if ((match_name[match_len] == '@') && + (strncmp(&match_name[match_len + 1], &file->d_name[len + 1], LY_REV_SIZE - 1) >= 0)) { + continue; + } + free(match_name); + } + + match_name = wn; + wn = NULL; + match_len = dir_len + 1 + len; + match_format = format_aux; + continue; + } + } + } + } + +success: + (*localfile) = match_name; + match_name = NULL; + if (format) { + (*format) = match_format; + } + ret = LY_SUCCESS; + +cleanup: + free(wn); + free(wd); + if (dir) { + closedir(dir); + } + free(match_name); + ly_set_free(dirs, free); + + return ret; +} diff --git a/src/tree_schema.h b/src/tree_schema.h new file mode 100644 index 0000000..c57a0fc --- /dev/null +++ b/src/tree_schema.h @@ -0,0 +1,2248 @@ +/** + * @file tree_schema.h + * @author Radek Krejci + * @author Michal Vasko + * @brief libyang representation of YANG schema trees. + * + * Copyright (c) 2015 - 2022 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_TREE_SCHEMA_H_ +#define LY_TREE_SCHEMA_H_ + +#define PCRE2_CODE_UNIT_WIDTH 8 + +#include + +#include +#include + +#include "config.h" +#include "log.h" +#include "tree.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ly_ctx; +struct ly_path; +struct ly_set; +struct lys_module; +struct lysc_node; +struct lyxp_expr; + +/** + * @page howtoSchema YANG Modules + * + * To be able to work with YANG data instances, libyang has to represent YANG data models. All the processed modules are stored + * in libyang [context](@ref howtoContext) and loaded using [parser functions](@ref howtoSchemaParsers). It means, that there is + * no way to create/change YANG module programmatically. However, all the YANG model definitions are available and can be examined + * through the C structures. All the context's modules together form YANG Schema for the data being instantiated. + * + * Any YANG module is represented as ::lys_module. In fact, the module is represented in two different formats. As ::lys_module.parsed, + * there is a parsed schema reflecting the source YANG module. It is exactly what is read from the input. This format is good for + * converting from one format to another (YANG to YIN and vice versa), but it is not very useful for validating/manipulating YANG + * data. Therefore, there is ::lys_module.compiled storing the compiled YANG module. It is based on the parsed module, but all the + * references are resolved. It means that, for example, there are no `grouping`s or `typedef`s since they are supposed to be placed instead of + * `uses` or `type` references. This split also means, that the YANG module is fully validated after compilation of the parsed + * representation of the module. YANG submodules are available only in the parsed representation. When a submodule is compiled, it + * is fully integrated into its main module. + * + * The context can contain even modules without the compiled representation. Such modules are still useful as imports of other + * modules. The grouping or typedef definition can be even compiled into the importing modules. This is actually the main + * difference between the imported and implemented modules in the libyang context. The implemented modules are compiled while the + * imported modules are only parsed. + * + * By default, the module is implemented (and compiled) in case it is explicitly loaded or referenced in another module as + * target of leafref, augment or deviation. This behavior can be changed via context options ::LY_CTX_ALL_IMPLEMENTED, when + * all the modules in the context are marked as implemented (note the problem with multiple revisions of a single module), + * or by ::LY_CTX_REF_IMPLEMENTED option, extending the set of references making the module implemented by when, must and + * default statements. + * + * All modules with deviation definition are always marked as implemented. The imported (not implemented) module can be set implemented by ::lys_set_implemented(). But + * the implemented module cannot be changed back to just imported module. Note also that only one revision of a specific module + * can be implemented in a single context. The imported modules are used only as a + * source of definitions for types and groupings for uses statements. The data in such modules are ignored - caller + * is not allowed to create the data (including instantiating identities) defined in the model via data parsers, + * the default nodes are not added into any data tree and mandatory nodes are not checked in the data trees. + * + * The compiled schema tree nodes are able to hold private objects (::lysc_node.priv as a pointer to a structure, + * function, variable, ...) used by a caller application unless ::LY_CTX_SET_PRIV_PARSED is set, in that case + * the ::lysc_node.priv pointers are used by libyang. + * Note that the object is not freed by libyang when the context is being destroyed. So the caller is responsible + * for freeing the provided structure after the context is destroyed or the private pointer is set to NULL in + * appropriate schema nodes where the object was previously set. Also ::lysc_tree_dfs_full() can be useful to manage + * the private data. + * + * Despite all the schema structures and their members are available as part of the libyang API and callers can use + * it to navigate through the schema tree structure or to obtain various information, we recommend to use the following + * macros for the specific actions. + * - ::LYSC_TREE_DFS_BEGIN and ::LYSC_TREE_DFS_END to traverse the schema tree (depth-first). + * - ::LY_LIST_FOR and ::LY_ARRAY_FOR as described on @ref howtoStructures page. + * + * Further information about modules handling can be found on the following pages: + * - @subpage howtoSchemaParsers + * - @subpage howtoSchemaFeatures + * - @subpage howtoPlugins + * - @subpage howtoSchemaPrinters + * + * \note There are many functions to access information from the schema trees. Details are available in + * the [Schema Tree module](@ref schematree). + * + * For information about difference between implemented and imported modules, see the + * [context description](@ref howtoContext). + * + * Functions List (not assigned to above subsections) + * -------------------------------------------------- + * - ::lys_getnext() + * - ::lys_nodetype2str() + * - ::lys_set_implemented() + * + * - ::lysc_has_when() + * - ::lysc_owner_module() + * + * - ::lysc_node_child() + * - ::lysc_node_actions() + * - ::lysc_node_notifs() + * + * - ::lysp_node_child() + * - ::lysp_node_actions() + * - ::lysp_node_notifs() + * - ::lysp_node_groupings() + * - ::lysp_node_typedefs() + */ + +/** + * @page howtoSchemaFeatures YANG Features + * + * YANG feature statement is an important part of the language which can significantly affect the meaning of the schemas. + * Modifying features may have similar effects as loading/removing schema from the context so it is limited to context + * preparation period before working with data. YANG features, respectively their use in if-feature + * statements, are evaluated as part of schema compilation so a feature-specific compiled schema tree is generated + * as a result. + * + * To enable any features, they must currently be specified when implementing a new schema with ::lys_parse() or + * ::ly_ctx_load_module(). To later examine what the status of a feature is, check its ::LYS_FENABLED flag or + * search for it first with ::lys_feature_value(). Lastly, to evaluate compiled if-features, use ::lysc_iffeature_value(). + * + * To iterate over all features of a particular YANG module, use ::lysp_feature_next(). + * + * Note, that the feature's state can affect some of the output formats (e.g. Tree format). + * + * Functions List + * -------------- + * - ::lys_feature_value() + * - ::lysc_iffeature_value() + * - ::lysp_feature_next() + */ + +/** + * @ingroup trees + * @defgroup schematree Schema Tree + * @{ + * + * Data structures and functions to manipulate and access schema tree. + */ + +/* *INDENT-OFF* */ + +/** + * @brief Macro to iterate via all elements in a schema (sub)tree including input and output. + * Note that __actions__ and __notifications__ of traversed nodes __are ignored__! To traverse + * on all the nodes including those, use ::lysc_tree_dfs_full() instead. + * + * This is the opening part to the #LYSC_TREE_DFS_END - they always have to be used together. + * + * The function follows deep-first search algorithm: + *
+ *     1
+ *    / \
+ *   2   4
+ *  /   / \
+ * 3   5   6
+ * 
+ * + * Use the same parameters for #LYSC_TREE_DFS_BEGIN and #LYSC_TREE_DFS_END. While + * START can be any of the lysc_node* types (including lysc_node_action and lysc_node_notif), + * ELEM variable must be of the struct lysc_node* type. + * + * To skip a particular subtree, instead of the continue statement, set LYSC_TREE_DFS_continue + * variable to non-zero value. + * + * Use with opening curly bracket '{' after the macro. + * + * @param START Pointer to the starting element processed first. + * @param ELEM Iterator intended for use in the block. + */ +#define LYSC_TREE_DFS_BEGIN(START, ELEM) \ + { ly_bool LYSC_TREE_DFS_continue = 0; struct lysc_node *LYSC_TREE_DFS_next; \ + for ((ELEM) = (LYSC_TREE_DFS_next) = (struct lysc_node *)(START); \ + (ELEM); \ + (ELEM) = (LYSC_TREE_DFS_next), LYSC_TREE_DFS_continue = 0) + +/** + * @brief Macro to iterate via all elements in a (sub)tree. This is the closing part + * to the #LYSC_TREE_DFS_BEGIN - they always have to be used together. + * + * Use the same parameters for #LYSC_TREE_DFS_BEGIN and #LYSC_TREE_DFS_END. While + * START can be a pointer to any of the lysc_node* types (including lysc_node_action and lysc_node_notif), + * ELEM variable must be pointer to the lysc_node type. + * + * Use with closing curly bracket '}' after the macro. + * + * @param START Pointer to the starting element processed first. + * @param ELEM Iterator intended for use in the block. + */ +#define LYSC_TREE_DFS_END(START, ELEM) \ + /* select element for the next run - children first */ \ + if (LYSC_TREE_DFS_continue) { \ + (LYSC_TREE_DFS_next) = NULL; \ + } else { \ + (LYSC_TREE_DFS_next) = (struct lysc_node *)lysc_node_child(ELEM); \ + } \ + if (!(LYSC_TREE_DFS_next)) { \ + /* no children, try siblings */ \ + _LYSC_TREE_DFS_NEXT(START, ELEM, LYSC_TREE_DFS_next); \ + } \ + while (!(LYSC_TREE_DFS_next)) { \ + /* parent is already processed, go to its sibling */ \ + (ELEM) = (ELEM)->parent; \ + _LYSC_TREE_DFS_NEXT(START, ELEM, LYSC_TREE_DFS_next); \ + } } + +/** + * @brief Helper macro for #LYSC_TREE_DFS_END, should not be used directly! + */ +#define _LYSC_TREE_DFS_NEXT(START, ELEM, NEXT) \ + if ((ELEM) == (struct lysc_node *)(START)) { \ + /* we are done, no next element to process */ \ + break; \ + } \ + (NEXT) = (ELEM)->next; + +/* *INDENT-ON* */ + +#define LY_REV_SIZE 11 /**< revision data string length (including terminating NULL byte) */ + +/** + * @defgroup schemanodetypes Schema Node Types + * Values of the ::lysp_node.nodetype and ::lysc_node.nodetype members. + * @{ + */ +#define LYS_UNKNOWN 0x0000 /**< uninitalized unknown statement node */ +#define LYS_CONTAINER 0x0001 /**< container statement node */ +#define LYS_CHOICE 0x0002 /**< choice statement node */ +#define LYS_LEAF 0x0004 /**< leaf statement node */ +#define LYS_LEAFLIST 0x0008 /**< leaf-list statement node */ +#define LYS_LIST 0x0010 /**< list statement node */ +#define LYS_ANYXML 0x0020 /**< anyxml statement node */ +#define LYS_ANYDATA 0x0060 /**< anydata statement node, in tests it can be used for both #LYS_ANYXML and #LYS_ANYDATA */ +#define LYS_CASE 0x0080 /**< case statement node */ + +#define LYS_RPC 0x0100 /**< RPC statement node */ +#define LYS_ACTION 0x0200 /**< action statement node */ +#define LYS_NOTIF 0x0400 /**< notification statement node */ + +#define LYS_USES 0x0800 /**< uses statement node */ +#define LYS_INPUT 0x1000 /**< RPC/action input node */ +#define LYS_OUTPUT 0x2000 /**< RPC/action output node */ +#define LYS_GROUPING 0x4000 +#define LYS_AUGMENT 0x8000 + +#define LYS_NODETYPE_MASK 0xffff /**< Mask for nodetypes, the value is limited for 16 bits */ +/** @} schemanodetypes */ + +/** + * @brief YANG import-stmt + */ +struct lysp_import { + struct lys_module *module; /**< pointer to the imported module + (mandatory, but resolved when the referring module is completely parsed) */ + const char *name; /**< name of the imported module (mandatory) */ + const char *prefix; /**< prefix for the data from the imported schema (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< LYS_INTERNAL value (@ref snodeflags) */ + char rev[LY_REV_SIZE]; /**< revision-date of the imported module */ +}; + +/** + * @brief YANG include-stmt + */ +struct lysp_include { + struct lysp_submodule *submodule;/**< pointer to the parsed submodule structure + (mandatory, but resolved when the referring module is completely parsed) */ + const char *name; /**< name of the included submodule (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + char rev[LY_REV_SIZE]; /**< revision-date of the included submodule */ + ly_bool injected; /**< flag to mark includes copied into main module from submodules, + only for backward compatibility with YANG 1.0, which does not require the + main module to include all submodules. */ +}; + +/** + * @brief YANG extension-stmt + */ +struct lysp_ext { + const char *name; /**< extension name */ + const char *argname; /**< argument name, NULL if not specified */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< LYS_STATUS_* and LYS_YINELEM_* values (@ref snodeflags) */ + + struct lysc_ext *compiled; /**< pointer to the compiled extension definition. + The extension definition is compiled only if there is compiled extension instance, + otherwise this pointer remains NULL. The compiled extension definition is shared + among all extension instances. */ +}; + +/** + * @brief YANG feature-stmt + */ +struct lysp_feature { + const char *name; /**< feature name (mandatory) */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysc_iffeature *iffeatures_c; /**< compiled if-features */ + struct lysp_feature **depfeatures; /**< list of pointers to other features depending on this one + ([sized array](@ref sizedarrays)) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_* values and + LYS_FENABLED are allowed */ +}; + +/** + * @brief Compiled YANG if-feature-stmt + */ +struct lysc_iffeature { + uint8_t *expr; /**< 2bits array describing the if-feature expression in prefix format, see @ref ifftokens */ + struct lysp_feature **features; /**< array of pointers to the features used in expression ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief Qualified name (optional prefix followed by an identifier). + */ +struct lysp_qname { + const char *str; /**< qualified name string */ + const struct lysp_module *mod; /**< module to resolve any prefixes found in the string, it must be + stored explicitly because of deviations/refines */ +}; + +/** + * @brief YANG identity-stmt + */ +struct lysp_ident { + const char *name; /**< identity name (mandatory), including possible prefix */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + const char **bases; /**< list of base identifiers ([sized array](@ref sizedarrays)) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_ values are allowed */ +}; + +/** + * @brief Covers restrictions: range, length, pattern, must + */ +struct lysp_restr { +#define LYSP_RESTR_PATTERN_ACK 0x06 +#define LYSP_RESTR_PATTERN_NACK 0x15 + struct lysp_qname arg; /**< The restriction expression/value (mandatory); + in case of pattern restriction, the first byte has a special meaning: + 0x06 (ACK) for regular match and 0x15 (NACK) for invert-match */ + const char *emsg; /**< error-message */ + const char *eapptag; /**< error-app-tag value */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief YANG revision-stmt + */ +struct lysp_revision { + char date[LY_REV_SIZE]; /**< revision date (madatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief Enumeration/Bit value definition + */ +struct lysp_type_enum { + const char *name; /**< name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + int64_t value; /**< enum's value or bit's position */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_ and LYS_SET_VALUE + values are allowed */ +}; + +/** + * @brief YANG type-stmt + * + * Some of the items in the structure may be mandatory, but it is necessary to resolve the type's base type first + */ +struct lysp_type { + const char *name; /**< name of the type (mandatory) */ + struct lysp_restr *range; /**< allowed values range - numerical, decimal64 */ + struct lysp_restr *length; /**< allowed length of the value - string, binary */ + struct lysp_restr *patterns; /**< list of patterns ([sized array](@ref sizedarrays)) - string */ + struct lysp_type_enum *enums; /**< list of enum-stmts ([sized array](@ref sizedarrays)) - enum */ + struct lysp_type_enum *bits; /**< list of bit-stmts ([sized array](@ref sizedarrays)) - bits */ + struct lyxp_expr *path; /**< parsed path - leafref */ + const char **bases; /**< list of base identifiers ([sized array](@ref sizedarrays)) - identityref */ + struct lysp_type *types; /**< list of sub-types ([sized array](@ref sizedarrays)) - union */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + + const struct lysp_module *pmod; /**< (sub)module where the type is defined (needed for deviations) */ + struct lysc_type *compiled; /**< pointer to the compiled custom type, not used for built-in types */ + + uint8_t fraction_digits; /**< number of fraction digits - decimal64 */ + uint8_t require_instance; /**< require-instance flag - leafref, instance */ + uint16_t flags; /**< [schema node flags](@ref spnodeflags) */ +}; + +/** + * @brief YANG typedef-stmt + */ +struct lysp_tpdf { + const char *name; /**< name of the newly defined type (mandatory) */ + const char *units; /**< units of the newly defined type */ + struct lysp_qname dflt; /**< default value of the newly defined type, it may or may not be a qualified name */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lysp_type type; /**< base type from which the typedef is derived (mandatory) */ + uint16_t flags; /**< [schema node flags](@ref spnodeflags) */ +}; + +/** + * @brief YANG when-stmt + */ +struct lysp_when { + const char *cond; /**< specified condition (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief YANG refine-stmt + */ +struct lysp_refine { + const char *nodeid; /**< target descendant schema nodeid (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + const char *presence; /**< presence description */ + struct lysp_qname *dflts; /**< list of default values ([sized array](@ref sizedarrays)) */ + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint, 0 means unbounded */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ +}; + +/** + * @ingroup schematree + * @defgroup deviatetypes Deviate types + * + * Type of the deviate operation (used as ::lysp_deviate.mod) + * + * @{ + */ +#define LYS_DEV_NOT_SUPPORTED 1 /**< deviate type not-supported */ +#define LYS_DEV_ADD 2 /**< deviate type add */ +#define LYS_DEV_DELETE 3 /**< deviate type delete */ +#define LYS_DEV_REPLACE 4 /**< deviate type replace */ +/** @} deviatetypes */ + +/** + * @brief Generic deviate structure to get type and cast to lysp_deviate_* structure + */ +struct lysp_deviate { + uint8_t mod; /**< [type](@ref deviatetypes) of the deviate modification */ + struct lysp_deviate *next; /**< next deviate structure in the list */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +struct lysp_deviate_add { + uint8_t mod; /**< [type](@ref deviatetypes) of the deviate modification */ + struct lysp_deviate *next; /**< next deviate structure in the list */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + const char *units; /**< units of the values */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_qname *uniques; /**< list of uniques specifications ([sized array](@ref sizedarrays)) */ + struct lysp_qname *dflts; /**< list of default values ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint, 0 means unbounded */ +}; + +struct lysp_deviate_del { + uint8_t mod; /**< [type](@ref deviatetypes) of the deviate modification */ + struct lysp_deviate *next; /**< next deviate structure in the list */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + const char *units; /**< units of the values */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_qname *uniques; /**< list of uniques specifications ([sized array](@ref sizedarrays)) */ + struct lysp_qname *dflts; /**< list of default values ([sized array](@ref sizedarrays)) */ +}; + +struct lysp_deviate_rpl { + uint8_t mod; /**< [type](@ref deviatetypes) of the deviate modification */ + struct lysp_deviate *next; /**< next deviate structure in the list */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lysp_type *type; /**< type of the node */ + const char *units; /**< units of the values */ + struct lysp_qname dflt; /**< default value */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint, 0 means unbounded */ +}; + +struct lysp_deviation { + const char *nodeid; /**< target absolute schema nodeid (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_deviate *deviates; /**< list of deviate specifications (linked list) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +/** + * @ingroup snodeflags + * @defgroup spnodeflags Parsed schema nodes flags + * + * Various flags for parsed schema nodes (used as ::lysp_node.flags). + * + * 1 - container 6 - anydata/anyxml 11 - output 16 - grouping 21 - enum + * 2 - choice 7 - case 12 - feature 17 - uses 22 - type + * 3 - leaf 8 - notification 13 - identity 18 - refine 23 - stmt + * 4 - leaflist 9 - rpc 14 - extension 19 - augment + * 5 - list 10 - input 15 - typedef 20 - deviate + * + * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 + * bit name 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + * ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 1 LYS_CONFIG_W |x|x|x|x|x|x| | | | | | | | | | | |x| |x| | | | + * LYS_SET_BASE | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 2 LYS_CONFIG_R |x|x|x|x|x|x| | | | | | | | | | | |x| |x| | | | + * LYS_SET_BIT | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 3 LYS_STATUS_CURR |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| | | + * LYS_SET_ENUM | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 4 LYS_STATUS_DEPRC |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| | | + * LYS_SET_FRDIGITS | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 5 LYS_STATUS_OBSLT |x|x|x|x|x|x|x|x|x| | |x|x|x|x|x|x| |x|x|x| | | + * LYS_SET_LENGTH | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 6 LYS_MAND_TRUE | |x|x| | |x| | | | | | | | | | | |x| |x| | | | + * LYS_SET_PATH | | | | | | | | | | | | | | | | | | | | | |x| | + * LYS_FENABLED | | | | | | | | | | | |x| | | | | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 7 LYS_MAND_FALSE | |x|x| | |x| | | | | | | | | | | |x| |x| | | | + * LYS_ORDBY_USER | | | |x|x| | | | | | | | | | | | | | | | | | | + * LYS_SET_PATTERN | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 8 LYS_ORDBY_SYSTEM | | | |x|x| | | | | | | | | | | | | | | | | | | + * LYS_YINELEM_TRUE | | | | | | | | | | | | | |x| | | | | | | | | | + * LYS_SET_RANGE | | | | | | | | | | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 9 LYS_YINELEM_FALSE| | | | | | | | | | | | | |x| | | | | | | | | | + * LYS_SET_TYPE | | | | | | | | | | | | | | | | | | | | | |x| | + * LYS_SINGLEQUOTED | | | | | | | | | | | | | | | | | | | | | | |x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 10 LYS_SET_VALUE | | | | | | | | | | | | | | | | | | | | |x| | | + * LYS_SET_REQINST | | | | | | | | | | | | | | | | | | | | | |x| | + * LYS_SET_MIN | | | |x|x| | | | | | | | | | | | |x| |x| | | | + * LYS_DOUBLEQUOTED | | | | | | | | | | | | | | | | | | | | | | |x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 11 LYS_SET_MAX | | | |x|x| | | | | | | | | | | | |x| |x| | | | + * LYS_USED_GRP | | | | | | | | | | | | | | | |x| | | | | | | | + * LYS_YIN_ATTR | | | | | | | | | | | | | | | | | | | | | | |x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 12 LYS_YIN_ARGUMENT | | | | | | | | | | | | | | | | | | | | | | |x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 13 LYS_INTERNAL |x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x| + * ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +/** + * @ingroup snodeflags + * @defgroup scnodeflags Compiled schema nodes flags + * + * Various flags for compiled schema nodes (used as ::lysc_node.flags). + * + * 1 - container 6 - anydata/anyxml 11 - identity + * 2 - choice 7 - case 12 - extension + * 3 - leaf 8 - notification 13 - bitenum + * 4 - leaflist 9 - rpc/action 14 - when + * 5 - list 10 - feature + * + * 1 1 1 1 1 + * bit name 1 2 3 4 5 6 7 8 9 0 1 2 3 4 + * ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 1 LYS_CONFIG_W |x|x|x|x|x|x|x| | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 2 LYS_CONFIG_R |x|x|x|x|x|x|x| | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 3 LYS_STATUS_CURR |x|x|x|x|x|x|x|x|x|x|x|x|x|x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 4 LYS_STATUS_DEPRC |x|x|x|x|x|x|x|x|x|x|x|x|x|x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 5 LYS_STATUS_OBSLT |x|x|x|x|x|x|x|x|x|x|x|x|x|x| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 6 LYS_MAND_TRUE |x|x|x|x|x|x| | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 7 LYS_ORDBY_USER | | | |x|x| | | | | | | | | | + * LYS_MAND_FALSE | |x|x| | |x| | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 8 LYS_ORDBY_SYSTEM | | | |x|x| | | | | | | | | | + * LYS_PRESENCE |x| | | | | | | | | | | | | | + * LYS_UNIQUE | | |x| | | | | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 9 LYS_KEY | | |x| | | | | | | | | | | | + * LYS_DISABLED | | | | | | | | | | | | |x| | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 10 LYS_SET_DFLT | | |x|x| | |x| | | | | | | | + * LYS_IS_ENUM | | | | | | | | | | | | |x| | + * LYS_KEYLESS | | | | |x| | | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 11 LYS_SET_UNITS | | |x|x| | | | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 12 LYS_SET_CONFIG |x|x|x|x|x|x| | | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 13 LYS_IS_INPUT |x|x|x|x|x|x|x| | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 14 LYS_IS_OUTPUT |x|x|x|x|x|x|x| | | | | | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * 15 LYS_IS_NOTIF |x|x|x|x|x|x|x| | | | | | | | + * ---------------------+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +/** + * @defgroup snodeflags Schema nodes flags + * + * Various flags for schema nodes ([parsed](@ref spnodeflags) as well as [compiled](@ref scnodeflags)). + * + * @{ + */ +#define LYS_CONFIG_W 0x01 /**< config true; */ +#define LYS_CONFIG_R 0x02 /**< config false; */ +#define LYS_CONFIG_MASK 0x03 /**< mask for config value */ +#define LYS_STATUS_CURR 0x04 /**< status current; */ +#define LYS_STATUS_DEPRC 0x08 /**< status deprecated; */ +#define LYS_STATUS_OBSLT 0x10 /**< status obsolete; */ +#define LYS_STATUS_MASK 0x1C /**< mask for status value */ +#define LYS_MAND_TRUE 0x20 /**< mandatory true; applicable only to ::lysp_node_choice/::lysc_node_choice, + ::lysp_node_leaf/::lysc_node_leaf and ::lysp_node_anydata/::lysc_node_anydata. + The ::lysc_node_leaflist and ::lysc_node_leaflist have this flag in case that min-elements > 0. + The ::lysc_node_container has this flag if it is not a presence container and it has at least one + child with LYS_MAND_TRUE. */ +#define LYS_MAND_FALSE 0x40 /**< mandatory false; applicable only to ::lysp_node_choice/::lysc_node_choice, + ::lysp_node_leaf/::lysc_node_leaf and ::lysp_node_anydata/::lysc_node_anydata. + This flag is present only in case the mandatory false statement was explicitly specified. */ +#define LYS_MAND_MASK 0x60 /**< mask for mandatory values */ +#define LYS_PRESENCE 0x80 /**< flag for presence property of a container, but it is not only for explicit presence + containers, but also for NP containers with some meaning, applicable only to + ::lysc_node_container */ +#define LYS_UNIQUE 0x80 /**< flag for leafs being part of a unique set, applicable only to ::lysc_node_leaf */ +#define LYS_KEY 0x0100 /**< flag for leafs being a key of a list, applicable only to ::lysc_node_leaf */ +#define LYS_KEYLESS 0x0200 /**< flag for list without any key, applicable only to ::lysc_node_list */ +#define LYS_DISABLED 0x0100 /**< internal flag for a disabled statement, used only for bits/enums */ +#define LYS_FENABLED 0x20 /**< feature enabled flag, applicable only to ::lysp_feature. */ +#define LYS_ORDBY_SYSTEM 0x80 /**< ordered-by system configuration lists, applicable only to + ::lysc_node_leaflist/::lysp_node_leaflist and ::lysc_node_list/::lysp_node_list */ +#define LYS_ORDBY_USER 0x40 /**< ordered-by user configuration lists, applicable only to + ::lysc_node_leaflist/::lysp_node_leaflist and ::lysc_node_list/::lysp_node_list; + is always set for state leaf-lists, and key-less lists */ +#define LYS_ORDBY_MASK 0x60 /**< mask for ordered-by values */ +#define LYS_YINELEM_TRUE 0x80 /**< yin-element true for extension's argument */ +#define LYS_YINELEM_FALSE 0x0100 /**< yin-element false for extension's argument */ +#define LYS_YINELEM_MASK 0x0180 /**< mask for yin-element value */ +#define LYS_USED_GRP 0x0400 /**< internal flag for validating not-instantiated groupings + (resp. do not validate again the instantiated groupings). */ +#define LYS_SET_VALUE 0x0200 /**< value attribute is set */ +#define LYS_SET_MIN 0x0200 /**< min attribute is set */ +#define LYS_SET_MAX 0x0400 /**< max attribute is set */ + +#define LYS_SET_BASE 0x0001 /**< type's flag for present base substatement */ +#define LYS_SET_BIT 0x0002 /**< type's flag for present bit substatement */ +#define LYS_SET_ENUM 0x0004 /**< type's flag for present enum substatement */ +#define LYS_SET_FRDIGITS 0x0008 /**< type's flag for present fraction-digits substatement */ +#define LYS_SET_LENGTH 0x0010 /**< type's flag for present length substatement */ +#define LYS_SET_PATH 0x0020 /**< type's flag for present path substatement */ +#define LYS_SET_PATTERN 0x0040 /**< type's flag for present pattern substatement */ +#define LYS_SET_RANGE 0x0080 /**< type's flag for present range substatement */ +#define LYS_SET_TYPE 0x0100 /**< type's flag for present type substatement */ +#define LYS_SET_REQINST 0x0200 /**< type's flag for present require-instance substatement */ +#define LYS_SET_DFLT 0x0200 /**< flag to mark leaf/leaflist with own (or refined) default value, not a default value taken from its type, and default + cases of choice. This information is important for refines, since it is prohibited to make leafs + with default statement mandatory. In case the default leaf value is taken from type, it is thrown + away when it is refined to be mandatory node. Similarly it is used for deviations to distinguish + between own default or the default values taken from the type. */ +#define LYS_SET_UNITS 0x0400 /**< flag to know if the leaf's/leaflist's units are their own (flag set) or it is taken from the type. */ +#define LYS_SET_CONFIG 0x0800 /**< flag to know if the config property was set explicitly (flag set) or it is inherited. */ + +#define LYS_SINGLEQUOTED 0x0100 /**< flag for single-quoted argument of an extension instance's substatement, only when the source is YANG */ +#define LYS_DOUBLEQUOTED 0x0200 /**< flag for double-quoted argument of an extension instance's substatement, only when the source is YANG */ + +#define LYS_YIN_ATTR 0x0400 /**< flag to identify YIN attribute parsed as extension's substatement, only when the source is YIN */ +#define LYS_YIN_ARGUMENT 0x0800 /**< flag to identify statement representing extension's argument, only when the source is YIN */ + +#define LYS_INTERNAL 0x1000 /**< flag to identify internal parsed statements that should not be printed */ + +#define LYS_IS_ENUM 0x0200 /**< flag to simply distinguish type in struct lysc_type_bitenum_item */ + +#define LYS_IS_INPUT 0x1000 /**< flag for nodes that are in the subtree of an input statement */ + +#define LYS_IS_OUTPUT 0x2000 /**< flag for nodes that are in the subtree of an output statement */ + +#define LYS_IS_NOTIF 0x4000 /**< flag for nodes that are in the subtree of a notification statement */ + +#define LYS_FLAGS_COMPILED_MASK 0xff /**< mask for flags that maps to the compiled structures */ +/** @} snodeflags */ + +/** + * @brief Generic YANG data node + */ +struct lysp_node { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< [type of the node](@ref schemanodetypes) (mandatory) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)), + must be qname because of refines */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief Extension structure of the lysp_node for YANG container + */ +struct lysp_node_container { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_CONTAINER */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* container */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_when *when; /**< when statement */ + const char *presence; /**< presence description */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *child; /**< list of data nodes (linked list) */ + struct lysp_node_action *actions;/**< list of actions (linked list) */ + struct lysp_node_notif *notifs; /**< list of notifications (linked list) */ +}; + +struct lysp_node_leaf { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_LEAF */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* leaf */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_when *when; /**< when statement */ + struct lysp_type type; /**< type of the leaf node (mandatory) */ + const char *units; /**< units of the leaf's type */ + struct lysp_qname dflt; /**< default value, it may or may not be a qualified name */ +}; + +struct lysp_node_leaflist { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_LEAFLIST */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* leaf-list */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_when *when; /**< when statement */ + struct lysp_type type; /**< type of the leaf node (mandatory) */ + const char *units; /**< units of the leaf's type */ + struct lysp_qname *dflts; /**< list of default values ([sized array](@ref sizedarrays)), they may or + may not be qualified names */ + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint, 0 means unbounded */ +}; + +struct lysp_node_list { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_LIST */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* list */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_when *when; /**< when statement */ + const char *key; /**< keys specification */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *child; /**< list of data nodes (linked list) */ + struct lysp_node_action *actions;/**< list of actions (linked list) */ + struct lysp_node_notif *notifs; /**< list of notifications (linked list) */ + struct lysp_qname *uniques; /**< list of unique specifications ([sized array](@ref sizedarrays)) */ + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint, 0 means unbounded */ +}; + +struct lysp_node_choice { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_CHOICE */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* choice */ + struct lysp_node *child; /**< list of data nodes (linked list) */ + struct lysp_when *when; /**< when statement */ + struct lysp_qname dflt; /**< default case */ +}; + +struct lysp_node_case { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_CASE */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* case */ + struct lysp_node *child; /**< list of data nodes (linked list) */ + struct lysp_when *when; /**< when statement */ +}; + +struct lysp_node_anydata { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_ANYXML or LYS_ANYDATA */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* anyxml/anydata */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_when *when; /**< when statement */ +}; + +struct lysp_node_uses { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_USES */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< pointer to the next sibling node (NULL if there is no one) */ + const char *name; /**< grouping name reference (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* uses */ + struct lysp_refine *refines; /**< list of uses's refines ([sized array](@ref sizedarrays)) */ + struct lysp_node_augment *augments; /**< list of augments (linked list) */ + struct lysp_when *when; /**< when statement */ +}; + +/** + * @brief YANG input-stmt and output-stmt + */ +struct lysp_node_action_inout { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_INPUT or LYS_OUTPUT */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node *next; /**< NULL */ + const char *name; /**< empty string */ + const char *dsc; /**< ALWAYS NULL, compatibility member with ::lysp_node */ + const char *ref; /**< ALWAYS NULL, compatibility member with ::lysp_node */ + struct lysp_qname *iffeatures; /**< ALWAYS NULL, compatibility member with ::lysp_node */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* inout */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *child; /**< list of data nodes (linked list) */ +}; + +/** + * @brief YANG rpc-stmt and action-stmt + */ +struct lysp_node_action { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_RPC or LYS_ACTION */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + struct lysp_node_action *next; /**< pointer to the next action (NULL if there is no one) */ + const char *name; /**< grouping name reference (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* action */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + + struct lysp_node_action_inout input; /**< RPC's/Action's input */ + struct lysp_node_action_inout output; /**< RPC's/Action's output */ +}; + +/** + * @brief YANG notification-stmt + */ +struct lysp_node_notif { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent; /**< parent node (NULL if this is a top-level node) */ + uint16_t nodetype; /**< LYS_NOTIF */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_* values are allowed */ + struct lysp_node_notif *next; /**< pointer to the next notification (NULL if there is no one) */ + const char *name; /**< grouping name reference (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* notif */ + struct lysp_restr *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *child; /**< list of data nodes (linked list) */ +}; + +/** + * @brief YANG grouping-stmt + */ +struct lysp_node_grp { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent;/**< parent node (NULL if this is a top-level grouping) */ + uint16_t nodetype; /**< LYS_GROUPING */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_* values are allowed */ + struct lysp_node_grp *next; /**< pointer to the next grouping (NULL if there is no one) */ + const char *name; /**< grouping name (mandatory) */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< ALWAYS NULL, compatibility member with ::lysp_node */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + /* grp */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *child; /**< list of data nodes (linked list) */ + struct lysp_node_action *actions; /**< list of actions (linked list) */ + struct lysp_node_notif *notifs; /**< list of notifications (linked list) */ +}; + +/** + * @brief YANG uses-augment-stmt and augment-stmt (compatible with struct lysp_node ) + */ +struct lysp_node_augment { + union { + struct lysp_node node; /**< implicit cast for the members compatible with ::lysp_node */ + + struct { + struct lysp_node *parent;/**< parent node (NULL if this is a top-level augment) */ + uint16_t nodetype; /**< LYS_AUGMENT */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_* values are allowed */ + struct lysp_node_augment *next; /**< pointer to the next augment (NULL if there is no one) */ + const char *nodeid; /**< target schema nodeid (mandatory) - absolute for global augments, descendant for uses's augments */ + const char *dsc; /**< description statement */ + const char *ref; /**< reference statement */ + struct lysp_qname *iffeatures; /**< list of if-feature expressions ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + }; + }; /**< common part corresponding to ::lysp_node */ + + struct lysp_node *child; /**< list of data nodes (linked list) */ + struct lysp_when *when; /**< when statement */ + struct lysp_node_action *actions;/**< list of actions (linked list) */ + struct lysp_node_notif *notifs; /**< list of notifications (linked list) */ +}; + +/** + * @brief supported YANG schema version values + */ +typedef enum LYS_VERSION { + LYS_VERSION_UNDEF = 0, /**< no specific version, YANG 1.0 as default */ + LYS_VERSION_1_0 = 1, /**< YANG 1 (1.0) */ + LYS_VERSION_1_1 = 2 /**< YANG 1.1 */ +} LYS_VERSION; + +/** + * @brief Printable YANG schema tree structure representing YANG module. + * + * Simple structure corresponding to the YANG format. The schema is only syntactically validated. + */ +struct lysp_module { + struct lys_module *mod; /**< covering module structure */ + + struct lysp_revision *revs; /**< list of the module revisions ([sized array](@ref sizedarrays)), the first revision + in the list is always the last (newest) revision of the module */ + struct lysp_import *imports; /**< list of imported modules ([sized array](@ref sizedarrays)) */ + struct lysp_include *includes; /**< list of included submodules ([sized array](@ref sizedarrays)) */ + struct lysp_ext *extensions; /**< list of extension statements ([sized array](@ref sizedarrays)) */ + struct lysp_feature *features; /**< list of feature definitions ([sized array](@ref sizedarrays)) */ + struct lysp_ident *identities; /**< list of identities ([sized array](@ref sizedarrays)) */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *data; /**< list of module's top-level data nodes (linked list) */ + struct lysp_node_augment *augments; /**< list of augments (linked list) */ + struct lysp_node_action *rpcs; /**< list of RPCs (linked list) */ + struct lysp_node_notif *notifs; /**< list of notifications (linked list) */ + struct lysp_deviation *deviations; /**< list of deviations ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + + uint8_t version; /**< yang-version (LYS_VERSION values) */ + uint8_t parsing : 1; /**< flag for circular check */ + uint8_t is_submod : 1; /**< always 0 */ +}; + +struct lysp_submodule { + struct lys_module *mod; /**< belongs to parent module (submodule - mandatory) */ + + struct lysp_revision *revs; /**< list of the module revisions ([sized array](@ref sizedarrays)), the first revision + in the list is always the last (newest) revision of the module */ + struct lysp_import *imports; /**< list of imported modules ([sized array](@ref sizedarrays)) */ + struct lysp_include *includes; /**< list of included submodules ([sized array](@ref sizedarrays)) */ + struct lysp_ext *extensions; /**< list of extension statements ([sized array](@ref sizedarrays)) */ + struct lysp_feature *features; /**< list of feature definitions ([sized array](@ref sizedarrays)) */ + struct lysp_ident *identities; /**< list of identities ([sized array](@ref sizedarrays)) */ + struct lysp_tpdf *typedefs; /**< list of typedefs ([sized array](@ref sizedarrays)) */ + struct lysp_node_grp *groupings; /**< list of groupings (linked list) */ + struct lysp_node *data; /**< list of module's top-level data nodes (linked list) */ + struct lysp_node_augment *augments; /**< list of augments (linked list) */ + struct lysp_node_action *rpcs; /**< list of RPCs (linked list) */ + struct lysp_node_notif *notifs; /**< list of notifications (linked list) */ + struct lysp_deviation *deviations; /**< list of deviations ([sized array](@ref sizedarrays)) */ + struct lysp_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + + uint8_t version; /**< yang-version (LYS_VERSION values) */ + uint8_t parsing : 1; /**< flag for circular check */ + uint8_t is_submod : 1; /**< always 1 */ + + uint8_t latest_revision : 2; /**< flag to mark the latest available revision: + 1 - the latest revision in searchdirs was not searched yet and this is the + latest revision in the current context + 2 - searchdirs were searched and this is the latest available revision */ + const char *name; /**< name of the module (mandatory) */ + const char *filepath; /**< path, if the schema was read from a file, NULL in case of reading from memory */ + const char *prefix; /**< submodule belongsto prefix of main module (mandatory) */ + const char *org; /**< party/company responsible for the module */ + const char *contact; /**< contact information for the module */ + const char *dsc; /**< description of the module */ + const char *ref; /**< cross-reference for the module */ +}; + +/** + * @brief Get the parsed module or submodule name. + * + * @param[in] PMOD Parsed module or submodule. + * @return Module or submodule name. + */ +#define LYSP_MODULE_NAME(PMOD) (PMOD->is_submod ? ((struct lysp_submodule *)PMOD)->name : ((struct lysp_module *)PMOD)->mod->name) + +/** + * @brief Compiled prefix data pair mapping of prefixes to modules. In case the format is ::LY_VALUE_SCHEMA_RESOLVED, + * the expected prefix data is a sized array of these structures. + */ +struct lysc_prefix { + char *prefix; /**< used prefix */ + const struct lys_module *mod; /**< mapping to a module */ +}; + +/** + * @brief Compiled YANG extension-stmt + * + * Note that the compiled extension definition is created only in case the extension is instantiated. It is not available + * from the compiled schema, but from the parsed extension definition which is being searched when an extension instance + * is being compiled. + */ +struct lysc_ext { + const char *name; /**< extension name */ + const char *argname; /**< argument name, NULL if not specified */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_ext *plugin; /**< Plugin implementing the specific extension */ + struct lys_module *module; /**< module structure */ + uint32_t refcount; /**< unused, always 1 */ + uint16_t flags; /**< LYS_STATUS_* value (@ref snodeflags) */ +}; + +/** + * @brief YANG when-stmt + */ +struct lysc_when { + struct lyxp_expr *cond; /**< XPath when condition */ + struct lysc_node *context; /**< context node for evaluating the expression, NULL if the context is root node */ + struct lysc_prefix *prefixes; /**< compiled used prefixes in the condition */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint32_t refcount; /**< reference counter since some of the when statements are shared among several nodes */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS is allowed */ +}; + +/** + * @brief YANG identity-stmt + */ +struct lysc_ident { + const char *name; /**< identity name (mandatory, no prefix) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lys_module *module; /**< module structure */ + struct lysc_ident **derived; /**< list of (pointers to the) derived identities ([sized array](@ref sizedarrays)) + It also contains references to identities located in unimplemented modules. */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_ values are allowed */ +}; + +/** + * @defgroup ifftokens if-feature expression tokens + * Tokens of if-feature expression used in ::lysc_iffeature.expr. + * + * @{ + */ +#define LYS_IFF_NOT 0x00 /**< operand "not" */ +#define LYS_IFF_AND 0x01 /**< operand "and" */ +#define LYS_IFF_OR 0x02 /**< operand "or" */ +#define LYS_IFF_F 0x03 /**< feature */ +/** @} ifftokens */ + +/** + * @brief Compiled YANG revision statement + */ +struct lysc_revision { + char date[LY_REV_SIZE]; /**< revision-date (mandatory) */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +struct lysc_range { + struct lysc_range_part { + union { /**< min boundary */ + int64_t min_64; /**< for int8, int16, int32, int64 and decimal64 ( >= LY_TYPE_DEC64) */ + uint64_t min_u64; /**< for uint8, uint16, uint32, uint64, string and binary ( < LY_TYPE_DEC64) */ + }; + union { /**< max boundary */ + int64_t max_64; /**< for int8, int16, int32, int64 and decimal64 ( >= LY_TYPE_DEC64) */ + uint64_t max_u64; /**< for uint8, uint16, uint32, uint64, string and binary ( < LY_TYPE_DEC64) */ + }; + } *parts; /**< compiled range expression ([sized array](@ref sizedarrays)) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + const char *emsg; /**< error-message */ + const char *eapptag; /**< error-app-tag value */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +struct lysc_pattern { + const char *expr; /**< original, not compiled, regular expression */ + pcre2_code *code; /**< compiled regular expression */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + const char *emsg; /**< error-message */ + const char *eapptag; /**< error-app-tag value */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + uint32_t inverted : 1; /**< invert-match flag */ + uint32_t refcount : 31; /**< reference counter */ +}; + +struct lysc_must { + struct lyxp_expr *cond; /**< XPath when condition */ + struct lysc_prefix *prefixes; /**< compiled used prefixes in the condition */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + const char *emsg; /**< error-message */ + const char *eapptag; /**< error-app-tag value */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +struct lysc_type { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing, it may be accessed concurrently when + creating/freeing data node values that reference it (instance-identifier) */ +}; + +struct lysc_type_num { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_range *range; /**< Optional range limitation */ +}; + +struct lysc_type_dec { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + uint8_t fraction_digits; /**< fraction digits specification */ + struct lysc_range *range; /**< Optional range limitation */ +}; + +struct lysc_type_str { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_range *length; /**< Optional length limitation */ + struct lysc_pattern **patterns; /**< Optional list of pointers to pattern limitations ([sized array](@ref sizedarrays)) */ +}; + +struct lysc_type_bitenum_item { + const char *name; /**< enumeration identifier */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + + union { + int32_t value; /**< integer value associated with the enumeration */ + uint32_t position; /**< non-negative integer value associated with the bit */ + }; + uint16_t flags; /**< [schema node flags](@ref snodeflags) - only LYS_STATUS_ and LYS_IS_ENUM values + are allowed */ +}; + +struct lysc_type_enum { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_type_bitenum_item *enums; /**< enumerations list ([sized array](@ref sizedarrays)), mandatory (at least 1 item) */ +}; + +struct lysc_type_bits { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_type_bitenum_item *bits; /**< bits list ([sized array](@ref sizedarrays)), mandatory (at least 1 item), + the items are ordered by their position value. */ +}; + +struct lysc_type_leafref { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lyxp_expr *path; /**< parsed target path, compiled path cannot be stored because of type sharing */ + struct lysc_prefix *prefixes; /**< resolved prefixes used in the path */ + const struct lys_module *cur_mod;/**< unused, not needed */ + struct lysc_type *realtype; /**< pointer to the real (first non-leafref in possible leafrefs chain) type. */ + uint8_t require_instance; /**< require-instance flag */ +}; + +struct lysc_type_identityref { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_ident **bases; /**< list of pointers to the base identities ([sized array](@ref sizedarrays)), + mandatory (at least 1 item) */ +}; + +struct lysc_type_instanceid { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + uint8_t require_instance; /**< require-instance flag */ +}; + +struct lysc_type_union { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_type **types; /**< list of types in the union ([sized array](@ref sizedarrays)), mandatory (at least 1 item) */ +}; + +struct lysc_type_bin { + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + struct lyplg_type *plugin; /**< type's plugin with built-in as well as user functions to canonize or validate the value of the type */ + LY_DATA_TYPE basetype; /**< Base type of the type */ + uint32_t refcount; /**< reference counter for type sharing */ + struct lysc_range *length; /**< Optional length limitation */ +}; + +/** + * @brief Maximum number of hashes stored in a schema node. + */ +#define LYS_NODE_HASH_COUNT 4 + +/** + * @brief Compiled YANG data node + */ +struct lysc_node { + uint16_t nodetype; /**< [type of the node](@ref schemanodetypes) (mandatory) */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ +}; + +struct lysc_node_action_inout { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_INPUT or LYS_OUTPUT */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent;/**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (output node for input, NULL for output) */ + struct lysc_node *prev; /**< pointer to the previous sibling node (input and output node pointing to each other) */ + const char *name; /**< "input" or "output" */ + const char *dsc; /**< ALWAYS NULL, compatibility member with ::lysc_node */ + const char *ref; /**< ALWAYS NULL, compatibility member with ::lysc_node */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /** private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_node *child; /**< first child node (linked list) */ + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ +}; + +struct lysc_node_action { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_RPC or LYS_ACTION */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node - RPC) */ + struct lysc_node_action *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node_action *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< action/RPC name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /** private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)), + the action/RPC nodes do not contain the when statement on their own, but they can + inherit it from the parent's uses. */ + struct lysc_node_action_inout input; /**< RPC's/action's input */ + struct lysc_node_action_inout output; /**< RPC's/action's output */ + +}; + +struct lysc_node_notif { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_NOTIF */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node_notif *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node_notif *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< Notification name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /** private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_node *child; /**< first child node (linked list) */ + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)), + the notification nodes do not contain the when statement on their own, but they can + inherit it from the parent's uses. */ +}; + +struct lysc_node_container { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_CONTAINER */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_node *child; /**< first child node (linked list) */ + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ + struct lysc_node_action *actions;/**< first of actions nodes (linked list) */ + struct lysc_node_notif *notifs; /**< first of notifications nodes (linked list) */ +}; + +struct lysc_node_case { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_CASE */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser, unused */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< name of the case, including the implicit case */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_node *child; /**< first child node of the case (linked list). Note that all the children of all the sibling cases are linked + each other as siblings with the parent pointer pointing to appropriate case node. */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ +}; + +struct lysc_node_choice { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_CHOICE */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser, unused */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_node_case *cases; /**< list of the cases (linked list). Note that all the children of all the cases are linked each other + as siblings. Their parent pointers points to the specific case they belongs to, so distinguish the + case is simple. */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ + struct lysc_node_case *dflt; /**< default case of the choice, only a pointer into the cases array. */ +}; + +struct lysc_node_leaf { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_LEAF */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ + struct lysc_type *type; /**< type of the leaf node (mandatory) */ + + const char *units; /**< units of the leaf's type */ + struct lyd_value *dflt; /**< default value, use ::lyd_value_get_canonical() to get the canonical string */ +}; + +struct lysc_node_leaflist { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_LEAFLIST */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ + struct lysc_type *type; /**< type of the leaf node (mandatory) */ + + const char *units; /**< units of the leaf's type */ + struct lyd_value **dflts; /**< list ([sized array](@ref sizedarrays)) of default values, use + ::lyd_value_get_canonical() to get the canonical strings */ + + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint */ + +}; + +struct lysc_node_list { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_LIST */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_node *child; /**< first child node (linked list) */ + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ + struct lysc_node_action *actions;/**< first of actions nodes (linked list) */ + struct lysc_node_notif *notifs; /**< first of notifications nodes (linked list) */ + + struct lysc_node_leaf ***uniques;/**< list of sized arrays of pointers to the unique nodes ([sized array](@ref sizedarrays)) */ + uint32_t min; /**< min-elements constraint */ + uint32_t max; /**< max-elements constraint */ +}; + +struct lysc_node_anydata { + union { + struct lysc_node node; /**< implicit cast for the members compatible with ::lysc_node */ + + struct { + uint16_t nodetype; /**< LYS_ANYXML or LYS_ANYDATA */ + uint16_t flags; /**< [schema node flags](@ref snodeflags) */ + uint8_t hash[LYS_NODE_HASH_COUNT]; /**< schema hash required for LYB printer/parser */ + struct lys_module *module; /**< module structure */ + struct lysc_node *parent; /**< parent node (NULL in case of top level node) */ + struct lysc_node *next; /**< next sibling node (NULL if there is no one) */ + struct lysc_node *prev; /**< pointer to the previous sibling node \note Note that this pointer is + never NULL. If there is no sibling node, pointer points to the node + itself. In case of the first node, this pointer points to the last + node in the list. */ + const char *name; /**< node name (mandatory) */ + const char *dsc; /**< description */ + const char *ref; /**< reference */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ + void *priv; /**< private arbitrary user data, not used by libyang unless ::LY_CTX_SET_PRIV_PARSED is set */ + }; + }; + + struct lysc_must *musts; /**< list of must restrictions ([sized array](@ref sizedarrays)) */ + struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief Compiled YANG schema tree structure representing YANG module. + * + * Semantically validated YANG schema tree for data tree parsing. + * Contains only the necessary information for the data validation. + */ +struct lysc_module { + struct lys_module *mod; /**< covering module structure */ + + struct lysc_node *data; /**< list of module's top-level data nodes (linked list) */ + struct lysc_node_action *rpcs; /**< first of actions nodes (linked list) */ + struct lysc_node_notif *notifs; /**< first of notifications nodes (linked list) */ + struct lysc_ext_instance *exts; /**< list of the extension instances ([sized array](@ref sizedarrays)) */ +}; + +/** + * @brief Examine whether a node is user-ordered list or leaf-list. + * + * @param[in] lysc_node Schema node to examine. + * @return Boolean value whether the @p node is user-ordered or not. + */ +#define lysc_is_userordered(lysc_node) \ + ((!lysc_node || !(lysc_node->nodetype & (LYS_LEAFLIST | LYS_LIST)) || !(lysc_node->flags & LYS_ORDBY_USER)) ? 0 : 1) + +/** + * @brief Examine whether a node is a list's key. + * + * @param[in] lysc_node Schema node to examine. + * @return Boolean value whether the @p node is a key or not. + */ +#define lysc_is_key(lysc_node) \ + ((!lysc_node || (lysc_node->nodetype != LYS_LEAF) || !(lysc_node->flags & LYS_KEY)) ? 0 : 1) + +/** + * @brief Examine whether a node is a non-presence container. + * + * @param[in] lysc_node Schema node to examine. + * @return Boolean value whether the @p node is a NP container or not. + */ +#define lysc_is_np_cont(lysc_node) \ + ((!lysc_node || (lysc_node->nodetype != LYS_CONTAINER) || (lysc_node->flags & LYS_PRESENCE)) ? 0 : 1) + +/** + * @brief Examine whether a node is a key-less list or a non-configuration leaf-list. + * + * @param[in] lysc_node Schema node to examine. + * @return Boolean value whether the @p node is a list with duplicate instances allowed. + */ +#define lysc_is_dup_inst_list(lysc_node) \ + ((lysc_node && (((lysc_node->nodetype == LYS_LIST) && (lysc_node->flags & LYS_KEYLESS)) || \ + ((lysc_node->nodetype == LYS_LEAFLIST) && !(lysc_node->flags & LYS_CONFIG_W)))) ? 1 : 0) + +/** + * @brief Get nearest @p schema parent (including the node itself) that can be instantiated in data. + * + * @param[in] schema Schema node to get the nearest data node for. + * @return Schema data node, NULL if top-level (in data). + */ +LIBYANG_API_DECL const struct lysc_node *lysc_data_node(const struct lysc_node *schema); + +/** + * @brief Same as ::lysc_data_node() but never returns the node itself. + */ +#define lysc_data_parent(SCHEMA) lysc_data_node((SCHEMA) ? (SCHEMA)->parent : NULL) + +/** + * @brief Check whether the schema node data instance existence depends on any when conditions. + * This node and any direct parent choice and case schema nodes are also examined for when conditions. + * + * Be careful, this function is not recursive and checks only conditions that apply to this node directly. + * Meaning if there are any conditions associated with any data parent instance of @p node, they are not returned. + * + * @param[in] node Schema node to examine. + * @return When condition associated with the node data instance, NULL if there is none. + */ +LIBYANG_API_DECL const struct lysc_when *lysc_has_when(const struct lysc_node *node); + +/** + * @brief Get the owner module of the schema node. It is the module of the top-level node. Generally, + * in case of augments it is the target module, recursively, otherwise it is the module where the node is defined. + * + * @param[in] node Schema node to examine. + * @return Module owner of the node. + */ +LIBYANG_API_DECL const struct lys_module *lysc_owner_module(const struct lysc_node *node); + +/** + * @brief Get the groupings linked list of the given (parsed) schema node. + * Decides the node's type and in case it has a groupings array, returns it. + * @param[in] node Node to examine. + * @return The node's groupings linked list if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysp_node_grp *lysp_node_groupings(const struct lysp_node *node); + +/** + * @brief Get the typedefs sized array of the given (parsed) schema node. + * Decides the node's type and in case it has a typedefs array, returns it. + * @param[in] node Node to examine. + * @return The node's typedefs sized array if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysp_tpdf *lysp_node_typedefs(const struct lysp_node *node); + +/** + * @brief Get the actions/RPCs linked list of the given (parsed) schema node. + * Decides the node's type and in case it has a actions/RPCs array, returns it. + * @param[in] node Node to examine. + * @return The node's actions/RPCs linked list if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysp_node_action *lysp_node_actions(const struct lysp_node *node); + +/** + * @brief Get the Notifications linked list of the given (parsed) schema node. + * Decides the node's type and in case it has a Notifications array, returns it. + * @param[in] node Node to examine. + * @return The node's Notifications linked list if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysp_node_notif *lysp_node_notifs(const struct lysp_node *node); + +/** + * @brief Get the children linked list of the given (parsed) schema node. + * Decides the node's type and in case it has a children list, returns it. + * @param[in] node Node to examine. + * @return The node's children linked list if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysp_node *lysp_node_child(const struct lysp_node *node); + +/** + * @brief Get the actions/RPCs linked list of the given (compiled) schema node. + * Decides the node's type and in case it has a actions/RPCs array, returns it. + * @param[in] node Node to examine. + * @return The node's actions/RPCs linked list if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysc_node_action *lysc_node_actions(const struct lysc_node *node); + +/** + * @brief Get the Notifications linked list of the given (compiled) schema node. + * Decides the node's type and in case it has a Notifications array, returns it. + * @param[in] node Node to examine. + * @return The node's Notifications linked list if any, NULL otherwise. + */ +LIBYANG_API_DECL const struct lysc_node_notif *lysc_node_notifs(const struct lysc_node *node); + +/** + * @brief Get the children linked list of the given (compiled) schema node. + * + * Note that ::LYS_CHOICE has only ::LYS_CASE children. + * Also, ::LYS_RPC and ::LYS_ACTION have the first child ::LYS_INPUT, its sibling is ::LYS_OUTPUT. + * + * @param[in] node Node to examine. + * @return Children linked list if any, + * @return NULL otherwise. + */ +LIBYANG_API_DECL const struct lysc_node *lysc_node_child(const struct lysc_node *node); + +/** + * @brief Get the must statements list if present in the @p node + * + * @param[in] node Node to examine. + * @return Pointer to the list of must restrictions ([sized array](@ref sizedarrays)) + * @return NULL if there is no must statement in the node, no matter if it is not even allowed or just present + */ +LIBYANG_API_DECL struct lysc_must *lysc_node_musts(const struct lysc_node *node); + +/** + * @brief Get the when statements list if present in the @p node + * + * @param[in] node Node to examine. + * @return Pointer to the list of pointers to when statements ([sized array](@ref sizedarrays)) + * @return NULL if there is no when statement in the node, no matter if it is not even allowed or just present + */ +LIBYANG_API_DECL struct lysc_when **lysc_node_when(const struct lysc_node *node); + +/** + * @brief Callback to be called for every schema node in a DFS traversal. + * + * @param[in] node Current node. + * @param[in] data Arbitrary user data. + * @param[out] dfs_continue Set to true if the current subtree should be skipped and continue with siblings instead. + * @return LY_SUCCESS on success, + * @return LY_ERR value to terminate DFS and return this value. + */ +typedef LY_ERR (*lysc_dfs_clb)(struct lysc_node *node, void *data, ly_bool *dfs_continue); + +/** + * @brief DFS traversal of all the schema nodes in a (sub)tree including any actions and nested notifications. + * + * Node with children, actions, and notifications is traversed in this order: + * 1) each child subtree; + * 2) each action subtree; + * 3) each notification subtree. + * + * For algorithm illustration or traversal with actions and notifications skipped, see ::LYSC_TREE_DFS_BEGIN. + * + * @param[in] root Schema root to fully traverse. + * @param[in] dfs_clb Callback to call for each node. + * @param[in] data Arbitrary user data passed to @p dfs_clb. + * @return LY_SUCCESS on success, + * @return LY_ERR value returned by @p dfs_clb. + */ +LIBYANG_API_DECL LY_ERR lysc_tree_dfs_full(const struct lysc_node *root, lysc_dfs_clb dfs_clb, void *data); + +/** + * @brief DFS traversal of all the schema nodes in a module including RPCs and notifications. + * + * For more details, see ::lysc_tree_dfs_full(). + * + * @param[in] mod Module to fully traverse. + * @param[in] dfs_clb Callback to call for each node. + * @param[in] data Arbitrary user data passed to @p dfs_clb. + * @return LY_SUCCESS on success, + * @return LY_ERR value returned by @p dfs_clb. + */ +LIBYANG_API_DECL LY_ERR lysc_module_dfs_full(const struct lys_module *mod, lysc_dfs_clb dfs_clb, void *data); + +/** + * @brief Get how the if-feature statement currently evaluates. + * + * @param[in] iff Compiled if-feature statement to evaluate. + * @return LY_SUCCESS if the statement evaluates to true, + * @return LY_ENOT if it evaluates to false, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lysc_iffeature_value(const struct lysc_iffeature *iff); + +/** + * @brief Get how the if-feature statement is evaluated for certain identity. + * + * The function can be called even if the identity does not contain + * if-features, in which case ::LY_SUCCESS is returned. + * + * @param[in] ident Compiled identity statement to evaluate. + * @return LY_SUCCESS if the statement evaluates to true, + * @return LY_ENOT if it evaluates to false, + * @return LY_ERR on error. + */ +LIBYANG_API_DECL LY_ERR lys_identity_iffeature_value(const struct lysc_ident *ident); + +/** + * @brief Get the next feature in the module or submodules. + * + * @param[in] last Last returned feature. + * @param[in] pmod Parsed module and submodules whose features to iterate over. + * @param[in,out] idx Submodule index, set to 0 on first call. + * @return Next found feature, NULL if the last has already been returned. + */ +LIBYANG_API_DECL struct lysp_feature *lysp_feature_next(const struct lysp_feature *last, const struct lysp_module *pmod, + uint32_t *idx); + +/** + * @defgroup findxpathoptions Atomize XPath options + * Options to modify behavior of ::lys_find_xpath() and ::lys_find_xpath_atoms() searching for schema nodes in schema tree. + * @{ + */ +#define LYS_FIND_XP_SCHEMA 0x08 /**< Apply node access restrictions defined for 'when' and 'must' evaluation. */ +#define LYS_FIND_XP_OUTPUT 0x10 /**< Search RPC/action output nodes instead of input ones. */ +#define LYS_FIND_NO_MATCH_ERROR 0x40 /**< Return error if a path segment matches no nodes, otherwise only warning + is printed. */ +/** @} findxpathoptions */ + +/** + * @brief Get all the schema nodes that are required for @p xpath to be evaluated (atoms). + * + * @param[in] ctx libyang context to use. May be NULL if @p ctx_node is set. + * @param[in] ctx_node XPath schema context node. Use NULL for the root node. + * @param[in] xpath Data XPath expression filtering the matching nodes. ::LY_VALUE_JSON prefix format is expected. + * @param[in] options Whether to apply some node access restrictions, see @ref findxpathoptions. + * @param[out] set Set of found atoms (schema nodes). + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lys_find_xpath_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *xpath, + uint32_t options, struct ly_set **set); + +/** + * @brief Get all the schema nodes that are required for @p expr to be evaluated (atoms). + * + * @param[in] ctx_node XPath schema context node. Use NULL for the root node. + * @param[in] cur_mod Current module for the expression (where it was "instantiated"). + * @param[in] expr Parsed expression to use. + * @param[in] prefixes Sized array of compiled prefixes. + * @param[in] options Whether to apply some node access restrictions, see @ref findxpathoptions. + * @param[out] set Set of found atoms (schema nodes). + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lys_find_expr_atoms(const struct lysc_node *ctx_node, const struct lys_module *cur_mod, + const struct lyxp_expr *expr, const struct lysc_prefix *prefixes, uint32_t options, struct ly_set **set); + +/** + * @brief Evaluate an @p xpath expression on schema nodes. + * + * @param[in] ctx libyang context to use for absolute @p xpath. May be NULL if @p ctx_node is set. + * @param[in] ctx_node XPath schema context node for relative @p xpath. Use NULL for the root node. + * @param[in] xpath Data XPath expression filtering the matching nodes. ::LY_VALUE_JSON prefix format is expected. + * @param[in] options Whether to apply some node access restrictions, see @ref findxpathoptions. + * @param[out] set Set of found schema nodes. + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value if an error occurred. + */ +LIBYANG_API_DECL LY_ERR lys_find_xpath(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *xpath, + uint32_t options, struct ly_set **set); + +/** + * @brief Get all the schema nodes that are required for @p path to be evaluated (atoms). + * + * @param[in] path Compiled path to use. + * @param[out] set Set of found atoms (schema nodes). + * @return LY_SUCCESS on success, @p set is returned. + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lys_find_lypath_atoms(const struct ly_path *path, struct ly_set **set); + +/** + * @brief Get all the schema nodes that are required for @p path to be evaluated (atoms). + * + * @param[in] ctx libyang context to use for absolute @p path. May be NULL if @p ctx_node is set. + * @param[in] ctx_node XPath schema context node for relative @p path. Use NULL for the root node. + * @param[in] path JSON path to examine. + * @param[in] output Search operation output instead of input. + * @param[out] set Set of found atoms (schema nodes). + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lys_find_path_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *path, + ly_bool output, struct ly_set **set); + +/** + * @brief Get a schema node based on the given data path (JSON format, see @ref howtoXPath). + * + * @param[in] ctx libyang context to use for absolute @p path. May be NULL if @p ctx_node is set. + * @param[in] ctx_node XPath schema context node for relative @p path. Use NULL for the root node. + * @param[in] path JSON path of the node to get. + * @param[in] output Search operation output instead of input. + * @return Found schema node or NULL. + */ +LIBYANG_API_DECL const struct lysc_node *lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, + const char *path, ly_bool output); + +/** + * @brief Types of the different schema paths. + */ +typedef enum { + LYSC_PATH_LOG, /**< Descriptive path format used in log messages */ + LYSC_PATH_DATA, /**< Similar to ::LYSC_PATH_LOG except that schema-only nodes (choice, case) are skipped */ + LYSC_PATH_DATA_PATTERN /**< Similar to ::LYSC_PATH_DATA but there are predicates for all list keys added with + "%s" where their values should be so that they can be printed there */ +} LYSC_PATH_TYPE; + +/** + * @brief Generate path of the given node in the requested format. + * + * @param[in] node Schema path of this node will be generated. + * @param[in] pathtype Format of the path to generate. + * @param[in,out] buffer Prepared buffer of the @p buflen length to store the generated path. + * If NULL, memory for the complete path is allocated. + * @param[in] buflen Size of the provided @p buffer. + * @return NULL in case of memory allocation error, path of the node otherwise. + * In case the @p buffer is NULL, the returned string is dynamically allocated and caller is responsible to free it. + */ +LIBYANG_API_DECL char *lysc_path(const struct lysc_node *node, LYSC_PATH_TYPE pathtype, char *buffer, size_t buflen); + +/** + * @brief Available YANG schema tree structures representing YANG module. + */ +struct lys_module { + struct ly_ctx *ctx; /**< libyang context of the module (mandatory) */ + const char *name; /**< name of the module (mandatory) */ + const char *revision; /**< revision of the module (if present) */ + const char *ns; /**< namespace of the module (module - mandatory) */ + const char *prefix; /**< module prefix or submodule belongsto prefix of main module (mandatory) */ + const char *filepath; /**< path, if the schema was read from a file, NULL in case of reading from memory */ + const char *org; /**< party/company responsible for the module */ + const char *contact; /**< contact information for the module */ + const char *dsc; /**< description of the module */ + const char *ref; /**< cross-reference for the module */ + + struct lysp_module *parsed; /**< Simply parsed (unresolved) YANG schema tree */ + struct lysc_module *compiled; /**< Compiled and fully validated YANG schema tree for data parsing. + Available only for implemented modules. */ + + struct lysc_ident *identities; /**< List of compiled identities of the module ([sized array](@ref sizedarrays)) + also contains the disabled identities when their if-feature(s) are evaluated to \"false\", + and also the list is filled even if the module is not implemented. + The list is located here because it avoids problems when the module became implemented in + future (no matter if implicitly via augment/deviate or explicitly via + ::lys_set_implemented()). Note that if the module is not implemented (compiled), the + identities cannot be instantiated in data (in identityrefs). */ + + struct lys_module **augmented_by;/**< List of modules that augment this module ([sized array](@ref sizedarrays)) */ + struct lys_module **deviated_by; /**< List of modules that deviate this module ([sized array](@ref sizedarrays)) */ + + ly_bool implemented; /**< flag if the module is implemented, not just imported */ + ly_bool to_compile; /**< flag marking a module that was changed but not (re)compiled, see + ::LY_CTX_EXPLICIT_COMPILE. */ + uint8_t latest_revision; /**< Flag to mark the latest available revision, see [latest_revision options](@ref latestrevflags). */ +}; + +/** + * @defgroup latestrevflags Options for ::lys_module.latest_revision. + * + * Various information bits of ::lys_module.latest_revision. + * + * @{ + */ +#define LYS_MOD_LATEST_REV 0x01 /**< This is the latest revision of the module in the current context. */ +#define LYS_MOD_LATEST_SEARCHDIRS 0x02 /**< This is the latest revision of the module found in searchdirs. */ +#define LYS_MOD_IMPORTED_REV 0x04 /**< This is the module revision used when importing the module without + an explicit revision-date. It is used for all such imports regardless of + any changes made in the context. */ +#define LYS_MOD_LATEST_IMPCLB 0x08 /**< This is the latest revision of the module obtained from import callback. */ +/** @} latestrevflags */ + +/** + * @brief Get the current real status of the specified feature in the module. + * + * If the feature is enabled, but some of its if-features are false, the feature is considered + * disabled. + * + * @param[in] module Module where the feature is defined. + * @param[in] feature Name of the feature to inspect. + * @return LY_SUCCESS if the feature is enabled, + * @return LY_ENOT if the feature is disabled, + * @return LY_ENOTFOUND if the feature was not found. + */ +LIBYANG_API_DECL LY_ERR lys_feature_value(const struct lys_module *module, const char *feature); + +/** + * @brief Get next schema (sibling) node element in the schema order that can be instantiated in a data tree. + * Returned node may be from an augment. + * + * ::lys_getnext() is supposed to be called sequentially. In the first call, the @p last parameter is usually NULL + * and function starts returning 1) the first @p parent child (if it is set) or 2) the first top level element of + * @p module. Consequent calls should provide the previously returned node as @p last and the same @p parent and + * @p module parameters. + * + * Without options, the function is used to traverse only the schema nodes that can be paired with corresponding + * data nodes in a data tree. By setting some @p options the behavior can be modified to the extent that + * all the schema nodes are iteratively returned. + * + * @param[in] last Previously returned schema tree node, or NULL in case of the first call. + * @param[in] parent Parent of the subtree to iterate over. If set, @p module is ignored. + * @param[in] module Module of the top level elements to iterate over. If @p parent is NULL, it must be specified. + * @param[in] options [ORed options](@ref sgetnextflags). + * @return Next schema tree node, NULL in case there are no more. + */ +LIBYANG_API_DECL const struct lysc_node *lys_getnext(const struct lysc_node *last, const struct lysc_node *parent, + const struct lysc_module *module, uint32_t options); + +/** + * @brief Get next schema (sibling) node element in the schema order of an extension that can be instantiated in + * a data tree. + * + * It is just ::lys_getnext() for extensions. + * + * @param[in] last Previously returned schema tree node, or NULL in case of the first call. + * @param[in] parent Parent of the subtree to iterate over. If set, @p ext is ignored. + * @param[in] ext Extension instance with schema nodes to iterate over. If @p parent is NULL, it must be specified. + * @param[in] options [ORed options](@ref sgetnextflags). + * @return Next schema tree node, NULL in case there are no more. + */ +LIBYANG_API_DECL const struct lysc_node *lys_getnext_ext(const struct lysc_node *last, const struct lysc_node *parent, + const struct lysc_ext_instance *ext, uint32_t options); + +/** + * @defgroup sgetnextflags Options for ::lys_getnext() and ::lys_getnext_ext(). + * + * Various options setting behavior of ::lys_getnext() and ::lys_getnext_ext(). + * + * @{ + */ +#define LYS_GETNEXT_WITHCHOICE 0x01 /**< ::lys_getnext() option to allow returning #LYS_CHOICE nodes instead of looking into them */ +#define LYS_GETNEXT_NOCHOICE 0x02 /**< ::lys_getnext() option to ignore (kind of conditional) nodes within choice node */ +#define LYS_GETNEXT_WITHCASE 0x04 /**< ::lys_getnext() option to allow returning #LYS_CASE nodes instead of looking into them */ +#define LYS_GETNEXT_INTONPCONT 0x08 /**< ::lys_getnext() option to look into non-presence container, instead of returning container itself */ +#define LYS_GETNEXT_OUTPUT 0x10 /**< ::lys_getnext() option to provide RPC's/action's output schema nodes instead of input schema nodes + provided by default */ +/** @} sgetnextflags */ + +/** + * @brief Get child node according to the specified criteria. + * + * @param[in] parent Optional parent of the node to find. If not specified, the module's top-level nodes are searched. + * @param[in] module module of the node to find. It is also limitation for the children node of the given parent. + * @param[in] name Name of the node to find. + * @param[in] name_len Optional length of the name in case it is not NULL-terminated string. + * @param[in] nodetype Optional criteria (to speedup) specifying nodetype(s) of the node to find. + * Used as a bitmask, so multiple nodetypes can be specified. + * @param[in] options [ORed options](@ref sgetnextflags). + * @return Found node if any. + */ +LIBYANG_API_DECL const struct lysc_node *lys_find_child(const struct lysc_node *parent, const struct lys_module *module, + const char *name, size_t name_len, uint16_t nodetype, uint32_t options); + +/** + * @brief Make the specific module implemented. + * + * If the module is already implemented but with a different set of features, the whole context is recompiled. + * + * @param[in] mod Module to make implemented. It is not an error + * to provide already implemented module, it just does nothing. + * @param[in] features Optional array specifying the enabled features terminated with NULL overriding any previous + * feature setting. The feature string '*' enables all the features and array of length 1 with only the terminating + * NULL explicitly disables all the features. In case the parameter is NULL, the features are untouched - left disabled + * in a newly implemented module or with the current features settings in case the module is already implemented. + * @return LY_SUCCESS on success. + * @return LY_EDENIED in case the context contains some other revision of the same module which is already implemented. + * @return LY_ERR on other errors during module compilation. + */ +LIBYANG_API_DECL LY_ERR lys_set_implemented(struct lys_module *mod, const char **features); + +/** + * @brief Stringify schema nodetype. + * + * @param[in] nodetype Nodetype to stringify. + * @return Constant string with the name of the node's type. + */ +LIBYANG_API_DECL const char *lys_nodetype2str(uint16_t nodetype); + +/** + * @brief Getter for original XPath expression from a parsed expression. + * + * @param[in] path Parsed expression. + * @return Original string expression. + */ +LIBYANG_API_DECL const char *lyxp_get_expr(const struct lyxp_expr *path); + +/** @} schematree */ + +#ifdef __cplusplus +} +#endif + +#endif /* LY_TREE_SCHEMA_H_ */ diff --git a/src/tree_schema_common.c b/src/tree_schema_common.c new file mode 100644 index 0000000..bbdd676 --- /dev/null +++ b/src/tree_schema_common.c @@ -0,0 +1,2617 @@ +/** + * @file tree_schema_common.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Parsing and validation common functions for schema trees + * + * Copyright (c) 2015 - 2022 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 + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "hash_table.h" +#include "in.h" +#include "in_internal.h" +#include "log.h" +#include "parser_schema.h" +#include "schema_compile.h" +#include "schema_features.h" +#include "set.h" +#include "tree.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" + +LY_ERR +lysp_check_prefix(struct lysp_ctx *ctx, struct lysp_import *imports, const char *module_prefix, const char **value) +{ + struct lysp_import *i; + + if (module_prefix && (&module_prefix != value) && !strcmp(module_prefix, *value)) { + LOGVAL_PARSER(ctx, LYVE_REFERENCE, "Prefix \"%s\" already used as module prefix.", *value); + return LY_EEXIST; + } + LY_ARRAY_FOR(imports, struct lysp_import, i) { + if (i->prefix && (&i->prefix != value) && !strcmp(i->prefix, *value)) { + LOGVAL_PARSER(ctx, LYVE_REFERENCE, "Prefix \"%s\" already used to import \"%s\" module.", *value, i->name); + return LY_EEXIST; + } + } + return LY_SUCCESS; +} + +LY_ERR +lysp_check_date(struct lysp_ctx *ctx, const char *date, size_t date_len, const char *stmt) +{ + struct tm tm, tm_; + char *r; + + LY_CHECK_ARG_RET(PARSER_CTX(ctx), date, LY_EINVAL); + + if (date_len != LY_REV_SIZE - 1) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Invalid length %" PRIu32 " of a date.", (uint32_t)date_len); + return LY_EINVAL; + } + + /* check format: YYYY-MM-DD */ + for (uint8_t i = 0; i < date_len; i++) { + if ((i == 4) || (i == 7)) { + if (date[i] != '-') { + goto error; + } + } else if (!isdigit(date[i])) { + goto error; + } + } + + /* check content, e.g. 2018-02-31 */ + memset(&tm, 0, sizeof tm); + r = strptime(date, "%Y-%m-%d", &tm); + if (!r || (r != &date[LY_REV_SIZE - 1])) { + goto error; + } + memcpy(&tm_, &tm, sizeof tm); + + /* DST may move the hour back resulting in a different day */ + tm_.tm_hour = 1; + + mktime(&tm_); /* mktime modifies tm_ if it refers invalid date */ + if (tm.tm_mday != tm_.tm_mday) { /* e.g 2018-02-29 -> 2018-03-01 */ + /* checking days is enough, since other errors + * have been checked by strptime() */ + goto error; + } + + return LY_SUCCESS; + +error: + if (stmt) { + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, date_len, date, stmt); + } + return LY_EINVAL; +} + +void +lysp_sort_revisions(struct lysp_revision *revs) +{ + LY_ARRAY_COUNT_TYPE i, r; + struct lysp_revision rev; + + for (i = 1, r = 0; i < LY_ARRAY_COUNT(revs); i++) { + if (strcmp(revs[i].date, revs[r].date) > 0) { + r = i; + } + } + + if (r) { + /* the newest revision is not on position 0, switch them */ + memcpy(&rev, &revs[0], sizeof rev); + memcpy(&revs[0], &revs[r], sizeof rev); + memcpy(&revs[r], &rev, sizeof rev); + } +} + +LY_ERR +lysp_check_enum_name(struct lysp_ctx *ctx, const char *name, size_t name_len) +{ + if (!name_len) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Enum name must not be zero-length."); + return LY_EVALID; + } else if (isspace(name[0]) || isspace(name[name_len - 1])) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Enum name must not have any leading or trailing whitespaces (\"%.*s\").", + (int)name_len, name); + return LY_EVALID; + } else { + for (size_t u = 0; u < name_len; ++u) { + if (iscntrl(name[u])) { + LOGWRN(PARSER_CTX(ctx), "Control characters in enum name should be avoided (\"%.*s\", character number %d).", + (int)name_len, name, u + 1); + break; + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Learn built-in type from its name. + * + * @param[in] name Type name. + * @param[in] len Length of @p name. + * @return Built-in data type, ::LY_TYPE_UNKNOWN if none matches. + */ +static LY_DATA_TYPE +lysp_type_str2builtin(const char *name, size_t len) +{ + if (len >= 4) { /* otherwise it does not match any built-in type */ + if (name[0] == 'b') { + if (name[1] == 'i') { + if ((len == 6) && !strncmp(&name[2], "nary", 4)) { + return LY_TYPE_BINARY; + } else if ((len == 4) && !strncmp(&name[2], "ts", 2)) { + return LY_TYPE_BITS; + } + } else if ((len == 7) && !strncmp(&name[1], "oolean", 6)) { + return LY_TYPE_BOOL; + } + } else if (name[0] == 'd') { + if ((len == 9) && !strncmp(&name[1], "ecimal64", 8)) { + return LY_TYPE_DEC64; + } + } else if (name[0] == 'e') { + if ((len == 5) && !strncmp(&name[1], "mpty", 4)) { + return LY_TYPE_EMPTY; + } else if ((len == 11) && !strncmp(&name[1], "numeration", 10)) { + return LY_TYPE_ENUM; + } + } else if (name[0] == 'i') { + if (name[1] == 'n') { + if ((len == 4) && !strncmp(&name[2], "t8", 2)) { + return LY_TYPE_INT8; + } else if (len == 5) { + if (!strncmp(&name[2], "t16", 3)) { + return LY_TYPE_INT16; + } else if (!strncmp(&name[2], "t32", 3)) { + return LY_TYPE_INT32; + } else if (!strncmp(&name[2], "t64", 3)) { + return LY_TYPE_INT64; + } + } else if ((len == 19) && !strncmp(&name[2], "stance-identifier", 17)) { + return LY_TYPE_INST; + } + } else if ((len == 11) && !strncmp(&name[1], "dentityref", 10)) { + return LY_TYPE_IDENT; + } + } else if (name[0] == 'l') { + if ((len == 7) && !strncmp(&name[1], "eafref", 6)) { + return LY_TYPE_LEAFREF; + } + } else if (name[0] == 's') { + if ((len == 6) && !strncmp(&name[1], "tring", 5)) { + return LY_TYPE_STRING; + } + } else if (name[0] == 'u') { + if (name[1] == 'n') { + if ((len == 5) && !strncmp(&name[2], "ion", 3)) { + return LY_TYPE_UNION; + } + } else if ((name[1] == 'i') && (name[2] == 'n') && (name[3] == 't')) { + if ((len == 5) && (name[4] == '8')) { + return LY_TYPE_UINT8; + } else if (len == 6) { + if (!strncmp(&name[4], "16", 2)) { + return LY_TYPE_UINT16; + } else if (!strncmp(&name[4], "32", 2)) { + return LY_TYPE_UINT32; + } else if (!strncmp(&name[4], "64", 2)) { + return LY_TYPE_UINT64; + } + } + } + } + } + + return LY_TYPE_UNKNOWN; +} + +/** + * @brief Find a typedef in a sized array. + * + * @param[in] name Typedef name. + * @param[in] typedefs Sized array of typedefs. + * @return Found typedef, NULL if none. + */ +static const struct lysp_tpdf * +lysp_typedef_match(const char *name, const struct lysp_tpdf *typedefs) +{ + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_FOR(typedefs, u) { + if (!strcmp(name, typedefs[u].name)) { + /* match */ + return &typedefs[u]; + } + } + return NULL; +} + +LY_ERR +lysp_type_find(const char *id, struct lysp_node *start_node, const struct lysp_module *start_module, + const struct lysc_ext_instance *ext, LY_DATA_TYPE *type, const struct lysp_tpdf **tpdf, struct lysp_node **node) +{ + const char *str, *name; + struct lysp_tpdf *typedefs; + const struct lysp_tpdf *ext_typedefs; + const struct lys_module *mod; + const struct lysp_module *local_module; + LY_ARRAY_COUNT_TYPE u, v; + + assert(id); + assert(start_module); + assert(tpdf); + assert(node); + + *node = NULL; + str = strchr(id, ':'); + if (str) { + mod = ly_resolve_prefix(start_module->mod->ctx, id, str - id, LY_VALUE_SCHEMA, (void *)start_module); + local_module = mod ? mod->parsed : NULL; + name = str + 1; + *type = LY_TYPE_UNKNOWN; + } else { + local_module = start_module; + name = id; + + /* check for built-in types */ + *type = lysp_type_str2builtin(name, strlen(name)); + if (*type) { + *tpdf = NULL; + return LY_SUCCESS; + } + } + LY_CHECK_RET(!local_module, LY_ENOTFOUND); + + if (local_module == start_module) { + if (start_node) { + /* search typedefs in parent's nodes */ + for (*node = start_node; *node; *node = (*node)->parent) { + *tpdf = lysp_typedef_match(name, lysp_node_typedefs(*node)); + if (*tpdf) { + /* match */ + return LY_SUCCESS; + } + } + } + + if (ext) { + /* search typedefs directly in the extension */ + lyplg_ext_parsed_get_storage(ext, LY_STMT_TYPEDEF, sizeof ext_typedefs, (const void **)&ext_typedefs); + if ((*tpdf = lysp_typedef_match(name, ext_typedefs))) { + /* match */ + return LY_SUCCESS; + } + } + } + + /* go to main module if in submodule */ + local_module = local_module->mod->parsed; + + /* search in top-level typedefs */ + if (local_module->typedefs) { + LY_ARRAY_FOR(local_module->typedefs, u) { + if (!strcmp(name, local_module->typedefs[u].name)) { + /* match */ + *tpdf = &local_module->typedefs[u]; + return LY_SUCCESS; + } + } + } + + /* search in all submodules' typedefs */ + LY_ARRAY_FOR(local_module->includes, u) { + typedefs = local_module->includes[u].submodule->typedefs; + LY_ARRAY_FOR(typedefs, v) { + if (!strcmp(name, typedefs[v].name)) { + /* match */ + *tpdf = &typedefs[v]; + return LY_SUCCESS; + } + } + } + + return LY_ENOTFOUND; +} + +/** + * @brief Insert @p name to hash table and if @p name has already + * been added, then log an error. + * + * This function is used to detect duplicate names. + * + * @param[in,out] ctx Context to log the error. + * @param[in,out] ht Hash table with top-level names. + * @param[in] name Inserted top-level identifier. + * @param[in] statement The name of the statement type from which + * @p name originated (eg typedef, feature, ...). + * @param[in] err_detail Optional error specification. + * @return LY_ERR, but LY_EEXIST is mapped to LY_EVALID. + */ +static LY_ERR +lysp_check_dup_ht_insert(struct lysp_ctx *ctx, struct hash_table *ht, + const char *name, const char *statement, const char *err_detail) +{ + LY_ERR ret; + uint32_t hash; + + hash = dict_hash(name, strlen(name)); + ret = lyht_insert(ht, &name, hash, NULL); + if (ret == LY_EEXIST) { + if (err_detail) { + LOGVAL_PARSER(ctx, LY_VCODE_DUPIDENT2, name, statement, err_detail); + } else { + LOGVAL_PARSER(ctx, LY_VCODE_DUPIDENT, name, statement); + } + ret = LY_EVALID; + } + + return ret; +} + +/** + * @brief Check name of a new type to avoid name collisions. + * + * @param[in] ctx Parser context, module where the type is being defined is taken from here. + * @param[in] node Schema node where the type is being defined, NULL in case of a top-level typedef. + * @param[in] tpdf Typedef definition to check. + * @param[in,out] tpdfs_global Initialized hash table to store temporary data between calls. When the module's + * typedefs are checked, caller is supposed to free the table. + * @return LY_EVALID in case of collision, LY_SUCCESS otherwise. + */ +static LY_ERR +lysp_check_dup_typedef(struct lysp_ctx *ctx, struct lysp_node *node, const struct lysp_tpdf *tpdf, + struct hash_table *tpdfs_global) +{ + struct lysp_node *parent; + uint32_t hash; + size_t name_len; + const char *name; + LY_ARRAY_COUNT_TYPE u; + const struct lysp_tpdf *typedefs; + + assert(ctx); + assert(tpdf); + + name = tpdf->name; + name_len = strlen(name); + + if (lysp_type_str2builtin(name, name_len)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of typedef statement - name collision with a built-in type.", name); + return LY_EVALID; + } + + /* check locally scoped typedefs (avoid name shadowing) */ + if (node) { + typedefs = lysp_node_typedefs(node); + LY_ARRAY_FOR(typedefs, u) { + if (&typedefs[u] == tpdf) { + break; + } + if (!strcmp(name, typedefs[u].name)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of typedef statement - name collision with sibling type.", name); + return LY_EVALID; + } + } + /* search typedefs in parent's nodes */ + for (parent = node->parent; parent; parent = parent->parent) { + if (lysp_typedef_match(name, lysp_node_typedefs(parent))) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of typedef statement - name collision with another scoped type.", name); + return LY_EVALID; + } + } + } + + /* check collision with the top-level typedefs */ + if (node) { + hash = dict_hash(name, name_len); + if (!lyht_find(tpdfs_global, &name, hash, NULL)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of typedef statement - scoped type collide with a top-level type.", name); + return LY_EVALID; + } + } else { + LY_CHECK_RET(lysp_check_dup_ht_insert(ctx, tpdfs_global, name, "typedef", + "name collision with another top-level type")); + /* it is not necessary to test collision with the scoped types - in lysp_check_typedefs, all the + * top-level typedefs are inserted into the tables before the scoped typedefs, so the collision + * is detected in the first branch few lines above */ + } + + return LY_SUCCESS; +} + +/** + * @brief Compare identifiers. + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lysp_id_cmp(void *val1, void *val2, ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + char *id1, *id2; + + id1 = *(char **)val1; + id2 = *(char **)val2; + + return strcmp(id1, id2) == 0 ? 1 : 0; +} + +LY_ERR +lysp_check_dup_typedefs(struct lysp_ctx *ctx, struct lysp_module *mod) +{ + struct hash_table *ids_global; + const struct lysp_tpdf *typedefs; + LY_ARRAY_COUNT_TYPE u, v; + uint32_t i; + LY_ERR ret = LY_SUCCESS; + + /* check name collisions - typedefs and groupings */ + ids_global = lyht_new(LYHT_MIN_SIZE, sizeof(char *), lysp_id_cmp, NULL, 1); + LY_ARRAY_FOR(mod->typedefs, v) { + ret = lysp_check_dup_typedef(ctx, NULL, &mod->typedefs[v], ids_global); + LY_CHECK_GOTO(ret, cleanup); + } + LY_ARRAY_FOR(mod->includes, v) { + LY_ARRAY_FOR(mod->includes[v].submodule->typedefs, u) { + ret = lysp_check_dup_typedef(ctx, NULL, &mod->includes[v].submodule->typedefs[u], ids_global); + LY_CHECK_GOTO(ret, cleanup); + } + } + for (i = 0; i < ctx->tpdfs_nodes.count; ++i) { + typedefs = lysp_node_typedefs((struct lysp_node *)ctx->tpdfs_nodes.objs[i]); + LY_ARRAY_FOR(typedefs, u) { + ret = lysp_check_dup_typedef(ctx, (struct lysp_node *)ctx->tpdfs_nodes.objs[i], &typedefs[u], ids_global); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyht_free(ids_global); + return ret; +} + +static const struct lysp_node_grp * +lysp_grouping_match(const char *name, struct lysp_node *node) +{ + const struct lysp_node_grp *groupings, *grp_iter; + + groupings = lysp_node_groupings(node); + LY_LIST_FOR(groupings, grp_iter) { + if (!strcmp(name, grp_iter->name)) { + /* match */ + return grp_iter; + } + } + + return NULL; +} + +/** + * @brief Check name of a new grouping to avoid name collisions. + * + * @param[in] ctx Parser context, module where the grouping is being defined is taken from here. + * @param[in] node Schema node where the grouping is being defined, NULL in case of a top-level grouping. + * @param[in] grp Grouping definition to check. + * @param[in,out] grps_global Initialized hash table to store temporary data between calls. When the module's + * groupings are checked, caller is supposed to free the table. + * @return LY_EVALID in case of collision, LY_SUCCESS otherwise. + */ +static LY_ERR +lysp_check_dup_grouping(struct lysp_ctx *ctx, struct lysp_node *node, const struct lysp_node_grp *grp, + struct hash_table *grps_global) +{ + struct lysp_node *parent; + uint32_t hash; + size_t name_len; + const char *name; + const struct lysp_node_grp *groupings, *grp_iter; + + assert(ctx); + assert(grp); + + name = grp->name; + name_len = strlen(name); + + /* check locally scoped groupings (avoid name shadowing) */ + if (node) { + groupings = lysp_node_groupings(node); + LY_LIST_FOR(groupings, grp_iter) { + if (grp_iter == grp) { + break; + } + if (!strcmp(name, grp_iter->name)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of grouping statement - name collision with sibling grouping.", name); + return LY_EVALID; + } + } + /* search grouping in parent's nodes */ + for (parent = node->parent; parent; parent = parent->parent) { + if (lysp_grouping_match(name, parent)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of grouping statement - name collision with another scoped grouping.", name); + return LY_EVALID; + } + } + } + + /* check collision with the top-level groupings */ + if (node) { + hash = dict_hash(name, name_len); + if (!lyht_find(grps_global, &name, hash, NULL)) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, + "Duplicate identifier \"%s\" of grouping statement - scoped grouping collide with a top-level grouping.", name); + return LY_EVALID; + } + } else { + LY_CHECK_RET(lysp_check_dup_ht_insert(ctx, grps_global, name, "grouping", + "name collision with another top-level grouping")); + } + + return LY_SUCCESS; +} + +LY_ERR +lysp_check_dup_groupings(struct lysp_ctx *ctx, struct lysp_module *mod) +{ + struct hash_table *ids_global; + const struct lysp_node_grp *groupings, *grp_iter; + LY_ARRAY_COUNT_TYPE u; + uint32_t i; + LY_ERR ret = LY_SUCCESS; + + ids_global = lyht_new(LYHT_MIN_SIZE, sizeof(char *), lysp_id_cmp, NULL, 1); + LY_LIST_FOR(mod->groupings, grp_iter) { + ret = lysp_check_dup_grouping(ctx, NULL, grp_iter, ids_global); + LY_CHECK_GOTO(ret, cleanup); + } + LY_ARRAY_FOR(mod->includes, u) { + LY_LIST_FOR(mod->includes[u].submodule->groupings, grp_iter) { + ret = lysp_check_dup_grouping(ctx, NULL, grp_iter, ids_global); + LY_CHECK_GOTO(ret, cleanup); + } + } + for (i = 0; i < ctx->grps_nodes.count; ++i) { + groupings = lysp_node_groupings((struct lysp_node *)ctx->grps_nodes.objs[i]); + LY_LIST_FOR(groupings, grp_iter) { + ret = lysp_check_dup_grouping(ctx, (struct lysp_node *)ctx->grps_nodes.objs[i], grp_iter, ids_global); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyht_free(ids_global); + return ret; +} + +static ly_bool +ly_ptrequal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + void *ptr1 = *((void **)val1_p), *ptr2 = *((void **)val2_p); + + return ptr1 == ptr2 ? 1 : 0; +} + +LY_ERR +lysp_check_dup_features(struct lysp_ctx *ctx, struct lysp_module *mod) +{ + LY_ARRAY_COUNT_TYPE u; + struct hash_table *ht; + struct lysp_feature *f; + LY_ERR ret = LY_SUCCESS; + + ht = lyht_new(LYHT_MIN_SIZE, sizeof(void *), ly_ptrequal_cb, NULL, 1); + LY_CHECK_RET(!ht, LY_EMEM); + + /* add all module features into a hash table */ + LY_ARRAY_FOR(mod->features, struct lysp_feature, f) { + ret = lysp_check_dup_ht_insert(ctx, ht, f->name, "feature", + "name collision with another top-level feature"); + LY_CHECK_GOTO(ret, cleanup); + } + + /* add all submodule features into a hash table */ + LY_ARRAY_FOR(mod->includes, u) { + LY_ARRAY_FOR(mod->includes[u].submodule->features, struct lysp_feature, f) { + ret = lysp_check_dup_ht_insert(ctx, ht, f->name, "feature", + "name collision with another top-level feature"); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyht_free(ht); + return ret; +} + +LY_ERR +lysp_check_dup_identities(struct lysp_ctx *ctx, struct lysp_module *mod) +{ + LY_ARRAY_COUNT_TYPE u; + struct hash_table *ht; + struct lysp_ident *i; + LY_ERR ret = LY_SUCCESS; + + ht = lyht_new(LYHT_MIN_SIZE, sizeof(void *), ly_ptrequal_cb, NULL, 1); + LY_CHECK_RET(!ht, LY_EMEM); + + /* add all module identities into a hash table */ + LY_ARRAY_FOR(mod->identities, struct lysp_ident, i) { + ret = lysp_check_dup_ht_insert(ctx, ht, i->name, "identity", + "name collision with another top-level identity"); + LY_CHECK_GOTO(ret, cleanup); + } + + /* add all submodule identities into a hash table */ + LY_ARRAY_FOR(mod->includes, u) { + LY_ARRAY_FOR(mod->includes[u].submodule->identities, struct lysp_ident, i) { + ret = lysp_check_dup_ht_insert(ctx, ht, i->name, "identity", + "name collision with another top-level identity"); + LY_CHECK_GOTO(ret, cleanup); + } + } + +cleanup: + lyht_free(ht); + return ret; +} + +struct lysp_load_module_check_data { + const char *name; + const char *revision; + const char *path; + const char *submoduleof; +}; + +static LY_ERR +lysp_load_module_check(const struct ly_ctx *ctx, struct lysp_module *mod, struct lysp_submodule *submod, void *data) +{ + struct lysp_load_module_check_data *info = data; + const char *filename, *dot, *rev, *name; + uint8_t latest_revision; + size_t len; + struct lysp_revision *revs; + + name = mod ? mod->mod->name : submod->name; + revs = mod ? mod->revs : submod->revs; + latest_revision = mod ? mod->mod->latest_revision : submod->latest_revision; + + if (info->name) { + /* check name of the parsed model */ + if (strcmp(info->name, name)) { + LOGERR(ctx, LY_EINVAL, "Unexpected module \"%s\" parsed instead of \"%s\").", name, info->name); + return LY_EINVAL; + } + } + if (info->revision) { + /* check revision of the parsed model */ + if (!revs || strcmp(info->revision, revs[0].date)) { + LOGERR(ctx, LY_EINVAL, "Module \"%s\" parsed with the wrong revision (\"%s\" instead \"%s\").", name, + revs ? revs[0].date : "none", info->revision); + return LY_EINVAL; + } + } else if (!latest_revision) { + /* do not log, we just need to drop the schema and use the latest revision from the context */ + return LY_EEXIST; + } + if (submod) { + assert(info->submoduleof); + + /* check that the submodule belongs-to our module */ + if (strcmp(info->submoduleof, submod->mod->name)) { + LOGVAL(ctx, LYVE_REFERENCE, "Included \"%s\" submodule from \"%s\" belongs-to a different module \"%s\".", + submod->name, info->submoduleof, submod->mod->name); + return LY_EVALID; + } + /* check circular dependency */ + if (submod->parsing) { + LOGVAL(ctx, LYVE_REFERENCE, "A circular dependency (include) for module \"%s\".", submod->name); + return LY_EVALID; + } + } + if (info->path) { + /* check that name and revision match filename */ + filename = strrchr(info->path, '/'); + if (!filename) { + filename = info->path; + } else { + filename++; + } + /* name */ + len = strlen(name); + rev = strchr(filename, '@'); + dot = strrchr(info->path, '.'); + if (strncmp(filename, name, len) || + ((rev && (rev != &filename[len])) || (!rev && (dot != &filename[len])))) { + LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, name); + } + /* revision */ + if (rev) { + len = dot - ++rev; + if (!revs || (len != LY_REV_SIZE - 1) || strncmp(revs[0].date, rev, len)) { + LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename, + revs ? revs[0].date : "none"); + } + } + } + return LY_SUCCESS; +} + +/** + * @brief Parse a (sub)module from a local file and add into the context. + * + * This function does not check the presence of the (sub)module in context, it should be done before calling this function. + * + * @param[in] ctx libyang context where to work. + * @param[in] name Name of the (sub)module to load. + * @param[in] revision Optional revision of the (sub)module to load, if NULL the newest revision is being loaded. + * @param[in] main_ctx Parser context of the main module in case of loading submodule. + * @param[in] main_name Main module name in case of loading submodule. + * @param[in] required Module is required so error (even if the input file not found) are important. If 0, there is some + * backup and it is actually ok if the input data are not found. However, parser reports errors even in this case. + * @param[in,out] new_mods Set of all the new mods added to the context. Includes this module and all of its imports. + * @param[out] result Parsed YANG schema tree of the requested module (struct lys_module*) or submodule (struct lysp_submodule*). + * If it is a module, it is already in the context! + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +static LY_ERR +lys_parse_localfile(struct ly_ctx *ctx, const char *name, const char *revision, struct lysp_ctx *main_ctx, + const char *main_name, ly_bool required, struct ly_set *new_mods, void **result) +{ + struct ly_in *in; + char *filepath = NULL; + LYS_INFORMAT format; + void *mod = NULL; + LY_ERR ret = LY_SUCCESS; + struct lysp_load_module_check_data check_data = {0}; + + LY_CHECK_RET(lys_search_localfile(ly_ctx_get_searchdirs(ctx), !(ctx->flags & LY_CTX_DISABLE_SEARCHDIR_CWD), name, + revision, &filepath, &format)); + if (!filepath) { + if (required) { + LOGERR(ctx, LY_ENOTFOUND, "Data model \"%s%s%s\" not found in local searchdirs.", name, revision ? "@" : "", + revision ? revision : ""); + } + return LY_ENOTFOUND; + } + + LOGVRB("Loading schema from \"%s\" file.", filepath); + + /* get the (sub)module */ + LY_CHECK_ERR_GOTO(ret = ly_in_new_filepath(filepath, 0, &in), + LOGERR(ctx, ret, "Unable to create input handler for filepath %s.", filepath), cleanup); + check_data.name = name; + check_data.revision = revision; + check_data.path = filepath; + check_data.submoduleof = main_name; + if (main_ctx) { + ret = lys_parse_submodule(ctx, in, format, main_ctx, lysp_load_module_check, &check_data, new_mods, + (struct lysp_submodule **)&mod); + } else { + ret = lys_parse_in(ctx, in, format, lysp_load_module_check, &check_data, new_mods, (struct lys_module **)&mod); + + } + ly_in_free(in, 1); + LY_CHECK_GOTO(ret, cleanup); + + *result = mod; + + /* success */ + +cleanup: + free(filepath); + return ret; +} + +/** + * @brief Load module from searchdirs or from callback. + * + * @param[in] ctx libyang context where to work. + * @param[in] name Name of module to load. + * @param[in] revision Revision of module to load. + * @param[in] mod_latest Module with the latest revision found in context, otherwise set to NULL. + * @param[in,out] new_mods Set of all the new mods added to the context. Includes this module and all of its imports. + * @param[out] mod Loaded module. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +static LY_ERR +lys_parse_load_from_clb_or_file(struct ly_ctx *ctx, const char *name, const char *revision, + struct lys_module *mod_latest, struct ly_set *new_mods, struct lys_module **mod) +{ + const char *module_data = NULL; + LYS_INFORMAT format = LYS_IN_UNKNOWN; + + void (*module_data_free)(void *module_data, void *user_data) = NULL; + struct lysp_load_module_check_data check_data = {0}; + struct ly_in *in; + + *mod = NULL; + + if (mod_latest && (!ctx->imp_clb || (mod_latest->latest_revision & LYS_MOD_LATEST_IMPCLB)) && + ((ctx->flags & LY_CTX_DISABLE_SEARCHDIRS) || (mod_latest->latest_revision & LYS_MOD_LATEST_SEARCHDIRS))) { + /* we are not able to find a newer revision */ + return LY_SUCCESS; + } + + if (!(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) { +search_clb: + /* check there is a callback and should be called */ + if (ctx->imp_clb && (!mod_latest || !(mod_latest->latest_revision & LYS_MOD_LATEST_IMPCLB))) { + if (!ctx->imp_clb(name, revision, NULL, NULL, ctx->imp_clb_data, &format, &module_data, &module_data_free)) { + LY_CHECK_RET(ly_in_new_memory(module_data, &in)); + check_data.name = name; + check_data.revision = revision; + lys_parse_in(ctx, in, format, lysp_load_module_check, &check_data, new_mods, mod); + ly_in_free(in, 0); + if (module_data_free) { + module_data_free((void *)module_data, ctx->imp_clb_data); + } + } + } + if (*mod && !revision) { + /* we got the latest revision module from the callback */ + (*mod)->latest_revision |= LYS_MOD_LATEST_IMPCLB; + } else if (!*mod && !(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) { + goto search_file; + } + } else { +search_file: + /* check we can use searchdirs and that we should */ + if (!(ctx->flags & LY_CTX_DISABLE_SEARCHDIRS) && + (!mod_latest || !(mod_latest->latest_revision & LYS_MOD_LATEST_SEARCHDIRS))) { + lys_parse_localfile(ctx, name, revision, NULL, NULL, mod_latest ? 0 : 1, new_mods, (void **)mod); + } + if (*mod && !revision) { + /* we got the latest revision module in the searchdirs */ + (*mod)->latest_revision |= LYS_MOD_LATEST_IMPCLB; + } else if (!*mod && (ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) { + goto search_clb; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Get module without revision according to priorities. + * + * 1. Search for the module with LYS_MOD_IMPORTED_REV. + * 2. Search for the implemented module. + * 3. Search for the latest module in the context. + * + * @param[in] ctx libyang context where module is searched. + * @param[in] name Name of the searched module. + * @return Found module from context or NULL. + */ +static struct lys_module * +lys_get_module_without_revision(struct ly_ctx *ctx, const char *name) +{ + struct lys_module *mod, *mod_impl; + uint32_t index; + + /* Try to find module with LYS_MOD_IMPORTED_REV flag. */ + index = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &index))) { + if (!strcmp(mod->name, name) && (mod->latest_revision & LYS_MOD_IMPORTED_REV)) { + break; + } + } + + /* Try to find the implemented module. */ + mod_impl = ly_ctx_get_module_implemented(ctx, name); + if (mod && mod_impl) { + LOGVRB("Implemented module \"%s@%s\" is not used for import, revision \"%s\" is imported instead.", + mod_impl->name, mod_impl->revision, mod->revision); + return mod; + } else if (mod_impl) { + return mod_impl; + } + + /* Try to find the latest module in the current context. */ + mod = ly_ctx_get_module_latest(ctx, name); + + return mod; +} + +/** + * @brief Check if a circular dependency exists between modules. + * + * @param[in] ctx libyang context for log an error. + * @param[in,out] mod Examined module which is set to NULL + * if the circular dependency is detected. + * @return LY_SUCCESS if no circular dependecy is detected, + * otherwise LY_EVALID. + */ +static LY_ERR +lys_check_circular_dependency(struct ly_ctx *ctx, struct lys_module **mod) +{ + if ((*mod) && (*mod)->parsed->parsing) { + LOGVAL(ctx, LYVE_REFERENCE, "A circular dependency (import) for module \"%s\".", (*mod)->name); + *mod = NULL; + return LY_EVALID; + } + + return LY_SUCCESS; +} + +LY_ERR +lys_parse_load(struct ly_ctx *ctx, const char *name, const char *revision, struct ly_set *new_mods, + struct lys_module **mod) +{ + struct lys_module *mod_latest = NULL; + + assert(mod && new_mods); + + /* + * Try to get the module from the context. + */ + if (revision) { + /* Get the specific revision. */ + *mod = ly_ctx_get_module(ctx, name, revision); + } else { + /* Get the requested module in a suitable revision in the context. */ + *mod = lys_get_module_without_revision(ctx, name); + if (*mod && !(*mod)->implemented && !((*mod)->latest_revision & LYS_MOD_IMPORTED_REV)) { + /* Let us now search with callback and searchpaths to check + * if there is newer revision outside the context. + */ + mod_latest = *mod; + *mod = NULL; + } + } + + if (!*mod) { + /* No suitable module in the context, try to load it. */ + LY_CHECK_RET(lys_parse_load_from_clb_or_file(ctx, name, revision, mod_latest, new_mods, mod)); + if (!*mod && !mod_latest) { + LOGVAL(ctx, LYVE_REFERENCE, "Loading \"%s\" module failed.", name); + return LY_EVALID; + } + + /* Update the latest_revision flag - here we have selected the latest available schema, + * consider that even the callback provides correct latest revision. + */ + if (!*mod) { + LOGVRB("Newer revision than \"%s@%s\" not found, using this as the latest revision.", + mod_latest->name, mod_latest->revision); + assert(mod_latest->latest_revision & LYS_MOD_LATEST_REV); + mod_latest->latest_revision |= LYS_MOD_LATEST_SEARCHDIRS; + *mod = mod_latest; + } else if (*mod && !revision && ((*mod)->latest_revision & LYS_MOD_LATEST_REV)) { + (*mod)->latest_revision |= LYS_MOD_LATEST_SEARCHDIRS; + } + } + + /* Checking the circular dependence of imported modules. */ + LY_CHECK_RET(lys_check_circular_dependency(ctx, mod)); + + return LY_SUCCESS; +} + +LY_ERR +lysp_check_stringchar(struct lysp_ctx *ctx, uint32_t c) +{ + if (!is_yangutf8char(c)) { + LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, c); + return LY_EVALID; + } + return LY_SUCCESS; +} + +LY_ERR +lysp_check_identifierchar(struct lysp_ctx *ctx, uint32_t c, ly_bool first, uint8_t *prefix) +{ + if (first || (prefix && ((*prefix) == 1))) { + if (!is_yangidentstartchar(c)) { + if ((c < UCHAR_MAX) && isprint(c)) { + if (ctx) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Invalid identifier first character '%c' (0x%04x).", (char)c, c); + } + } else { + if (ctx) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Invalid identifier first character 0x%04x.", c); + } + } + return LY_EVALID; + } + if (prefix) { + if (first) { + (*prefix) = 0; + } else { + (*prefix) = 2; + } + } + } else if ((c == ':') && prefix && ((*prefix) == 0)) { + (*prefix) = 1; + } else if (!is_yangidentchar(c)) { + if (ctx) { + LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Invalid identifier character '%c' (0x%04x).", (char)c, c); + } + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Try to find the parsed submodule in main module for the given include record. + * + * @param[in] pctx main parser context + * @param[in] inc The include record with missing parsed submodule. According to include info try to find + * the corresponding parsed submodule in main module's includes. + * @return LY_SUCCESS - the parsed submodule was found and inserted into the @p inc record + * @return LY_ENOT - the parsed module was not found. + * @return LY_EVALID - YANG rule violation + */ +static LY_ERR +lysp_main_pmod_get_submodule(struct lysp_ctx *pctx, struct lysp_include *inc) +{ + LY_ARRAY_COUNT_TYPE i; + struct lysp_module *main_pmod = PARSER_CUR_PMOD(pctx)->mod->parsed; + + LY_ARRAY_FOR(main_pmod->includes, i) { + if (strcmp(main_pmod->includes[i].name, inc->name)) { + continue; + } + + if (inc->rev[0] && strncmp(inc->rev, main_pmod->includes[i].rev, LY_REV_SIZE)) { + LOGVAL(PARSER_CTX(pctx), LYVE_REFERENCE, + "Submodule %s includes different revision (%s) of the submodule %s:%s included by the main module %s.", + ((struct lysp_submodule *)PARSER_CUR_PMOD(pctx))->name, inc->rev, + main_pmod->includes[i].name, main_pmod->includes[i].rev, main_pmod->mod->name); + return LY_EVALID; + } + + inc->submodule = main_pmod->includes[i].submodule; + return inc->submodule ? LY_SUCCESS : LY_ENOT; + } + + if (main_pmod->version == LYS_VERSION_1_1) { + LOGVAL(PARSER_CTX(pctx), LYVE_REFERENCE, + "YANG 1.1 requires all submodules to be included from main module. " + "But submodule \"%s\" includes submodule \"%s\" which is not included by main module \"%s\".", + ((struct lysp_submodule *)PARSER_CUR_PMOD(pctx))->name, inc->name, main_pmod->mod->name); + return LY_EVALID; + } else { + return LY_ENOT; + } +} + +/** + * @brief Try to find the parsed submodule in currenlty parsed modules for the given include record. + * + * @param[in] pctx main parser context + * @param[in] inc The include record with missing parsed submodule. + * @return LY_SUCCESS - the parsed submodule was found and inserted into the @p inc record + * @return LY_ENOT - the parsed module was not found. + * @return LY_EVALID - YANG rule violation + */ +static LY_ERR +lysp_parsed_mods_get_submodule(struct lysp_ctx *pctx, struct lysp_include *inc) +{ + uint32_t i; + struct lysp_submodule *submod; + + for (i = 0; i < pctx->parsed_mods->count - 1; ++i) { + submod = pctx->parsed_mods->objs[i]; + if (!submod->is_submod) { + continue; + } + + if (strcmp(submod->name, inc->name)) { + continue; + } + + if (inc->rev[0] && submod->revs && strncmp(inc->rev, submod->revs[0].date, LY_REV_SIZE)) { + LOGVAL(PARSER_CTX(pctx), LYVE_REFERENCE, + "Submodule %s includes different revision (%s) of the submodule %s:%s included by the main module %s.", + ((struct lysp_submodule *)PARSER_CUR_PMOD(pctx))->name, inc->rev, + submod->name, submod->revs[0].date, PARSER_CUR_PMOD(pctx)->mod->name); + return LY_EVALID; + } + + inc->submodule = submod; + return LY_SUCCESS; + } + + return LY_ENOT; +} + +/** + * @brief Make the copy of the given include record into the main module. + * + * YANG 1.0 does not require the main module to include all the submodules. Therefore, parsing submodules can cause + * reallocating and extending the includes array in the main module by the submodules included only in submodules. + * + * @param[in] pctx main parser context + * @param[in] inc Include record to copy into main module taken from @p pctx. + * @return LY_ERR value. + */ +static LY_ERR +lysp_inject_submodule(struct lysp_ctx *pctx, struct lysp_include *inc) +{ + LY_ARRAY_COUNT_TYPE i; + struct lysp_include *inc_new, *inc_tofill = NULL; + struct lysp_module *main_pmod = PARSER_CUR_PMOD(pctx)->mod->parsed; + + /* first, try to find the corresponding record with missing parsed submodule */ + LY_ARRAY_FOR(main_pmod->includes, i) { + if (strcmp(main_pmod->includes[i].name, inc->name)) { + continue; + } + inc_tofill = &main_pmod->includes[i]; + break; + } + + if (inc_tofill) { + inc_tofill->submodule = inc->submodule; + } else { + LY_ARRAY_NEW_RET(PARSER_CTX(pctx), main_pmod->includes, inc_new, LY_EMEM); + + inc_new->submodule = inc->submodule; + DUP_STRING_RET(PARSER_CTX(pctx), inc->name, inc_new->name); + DUP_STRING_RET(PARSER_CTX(pctx), inc->dsc, inc_new->dsc); + DUP_STRING_RET(PARSER_CTX(pctx), inc->ref, inc_new->ref); + /* TODO duplicate extensions */ + memcpy(inc_new->rev, inc->rev, LY_REV_SIZE); + inc_new->injected = 1; + } + return LY_SUCCESS; +} + +LY_ERR +lysp_load_submodules(struct lysp_ctx *pctx, struct lysp_module *pmod, struct ly_set *new_mods) +{ + LY_ARRAY_COUNT_TYPE u; + struct ly_ctx *ctx = PARSER_CTX(pctx); + + LY_ARRAY_FOR(pmod->includes, u) { + LY_ERR ret = LY_SUCCESS, r; + struct lysp_submodule *submod = NULL; + struct lysp_include *inc = &pmod->includes[u]; + + if (inc->submodule) { + continue; + } + + if (pmod->is_submod) { + /* try to find the submodule in the main module or its submodules */ + ret = lysp_main_pmod_get_submodule(pctx, inc); + LY_CHECK_RET(ret != LY_ENOT, ret); + } + + /* try to use currently parsed submodule */ + r = lysp_parsed_mods_get_submodule(pctx, inc); + LY_CHECK_RET(r != LY_ENOT, r); + + /* submodule not present in the main module, get the input data and parse it */ + if (!(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) { +search_clb: + if (ctx->imp_clb) { + const char *submodule_data = NULL; + LYS_INFORMAT format = LYS_IN_UNKNOWN; + + void (*submodule_data_free)(void *module_data, void *user_data) = NULL; + struct lysp_load_module_check_data check_data = {0}; + struct ly_in *in; + + if (ctx->imp_clb(PARSER_CUR_PMOD(pctx)->mod->name, NULL, inc->name, + inc->rev[0] ? inc->rev : NULL, ctx->imp_clb_data, + &format, &submodule_data, &submodule_data_free) == LY_SUCCESS) { + LY_CHECK_RET(ly_in_new_memory(submodule_data, &in)); + check_data.name = inc->name; + check_data.revision = inc->rev[0] ? inc->rev : NULL; + check_data.submoduleof = PARSER_CUR_PMOD(pctx)->mod->name; + lys_parse_submodule(ctx, in, format, pctx, lysp_load_module_check, &check_data, new_mods, &submod); + + /* update inc pointer - parsing another (YANG 1.0) submodule can cause injecting + * submodule's include into main module, where it is missing */ + inc = &pmod->includes[u]; + + ly_in_free(in, 0); + if (submodule_data_free) { + submodule_data_free((void *)submodule_data, ctx->imp_clb_data); + } + } + } + if (!submod && !(ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) { + goto search_file; + } + } else { +search_file: + if (!(ctx->flags & LY_CTX_DISABLE_SEARCHDIRS)) { + /* submodule was not received from the callback or there is no callback set */ + lys_parse_localfile(ctx, inc->name, inc->rev[0] ? inc->rev : NULL, pctx->main_ctx, + PARSER_CUR_PMOD(pctx->main_ctx)->mod->name, 1, new_mods, (void **)&submod); + + /* update inc pointer - parsing another (YANG 1.0) submodule can cause injecting + * submodule's include into main module, where it is missing */ + inc = &pmod->includes[u]; + } + if (!submod && (ctx->flags & LY_CTX_PREFER_SEARCHDIRS)) { + goto search_clb; + } + } + if (submod) { + if (!inc->rev[0] && (submod->latest_revision == 1)) { + /* update the latest_revision flag - here we have selected the latest available schema, + * consider that even the callback provides correct latest revision */ + submod->latest_revision = 2; + } + + inc->submodule = submod; + if (ret == LY_ENOT) { + /* the submodule include is not present in YANG 1.0 main module - add it there */ + LY_CHECK_RET(lysp_inject_submodule(pctx, &pmod->includes[u])); + } + } + if (!inc->submodule) { + LOGVAL(ctx, LYVE_REFERENCE, "Including \"%s\" submodule into \"%s\" failed.", inc->name, + PARSER_CUR_PMOD(pctx)->is_submod ? ((struct lysp_submodule *)PARSER_CUR_PMOD(pctx))->name : + PARSER_CUR_PMOD(pctx)->mod->name); + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +LIBYANG_API_DEF const struct lysc_when * +lysc_has_when(const struct lysc_node *node) +{ + struct lysc_when **when; + + if (!node) { + return NULL; + } + + do { + when = lysc_node_when(node); + if (when) { + return when[0]; + } + node = node->parent; + } while (node && (node->nodetype & (LYS_CASE | LYS_CHOICE))); + + return NULL; +} + +LIBYANG_API_DEF const struct lys_module * +lysc_owner_module(const struct lysc_node *node) +{ + if (!node) { + return NULL; + } + + for ( ; node->parent; node = node->parent) {} + return node->module; +} + +LIBYANG_API_DEF const char * +lys_nodetype2str(uint16_t nodetype) +{ + switch (nodetype) { + case LYS_CONTAINER: + return "container"; + case LYS_CHOICE: + return "choice"; + case LYS_LEAF: + return "leaf"; + case LYS_LEAFLIST: + return "leaf-list"; + case LYS_LIST: + return "list"; + case LYS_ANYXML: + return "anyxml"; + case LYS_ANYDATA: + return "anydata"; + case LYS_CASE: + return "case"; + case LYS_RPC: + return "RPC"; + case LYS_ACTION: + return "action"; + case LYS_NOTIF: + return "notification"; + case LYS_USES: + return "uses"; + default: + return "unknown"; + } +} + +const char * +lys_datatype2str(LY_DATA_TYPE basetype) +{ + switch (basetype) { + case LY_TYPE_BINARY: + return "binary"; + case LY_TYPE_UINT8: + return "uint8"; + case LY_TYPE_UINT16: + return "uint16"; + case LY_TYPE_UINT32: + return "uint32"; + case LY_TYPE_UINT64: + return "uint64"; + case LY_TYPE_STRING: + return "string"; + case LY_TYPE_BITS: + return "bits"; + case LY_TYPE_BOOL: + return "boolean"; + case LY_TYPE_DEC64: + return "decimal64"; + case LY_TYPE_EMPTY: + return "empty"; + case LY_TYPE_ENUM: + return "enumeration"; + case LY_TYPE_IDENT: + return "identityref"; + case LY_TYPE_INST: + return "instance-identifier"; + case LY_TYPE_LEAFREF: + return "leafref"; + case LY_TYPE_UNION: + return "union"; + case LY_TYPE_INT8: + return "int8"; + case LY_TYPE_INT16: + return "int16"; + case LY_TYPE_INT32: + return "int32"; + case LY_TYPE_INT64: + return "int64"; + default: + return "unknown"; + } +} + +LIBYANG_API_DEF const struct lysp_tpdf * +lysp_node_typedefs(const struct lysp_node *node) +{ + switch (node->nodetype) { + case LYS_CONTAINER: + return ((struct lysp_node_container *)node)->typedefs; + case LYS_LIST: + return ((struct lysp_node_list *)node)->typedefs; + case LYS_GROUPING: + return ((struct lysp_node_grp *)node)->typedefs; + case LYS_RPC: + case LYS_ACTION: + return ((struct lysp_node_action *)node)->typedefs; + case LYS_INPUT: + case LYS_OUTPUT: + return ((struct lysp_node_action_inout *)node)->typedefs; + case LYS_NOTIF: + return ((struct lysp_node_notif *)node)->typedefs; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysp_node_grp * +lysp_node_groupings(const struct lysp_node *node) +{ + switch (node->nodetype) { + case LYS_CONTAINER: + return ((struct lysp_node_container *)node)->groupings; + case LYS_LIST: + return ((struct lysp_node_list *)node)->groupings; + case LYS_GROUPING: + return ((struct lysp_node_grp *)node)->groupings; + case LYS_RPC: + case LYS_ACTION: + return ((struct lysp_node_action *)node)->groupings; + case LYS_INPUT: + case LYS_OUTPUT: + return ((struct lysp_node_action_inout *)node)->groupings; + case LYS_NOTIF: + return ((struct lysp_node_notif *)node)->groupings; + default: + return NULL; + } +} + +struct lysp_node_action ** +lysp_node_actions_p(struct lysp_node *node) +{ + assert(node); + + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysp_node_container *)node)->actions; + case LYS_LIST: + return &((struct lysp_node_list *)node)->actions; + case LYS_GROUPING: + return &((struct lysp_node_grp *)node)->actions; + case LYS_AUGMENT: + return &((struct lysp_node_augment *)node)->actions; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysp_node_action * +lysp_node_actions(const struct lysp_node *node) +{ + struct lysp_node_action **actions; + + actions = lysp_node_actions_p((struct lysp_node *)node); + if (actions) { + return *actions; + } else { + return NULL; + } +} + +struct lysp_node_notif ** +lysp_node_notifs_p(struct lysp_node *node) +{ + assert(node); + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysp_node_container *)node)->notifs; + case LYS_LIST: + return &((struct lysp_node_list *)node)->notifs; + case LYS_GROUPING: + return &((struct lysp_node_grp *)node)->notifs; + case LYS_AUGMENT: + return &((struct lysp_node_augment *)node)->notifs; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysp_node_notif * +lysp_node_notifs(const struct lysp_node *node) +{ + struct lysp_node_notif **notifs; + + notifs = lysp_node_notifs_p((struct lysp_node *)node); + if (notifs) { + return *notifs; + } else { + return NULL; + } +} + +struct lysp_node ** +lysp_node_child_p(struct lysp_node *node) +{ + assert(node); + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysp_node_container *)node)->child; + case LYS_CHOICE: + return &((struct lysp_node_choice *)node)->child; + case LYS_LIST: + return &((struct lysp_node_list *)node)->child; + case LYS_CASE: + return &((struct lysp_node_case *)node)->child; + case LYS_GROUPING: + return &((struct lysp_node_grp *)node)->child; + case LYS_AUGMENT: + return &((struct lysp_node_augment *)node)->child; + case LYS_INPUT: + case LYS_OUTPUT: + return &((struct lysp_node_action_inout *)node)->child; + case LYS_NOTIF: + return &((struct lysp_node_notif *)node)->child; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysp_node * +lysp_node_child(const struct lysp_node *node) +{ + struct lysp_node **child; + + if (!node) { + return NULL; + } + + child = lysp_node_child_p((struct lysp_node *)node); + if (child) { + return *child; + } else { + return NULL; + } +} + +struct lysp_restr ** +lysp_node_musts_p(const struct lysp_node *node) +{ + if (!node) { + return NULL; + } + + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysp_node_container *)node)->musts; + case LYS_LEAF: + return &((struct lysp_node_leaf *)node)->musts; + case LYS_LEAFLIST: + return &((struct lysp_node_leaflist *)node)->musts; + case LYS_LIST: + return &((struct lysp_node_list *)node)->musts; + case LYS_ANYXML: + case LYS_ANYDATA: + return &((struct lysp_node_anydata *)node)->musts; + case LYS_NOTIF: + return &((struct lysp_node_notif *)node)->musts; + case LYS_INPUT: + case LYS_OUTPUT: + return &((struct lysp_node_action_inout *)node)->musts; + default: + return NULL; + } +} + +struct lysp_restr * +lysp_node_musts(const struct lysp_node *node) +{ + struct lysp_restr **musts; + + musts = lysp_node_musts_p(node); + if (musts) { + return *musts; + } else { + return NULL; + } +} + +struct lysp_when ** +lysp_node_when_p(const struct lysp_node *node) +{ + if (!node) { + return NULL; + } + + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysp_node_container *)node)->when; + case LYS_CHOICE: + return &((struct lysp_node_choice *)node)->when; + case LYS_LEAF: + return &((struct lysp_node_leaf *)node)->when; + case LYS_LEAFLIST: + return &((struct lysp_node_leaflist *)node)->when; + case LYS_LIST: + return &((struct lysp_node_list *)node)->when; + case LYS_ANYXML: + case LYS_ANYDATA: + return &((struct lysp_node_anydata *)node)->when; + case LYS_CASE: + return &((struct lysp_node_case *)node)->when; + case LYS_USES: + return &((struct lysp_node_uses *)node)->when; + case LYS_AUGMENT: + return &((struct lysp_node_augment *)node)->when; + default: + return NULL; + } +} + +struct lysp_when * +lysp_node_when(const struct lysp_node *node) +{ + struct lysp_when **when; + + when = lysp_node_when_p(node); + if (when) { + return *when; + } else { + return NULL; + } +} + +struct lysc_node_action ** +lysc_node_actions_p(struct lysc_node *node) +{ + assert(node); + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysc_node_container *)node)->actions; + case LYS_LIST: + return &((struct lysc_node_list *)node)->actions; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysc_node_action * +lysc_node_actions(const struct lysc_node *node) +{ + struct lysc_node_action **actions; + + actions = lysc_node_actions_p((struct lysc_node *)node); + if (actions) { + return *actions; + } else { + return NULL; + } +} + +struct lysc_node_notif ** +lysc_node_notifs_p(struct lysc_node *node) +{ + assert(node); + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysc_node_container *)node)->notifs; + case LYS_LIST: + return &((struct lysc_node_list *)node)->notifs; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysc_node_notif * +lysc_node_notifs(const struct lysc_node *node) +{ + struct lysc_node_notif **notifs; + + notifs = lysc_node_notifs_p((struct lysc_node *)node); + if (notifs) { + return *notifs; + } else { + return NULL; + } +} + +struct lysc_node ** +lysc_node_child_p(const struct lysc_node *node) +{ + assert(node && !(node->nodetype & (LYS_RPC | LYS_ACTION))); + + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysc_node_container *)node)->child; + case LYS_CHOICE: + return (struct lysc_node **)&((struct lysc_node_choice *)node)->cases; + case LYS_CASE: + return &((struct lysc_node_case *)node)->child; + case LYS_LIST: + return &((struct lysc_node_list *)node)->child; + case LYS_INPUT: + case LYS_OUTPUT: + return &((struct lysc_node_action_inout *)node)->child; + case LYS_NOTIF: + return &((struct lysc_node_notif *)node)->child; + default: + return NULL; + } +} + +LIBYANG_API_DEF const struct lysc_node * +lysc_node_child(const struct lysc_node *node) +{ + struct lysc_node **child; + + if (!node) { + return NULL; + } + + if (node->nodetype & (LYS_RPC | LYS_ACTION)) { + return &((struct lysc_node_action *)node)->input.node; + } else { + child = lysc_node_child_p(node); + if (child) { + return *child; + } + } + + return NULL; +} + +struct lysc_must ** +lysc_node_musts_p(const struct lysc_node *node) +{ + if (!node) { + return NULL; + } + + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysc_node_container *)node)->musts; + case LYS_LEAF: + return &((struct lysc_node_leaf *)node)->musts; + case LYS_LEAFLIST: + return &((struct lysc_node_leaflist *)node)->musts; + case LYS_LIST: + return &((struct lysc_node_list *)node)->musts; + case LYS_ANYXML: + case LYS_ANYDATA: + return &((struct lysc_node_anydata *)node)->musts; + case LYS_NOTIF: + return &((struct lysc_node_notif *)node)->musts; + case LYS_INPUT: + case LYS_OUTPUT: + return &((struct lysc_node_action_inout *)node)->musts; + default: + return NULL; + } +} + +LIBYANG_API_DEF struct lysc_must * +lysc_node_musts(const struct lysc_node *node) +{ + struct lysc_must **must_p; + + must_p = lysc_node_musts_p(node); + if (must_p) { + return *must_p; + } else { + return NULL; + } +} + +struct lysc_when *** +lysc_node_when_p(const struct lysc_node *node) +{ + if (!node) { + return NULL; + } + + switch (node->nodetype) { + case LYS_CONTAINER: + return &((struct lysc_node_container *)node)->when; + case LYS_CHOICE: + return &((struct lysc_node_choice *)node)->when; + case LYS_LEAF: + return &((struct lysc_node_leaf *)node)->when; + case LYS_LEAFLIST: + return &((struct lysc_node_leaflist *)node)->when; + case LYS_LIST: + return &((struct lysc_node_list *)node)->when; + case LYS_ANYXML: + case LYS_ANYDATA: + return &((struct lysc_node_anydata *)node)->when; + case LYS_CASE: + return &((struct lysc_node_case *)node)->when; + case LYS_NOTIF: + return &((struct lysc_node_notif *)node)->when; + case LYS_RPC: + case LYS_ACTION: + return &((struct lysc_node_action *)node)->when; + default: + return NULL; + } +} + +LIBYANG_API_DEF struct lysc_when ** +lysc_node_when(const struct lysc_node *node) +{ + struct lysc_when ***when_p; + + when_p = lysc_node_when_p(node); + if (when_p) { + return *when_p; + } else { + return NULL; + } +} + +enum ly_stmt +lysp_match_kw(struct ly_in *in, uint64_t *indent) +{ +/** + * @brief Move the input by COUNT items. Also updates the indent value in yang parser context + * @param[in] COUNT number of items for which the DATA pointer is supposed to move on. + * + * *INDENT-OFF* + */ +#define MOVE_IN(COUNT) \ + ly_in_skip(in, COUNT); \ + if (indent) { \ + (*indent)+=COUNT; \ + } +#define IF_KW(STR, LEN, STMT) \ + if (!strncmp(in->current, STR, LEN)) { \ + MOVE_IN(LEN); \ + (*kw)=STMT; \ + } +#define IF_KW_PREFIX(STR, LEN) \ + if (!strncmp(in->current, STR, LEN)) { \ + MOVE_IN(LEN); +#define IF_KW_PREFIX_END \ + } + + const char *start = in->current; + enum ly_stmt result = LY_STMT_NONE; + enum ly_stmt *kw = &result; + /* read the keyword itself */ + switch (in->current[0]) { + case 'a': + MOVE_IN(1); + IF_KW("rgument", 7, LY_STMT_ARGUMENT) + else IF_KW("ugment", 6, LY_STMT_AUGMENT) + else IF_KW("ction", 5, LY_STMT_ACTION) + else IF_KW_PREFIX("ny", 2) + IF_KW("data", 4, LY_STMT_ANYDATA) + else IF_KW("xml", 3, LY_STMT_ANYXML) + IF_KW_PREFIX_END + break; + case 'b': + MOVE_IN(1); + IF_KW("ase", 3, LY_STMT_BASE) + else IF_KW("elongs-to", 9, LY_STMT_BELONGS_TO) + else IF_KW("it", 2, LY_STMT_BIT) + break; + case 'c': + MOVE_IN(1); + IF_KW("ase", 3, LY_STMT_CASE) + else IF_KW("hoice", 5, LY_STMT_CHOICE) + else IF_KW_PREFIX("on", 2) + IF_KW("fig", 3, LY_STMT_CONFIG) + else IF_KW_PREFIX("ta", 2) + IF_KW("ct", 2, LY_STMT_CONTACT) + else IF_KW("iner", 4, LY_STMT_CONTAINER) + IF_KW_PREFIX_END + IF_KW_PREFIX_END + break; + case 'd': + MOVE_IN(1); + IF_KW_PREFIX("e", 1) + IF_KW("fault", 5, LY_STMT_DEFAULT) + else IF_KW("scription", 9, LY_STMT_DESCRIPTION) + else IF_KW_PREFIX("viat", 4) + IF_KW("e", 1, LY_STMT_DEVIATE) + else IF_KW("ion", 3, LY_STMT_DEVIATION) + IF_KW_PREFIX_END + IF_KW_PREFIX_END + break; + case 'e': + MOVE_IN(1); + IF_KW("num", 3, LY_STMT_ENUM) + else IF_KW_PREFIX("rror-", 5) + IF_KW("app-tag", 7, LY_STMT_ERROR_APP_TAG) + else IF_KW("message", 7, LY_STMT_ERROR_MESSAGE) + IF_KW_PREFIX_END + else IF_KW("xtension", 8, LY_STMT_EXTENSION) + break; + case 'f': + MOVE_IN(1); + IF_KW("eature", 6, LY_STMT_FEATURE) + else IF_KW("raction-digits", 14, LY_STMT_FRACTION_DIGITS) + break; + case 'g': + MOVE_IN(1); + IF_KW("rouping", 7, LY_STMT_GROUPING) + break; + case 'i': + MOVE_IN(1); + IF_KW("dentity", 7, LY_STMT_IDENTITY) + else IF_KW("f-feature", 9, LY_STMT_IF_FEATURE) + else IF_KW("mport", 5, LY_STMT_IMPORT) + else IF_KW_PREFIX("n", 1) + IF_KW("clude", 5, LY_STMT_INCLUDE) + else IF_KW("put", 3, LY_STMT_INPUT) + IF_KW_PREFIX_END + break; + case 'k': + MOVE_IN(1); + IF_KW("ey", 2, LY_STMT_KEY) + break; + case 'l': + MOVE_IN(1); + IF_KW_PREFIX("e", 1) + IF_KW("af-list", 7, LY_STMT_LEAF_LIST) + else IF_KW("af", 2, LY_STMT_LEAF) + else IF_KW("ngth", 4, LY_STMT_LENGTH) + IF_KW_PREFIX_END + else IF_KW("ist", 3, LY_STMT_LIST) + break; + case 'm': + MOVE_IN(1); + IF_KW_PREFIX("a", 1) + IF_KW("ndatory", 7, LY_STMT_MANDATORY) + else IF_KW("x-elements", 10, LY_STMT_MAX_ELEMENTS) + IF_KW_PREFIX_END + else IF_KW("in-elements", 11, LY_STMT_MIN_ELEMENTS) + else IF_KW("ust", 3, LY_STMT_MUST) + else IF_KW_PREFIX("od", 2) + IF_KW("ule", 3, LY_STMT_MODULE) + else IF_KW("ifier", 5, LY_STMT_MODIFIER) + IF_KW_PREFIX_END + break; + case 'n': + MOVE_IN(1); + IF_KW("amespace", 8, LY_STMT_NAMESPACE) + else IF_KW("otification", 11, LY_STMT_NOTIFICATION) + break; + case 'o': + MOVE_IN(1); + IF_KW_PREFIX("r", 1) + IF_KW("dered-by", 8, LY_STMT_ORDERED_BY) + else IF_KW("ganization", 10, LY_STMT_ORGANIZATION) + IF_KW_PREFIX_END + else IF_KW("utput", 5, LY_STMT_OUTPUT) + break; + case 'p': + MOVE_IN(1); + IF_KW("ath", 3, LY_STMT_PATH) + else IF_KW("attern", 6, LY_STMT_PATTERN) + else IF_KW("osition", 7, LY_STMT_POSITION) + else IF_KW_PREFIX("re", 2) + IF_KW("fix", 3, LY_STMT_PREFIX) + else IF_KW("sence", 5, LY_STMT_PRESENCE) + IF_KW_PREFIX_END + break; + case 'r': + MOVE_IN(1); + IF_KW("ange", 4, LY_STMT_RANGE) + else IF_KW_PREFIX("e", 1) + IF_KW_PREFIX("f", 1) + IF_KW("erence", 6, LY_STMT_REFERENCE) + else IF_KW("ine", 3, LY_STMT_REFINE) + IF_KW_PREFIX_END + else IF_KW("quire-instance", 14, LY_STMT_REQUIRE_INSTANCE) + else IF_KW("vision-date", 11, LY_STMT_REVISION_DATE) + else IF_KW("vision", 6, LY_STMT_REVISION) + IF_KW_PREFIX_END + else IF_KW("pc", 2, LY_STMT_RPC) + break; + case 's': + MOVE_IN(1); + IF_KW("tatus", 5, LY_STMT_STATUS) + else IF_KW("ubmodule", 8, LY_STMT_SUBMODULE) + break; + case 't': + MOVE_IN(1); + IF_KW("ypedef", 6, LY_STMT_TYPEDEF) + else IF_KW("ype", 3, LY_STMT_TYPE) + break; + case 'u': + MOVE_IN(1); + IF_KW_PREFIX("ni", 2) + IF_KW("que", 3, LY_STMT_UNIQUE) + else IF_KW("ts", 2, LY_STMT_UNITS) + IF_KW_PREFIX_END + else IF_KW("ses", 3, LY_STMT_USES) + break; + case 'v': + MOVE_IN(1); + IF_KW("alue", 4, LY_STMT_VALUE) + break; + case 'w': + MOVE_IN(1); + IF_KW("hen", 3, LY_STMT_WHEN) + break; + case 'y': + MOVE_IN(1); + IF_KW("ang-version", 11, LY_STMT_YANG_VERSION) + else IF_KW("in-element", 10, LY_STMT_YIN_ELEMENT) + break; + default: + /* if indent is not NULL we are matching keyword from YANG data */ + if (indent) { + if (in->current[0] == ';') { + MOVE_IN(1); + *kw = LY_STMT_SYNTAX_SEMICOLON; + } else if (in->current[0] == '{') { + MOVE_IN(1); + *kw = LY_STMT_SYNTAX_LEFT_BRACE; + } else if (in->current[0] == '}') { + MOVE_IN(1); + *kw = LY_STMT_SYNTAX_RIGHT_BRACE; + } + } + break; + } + + if ((*kw < LY_STMT_SYNTAX_SEMICOLON) && isalnum(in->current[0])) { + /* the keyword is not terminated */ + *kw = LY_STMT_NONE; + in->current = start; + } + +#undef IF_KW +#undef IF_KW_PREFIX +#undef IF_KW_PREFIX_END +#undef MOVE_IN + /* *INDENT-ON* */ + + return result; +} + +LY_ERR +lysp_ext_find_definition(const struct ly_ctx *ctx, const struct lysp_ext_instance *ext, const struct lys_module **ext_mod, + struct lysp_ext **ext_def) +{ + const char *tmp, *name, *prefix; + size_t pref_len, name_len; + LY_ARRAY_COUNT_TYPE u, v; + const struct lys_module *mod = NULL; + const struct lysp_submodule *submod; + + if (ext_def) { + *ext_def = NULL; + } + + /* parse the prefix, the nodeid was previously already parsed and checked */ + tmp = ext->name; + ly_parse_nodeid(&tmp, &prefix, &pref_len, &name, &name_len); + + /* get module where the extension definition should be placed */ + *ext_mod = mod = ly_resolve_prefix(ctx, prefix, pref_len, ext->format, ext->prefix_data); + if (!mod) { + LOGVAL(ctx, LYVE_REFERENCE, "Invalid prefix \"%.*s\" used for extension instance identifier.", (int)pref_len, prefix); + return LY_EVALID; + } else if (!mod->parsed->extensions) { + LOGVAL(ctx, LYVE_REFERENCE, "Extension instance \"%s\" refers \"%s\" module that does not contain extension definitions.", + ext->name, mod->name); + return LY_EVALID; + } + + if (!ext_def) { + /* we are done */ + return LY_SUCCESS; + } + + /* find the parsed extension definition there */ + LY_ARRAY_FOR(mod->parsed->extensions, v) { + if (!strcmp(name, mod->parsed->extensions[v].name)) { + *ext_def = &mod->parsed->extensions[v]; + break; + } + } + if (!*ext_def) { + LY_ARRAY_FOR(mod->parsed->includes, u) { + submod = mod->parsed->includes[u].submodule; + LY_ARRAY_FOR(submod->extensions, v) { + if (!strcmp(name, submod->extensions[v].name)) { + *ext_def = &submod->extensions[v]; + break; + } + } + } + } + + if (!*ext_def) { + LOGVAL(ctx, LYVE_REFERENCE, "Extension definition of extension instance \"%s\" not found.", ext->name); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +LY_ERR +lysp_ext_instance_resolve_argument(struct ly_ctx *ctx, struct lysp_ext_instance *ext_p) +{ + assert(ext_p->def); + + if (!ext_p->def->argname || ext_p->argument) { + /* nothing to do */ + return LY_SUCCESS; + } + + if (ext_p->format == LY_VALUE_XML) { + /* schema was parsed from YIN and an argument is expected, ... */ + struct lysp_stmt *stmt = NULL; + + if (ext_p->def->flags & LYS_YINELEM_TRUE) { + /* ... argument was the first XML child element */ + for (stmt = ext_p->child; stmt && (stmt->flags & LYS_YIN_ATTR); stmt = stmt->next) {} + if (stmt) { + const char *arg, *ext, *name_arg, *name_ext, *prefix_arg, *prefix_ext; + size_t name_arg_len, name_ext_len, prefix_arg_len, prefix_ext_len; + + stmt = ext_p->child; + + arg = stmt->stmt; + ly_parse_nodeid(&arg, &prefix_arg, &prefix_arg_len, &name_arg, &name_arg_len); + if (ly_strncmp(ext_p->def->argname, name_arg, name_arg_len)) { + LOGVAL(ctx, LYVE_SEMANTICS, "Extension instance \"%s\" expects argument element \"%s\" as its first XML child, " + "but \"%.*s\" element found.", ext_p->name, ext_p->def->argname, (int)name_arg_len, name_arg); + return LY_EVALID; + } + + /* check namespace - all the extension instances must be qualified and argument element is expected in the same + * namespace. Do not check just prefixes, there can be different prefixes pointing to the same namespace */ + ext = ext_p->name; /* include prefix */ + ly_parse_nodeid(&ext, &prefix_ext, &prefix_ext_len, &name_ext, &name_ext_len); + + if (ly_resolve_prefix(ctx, prefix_ext, prefix_ext_len, ext_p->format, ext_p->prefix_data) != + ly_resolve_prefix(ctx, prefix_arg, prefix_arg_len, stmt->format, stmt->prefix_data)) { + LOGVAL(ctx, LYVE_SEMANTICS, "Extension instance \"%s\" element and its argument element \"%s\" are " + "expected in the same namespace, but they differ.", ext_p->name, ext_p->def->argname); + return LY_EVALID; + } + } + } else { + /* ... argument was one of the XML attributes which are represented as child stmt with LYS_YIN_ATTR flag */ + for (stmt = ext_p->child; stmt && (stmt->flags & LYS_YIN_ATTR); stmt = stmt->next) { + if (!strcmp(stmt->stmt, ext_p->def->argname)) { + /* this is the extension's argument */ + break; + } + } + } + + if (stmt) { + LY_CHECK_RET(lydict_insert(ctx, stmt->arg, 0, &ext_p->argument)); + stmt->flags |= LYS_YIN_ARGUMENT; + } + } + + if (!ext_p->argument) { + /* missing extension's argument */ + LOGVAL(ctx, LYVE_SEMANTICS, "Extension instance \"%s\" missing argument %s\"%s\".", + ext_p->name, (ext_p->def->flags & LYS_YINELEM_TRUE) ? "element " : "", ext_p->def->argname); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +LY_ARRAY_COUNT_TYPE +lysp_ext_instance_iter(struct lysp_ext_instance *ext, LY_ARRAY_COUNT_TYPE index, enum ly_stmt substmt) +{ + LY_CHECK_ARG_RET(NULL, ext, LY_EINVAL); + + for ( ; index < LY_ARRAY_COUNT(ext); index++) { + if (ext[index].parent_stmt == substmt) { + return index; + } + } + + return LY_ARRAY_COUNT(ext); +} + +LIBYANG_API_DEF const struct lysc_node * +lysc_data_node(const struct lysc_node *schema) +{ + const struct lysc_node *parent; + + parent = schema; + while (parent && !(parent->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYDATA | LYS_RPC | + LYS_ACTION | LYS_NOTIF))) { + parent = parent->parent; + } + + return parent; +} + +ly_bool +lys_has_recompiled(const struct lys_module *mod) +{ + LY_ARRAY_COUNT_TYPE u; + + if (LYSP_HAS_RECOMPILED(mod->parsed)) { + return 1; + } + + LY_ARRAY_FOR(mod->parsed->includes, u) { + if (LYSP_HAS_RECOMPILED(mod->parsed->includes[u].submodule)) { + return 1; + } + } + + return 0; +} + +ly_bool +lys_has_compiled(const struct lys_module *mod) +{ + LY_ARRAY_COUNT_TYPE u; + + if (LYSP_HAS_COMPILED(mod->parsed)) { + return 1; + } + + LY_ARRAY_FOR(mod->parsed->includes, u) { + if (LYSP_HAS_COMPILED(mod->parsed->includes[u].submodule)) { + return 1; + } + } + + return 0; +} + +ly_bool +lys_has_dep_mods(const struct lys_module *mod) +{ + LY_ARRAY_COUNT_TYPE u; + + /* features */ + if (mod->parsed->features) { + return 1; + } + + /* groupings */ + if (mod->parsed->groupings) { + return 1; + } + LY_ARRAY_FOR(mod->parsed->includes, u) { + if (mod->parsed->includes[u].submodule->groupings) { + return 1; + } + } + + /* augments (adding nodes with leafrefs) */ + if (mod->parsed->augments) { + return 1; + } + LY_ARRAY_FOR(mod->parsed->includes, u) { + if (mod->parsed->includes[u].submodule->augments) { + return 1; + } + } + + return 0; +} + +const char * +lys_stmt_str(enum ly_stmt stmt) +{ + switch (stmt) { + case LY_STMT_NONE: + return NULL; + case LY_STMT_ACTION: + return "action"; + case LY_STMT_ANYDATA: + return "anydata"; + case LY_STMT_ANYXML: + return "anyxml"; + case LY_STMT_ARGUMENT: + return "argument"; + case LY_STMT_ARG_TEXT: + return "text"; + case LY_STMT_ARG_VALUE: + return "value"; + case LY_STMT_AUGMENT: + return "augment"; + case LY_STMT_BASE: + return "base"; + case LY_STMT_BELONGS_TO: + return "belongs-to"; + case LY_STMT_BIT: + return "bit"; + case LY_STMT_CASE: + return "case"; + case LY_STMT_CHOICE: + return "choice"; + case LY_STMT_CONFIG: + return "config"; + case LY_STMT_CONTACT: + return "contact"; + case LY_STMT_CONTAINER: + return "container"; + case LY_STMT_DEFAULT: + return "default"; + case LY_STMT_DESCRIPTION: + return "description"; + case LY_STMT_DEVIATE: + return "deviate"; + case LY_STMT_DEVIATION: + return "deviation"; + case LY_STMT_ENUM: + return "enum"; + case LY_STMT_ERROR_APP_TAG: + return "error-app-tag"; + case LY_STMT_ERROR_MESSAGE: + return "error-message"; + case LY_STMT_EXTENSION: + return "extension"; + case LY_STMT_EXTENSION_INSTANCE: + return NULL; + case LY_STMT_FEATURE: + return "feature"; + case LY_STMT_FRACTION_DIGITS: + return "fraction-digits"; + case LY_STMT_GROUPING: + return "grouping"; + case LY_STMT_IDENTITY: + return "identity"; + case LY_STMT_IF_FEATURE: + return "if-feature"; + case LY_STMT_IMPORT: + return "import"; + case LY_STMT_INCLUDE: + return "include"; + case LY_STMT_INPUT: + return "input"; + case LY_STMT_KEY: + return "key"; + case LY_STMT_LEAF: + return "leaf"; + case LY_STMT_LEAF_LIST: + return "leaf-list"; + case LY_STMT_LENGTH: + return "length"; + case LY_STMT_LIST: + return "list"; + case LY_STMT_MANDATORY: + return "mandatory"; + case LY_STMT_MAX_ELEMENTS: + return "max-elements"; + case LY_STMT_MIN_ELEMENTS: + return "min-elements"; + case LY_STMT_MODIFIER: + return "modifier"; + case LY_STMT_MODULE: + return "module"; + case LY_STMT_MUST: + return "must"; + case LY_STMT_NAMESPACE: + return "namespace"; + case LY_STMT_NOTIFICATION: + return "notification"; + case LY_STMT_ORDERED_BY: + return "ordered-by"; + case LY_STMT_ORGANIZATION: + return "organization"; + case LY_STMT_OUTPUT: + return "output"; + case LY_STMT_PATH: + return "path"; + case LY_STMT_PATTERN: + return "pattern"; + case LY_STMT_POSITION: + return "position"; + case LY_STMT_PREFIX: + return "prefix"; + case LY_STMT_PRESENCE: + return "presence"; + case LY_STMT_RANGE: + return "range"; + case LY_STMT_REFERENCE: + return "reference"; + case LY_STMT_REFINE: + return "refine"; + case LY_STMT_REQUIRE_INSTANCE: + return "require-instance"; + case LY_STMT_REVISION: + return "revision"; + case LY_STMT_REVISION_DATE: + return "revision-date"; + case LY_STMT_RPC: + return "rpc"; + case LY_STMT_STATUS: + return "status"; + case LY_STMT_SUBMODULE: + return "submodule"; + case LY_STMT_SYNTAX_LEFT_BRACE: + return "{"; + case LY_STMT_SYNTAX_RIGHT_BRACE: + return "}"; + case LY_STMT_SYNTAX_SEMICOLON: + return ";"; + case LY_STMT_TYPE: + return "type"; + case LY_STMT_TYPEDEF: + return "typedef"; + case LY_STMT_UNIQUE: + return "unique"; + case LY_STMT_UNITS: + return "units"; + case LY_STMT_USES: + return "uses"; + case LY_STMT_VALUE: + return "value"; + case LY_STMT_WHEN: + return "when"; + case LY_STMT_YANG_VERSION: + return "yang-version"; + case LY_STMT_YIN_ELEMENT: + return "yin-element"; + } + + return NULL; +} + +const char * +lys_stmt_arg(enum ly_stmt stmt) +{ + switch (stmt) { + case LY_STMT_NONE: + case LY_STMT_ARG_TEXT: + case LY_STMT_ARG_VALUE: + case LY_STMT_EXTENSION_INSTANCE: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_SYNTAX_LEFT_BRACE: + case LY_STMT_SYNTAX_RIGHT_BRACE: + case LY_STMT_SYNTAX_SEMICOLON: + return NULL; + case LY_STMT_ACTION: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_ARGUMENT: + case LY_STMT_BASE: + case LY_STMT_BIT: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_ENUM: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IDENTITY: + case LY_STMT_IF_FEATURE: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_MODULE: + case LY_STMT_NOTIFICATION: + case LY_STMT_RPC: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPE: + case LY_STMT_TYPEDEF: + case LY_STMT_UNITS: + case LY_STMT_USES: + return "name"; + case LY_STMT_AUGMENT: + case LY_STMT_DEVIATION: + case LY_STMT_REFINE: + return "target-node"; + case LY_STMT_BELONGS_TO: + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + return "module"; + case LY_STMT_CONFIG: + case LY_STMT_DEFAULT: + case LY_STMT_DEVIATE: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_KEY: + case LY_STMT_LENGTH: + case LY_STMT_MANDATORY: + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + case LY_STMT_MODIFIER: + case LY_STMT_ORDERED_BY: + case LY_STMT_PATH: + case LY_STMT_PATTERN: + case LY_STMT_POSITION: + case LY_STMT_PREFIX: + case LY_STMT_PRESENCE: + case LY_STMT_RANGE: + case LY_STMT_REQUIRE_INSTANCE: + case LY_STMT_STATUS: + case LY_STMT_VALUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + return "value"; + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ORGANIZATION: + case LY_STMT_REFERENCE: + return "text"; + case LY_STMT_MUST: + case LY_STMT_WHEN: + return "condition"; + case LY_STMT_NAMESPACE: + return "uri"; + case LY_STMT_REVISION: + case LY_STMT_REVISION_DATE: + return "date"; + case LY_STMT_UNIQUE: + return "tag"; + } + + return NULL; +} + +uint8_t +lys_stmt_flags(enum ly_stmt stmt) +{ + switch (stmt) { + case LY_STMT_NONE: + case LY_STMT_ARG_TEXT: + case LY_STMT_ARG_VALUE: + case LY_STMT_DEFAULT: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_EXTENSION_INSTANCE: + case LY_STMT_IF_FEATURE: + case LY_STMT_INPUT: + case LY_STMT_KEY: + case LY_STMT_LENGTH: + case LY_STMT_MUST: + case LY_STMT_NAMESPACE: + case LY_STMT_OUTPUT: + case LY_STMT_PATH: + case LY_STMT_PATTERN: + case LY_STMT_PRESENCE: + case LY_STMT_RANGE: + case LY_STMT_SYNTAX_LEFT_BRACE: + case LY_STMT_SYNTAX_RIGHT_BRACE: + case LY_STMT_SYNTAX_SEMICOLON: + case LY_STMT_UNIQUE: + case LY_STMT_UNITS: + case LY_STMT_WHEN: + return 0; + case LY_STMT_ACTION: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_ARGUMENT: + case LY_STMT_AUGMENT: + case LY_STMT_BASE: + case LY_STMT_BELONGS_TO: + case LY_STMT_BIT: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONFIG: + case LY_STMT_CONTAINER: + case LY_STMT_DEVIATE: + case LY_STMT_DEVIATION: + case LY_STMT_ENUM: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_GROUPING: + case LY_STMT_IDENTITY: + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_MANDATORY: + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + case LY_STMT_MODIFIER: + case LY_STMT_MODULE: + case LY_STMT_NOTIFICATION: + case LY_STMT_ORDERED_BY: + case LY_STMT_POSITION: + case LY_STMT_PREFIX: + case LY_STMT_REFINE: + case LY_STMT_REQUIRE_INSTANCE: + case LY_STMT_REVISION: + case LY_STMT_REVISION_DATE: + case LY_STMT_RPC: + case LY_STMT_STATUS: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPE: + case LY_STMT_TYPEDEF: + case LY_STMT_USES: + case LY_STMT_VALUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + return LY_STMT_FLAG_ID; + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_ORGANIZATION: + case LY_STMT_REFERENCE: + return LY_STMT_FLAG_YIN; + } + + return 0; +} diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c new file mode 100644 index 0000000..9eedecf --- /dev/null +++ b/src/tree_schema_free.c @@ -0,0 +1,1737 @@ +/** + * @file tree_schema_free.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Freeing functions for schema tree structures. + * + * Copyright (c) 2019 - 2022 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "tree_schema_free.h" + +#include +#include + +#include "common.h" +#include "compat.h" +#include "dict.h" +#include "log.h" +#include "plugins_exts.h" +#include "plugins_types.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xml.h" +#include "xpath.h" + +static void lysc_node_free_(struct lysf_ctx *ctx, struct lysc_node *node); + +void +lysp_qname_free(const struct ly_ctx *ctx, struct lysp_qname *qname) +{ + if (qname) { + lydict_remove(ctx, qname->str); + } +} + +/** + * @brief Free the parsed generic statement structure. + * + * @param[in] ctx libyang context. + * @param[in] grp Parsed schema statement structure to free. Note that the structure itself is not freed. + */ +static void +lysp_stmt_free(struct ly_ctx *ctx, struct lysp_stmt *stmt) +{ + struct lysp_stmt *child, *next; + + lydict_remove(ctx, stmt->stmt); + lydict_remove(ctx, stmt->arg); + ly_free_prefix_data(stmt->format, stmt->prefix_data); + + LY_LIST_FOR_SAFE(stmt->child, next, child) { + lysp_stmt_free(ctx, child); + } + + free(stmt); +} + +void +lysp_ext_instance_free(struct lysf_ctx *ctx, struct lysp_ext_instance *ext) +{ + struct lysp_stmt *stmt, *next; + + lydict_remove(ctx->ctx, ext->name); + lydict_remove(ctx->ctx, ext->argument); + ly_free_prefix_data(ext->format, ext->prefix_data); + if (ext->record && ext->record->plugin.pfree) { + ext->record->plugin.pfree(ctx->ctx, ext); + } + + LY_LIST_FOR_SAFE(ext->child, next, stmt) { + lysp_stmt_free(ctx->ctx, stmt); + } +} + +/** + * @brief Free the parsed import structure. + * + * @param[in] ctx Free context. + * @param[in] import Parsed schema import structure to free. Note that the structure itself is not freed. + */ +static void +lysp_import_free(struct lysf_ctx *ctx, struct lysp_import *import) +{ + /* imported module is freed directly from the context's list */ + lydict_remove(ctx->ctx, import->name); + lydict_remove(ctx->ctx, import->prefix); + lydict_remove(ctx->ctx, import->dsc); + lydict_remove(ctx->ctx, import->ref); + FREE_ARRAY(ctx, import->exts, lysp_ext_instance_free); +} + +/** + * @brief Common function to erase include record in main module and submodule. + * + * There is a difference since the main module is expected to have the complete list if the included submodules and + * the parsed submodule is shared with any include in a submodule. Therefore, the referenced submodules in the include + * record are freed only from main module's records. + * + * @param[in] ctx libyang context + * @param[in] include The include record to be erased, the record itself is not freed. + * @param[in] main_module Flag to get know if the include record is placed in main module so also the referenced submodule + * is supposed to be freed. + */ +static void +lysp_include_free_(struct lysf_ctx *ctx, struct lysp_include *include, ly_bool main_module) +{ + if (main_module && include->submodule) { + lysp_module_free(ctx, (struct lysp_module *)include->submodule); + } + lydict_remove(ctx->ctx, include->name); + lydict_remove(ctx->ctx, include->dsc); + lydict_remove(ctx->ctx, include->ref); + FREE_ARRAY(ctx, include->exts, lysp_ext_instance_free); +} + +/** + * @brief Free the parsed include structure of a submodule. + * + * @param[in] ctx Free context. + * @param[in] include Parsed schema include structure to free. Note that the structure itself is not freed. + */ +static void +lysp_include_free_submodule(struct lysf_ctx *ctx, struct lysp_include *include) +{ + lysp_include_free_(ctx, include, 0); +} + +/** + * @brief Free the parsed include structure of a module. + * + * @param[in] ctx Free context. + * @param[in] include Parsed schema include structure to free. Note that the structure itself is not freed. + */ +static void +lysp_include_free(struct lysf_ctx *ctx, struct lysp_include *include) +{ + lysp_include_free_(ctx, include, 1); +} + +/** + * @brief Free the parsed revision structure. + * + * @param[in] ctx Free context. + * @param[in] rev Parsed schema revision structure to free. Note that the structure itself is not freed. + */ +static void +lysp_revision_free(struct lysf_ctx *ctx, struct lysp_revision *rev) +{ + lydict_remove(ctx->ctx, rev->dsc); + lydict_remove(ctx->ctx, rev->ref); + FREE_ARRAY(ctx, rev->exts, lysp_ext_instance_free); +} + +/** + * @brief Free the compiled extension definition and NULL the provided pointer. + * + * @param[in] ctx Free context. + * @param[in,out] ext Compiled extension definition to be freed. + */ +static void +lysc_extension_free(struct lysf_ctx *ctx, struct lysc_ext **ext) +{ + if (ly_set_contains(&ctx->ext_set, *ext, NULL)) { + /* already freed and only referenced again in this module */ + return; + } + + /* remember this extension to be freed, nothing to do on error */ + (void)ly_set_add(&ctx->ext_set, *ext, 0, NULL); + + /* recursive exts free */ + FREE_ARRAY(ctx, (*ext)->exts, lysc_ext_instance_free); + + *ext = NULL; +} + +/** + * @brief Free the parsed ext structure. + * + * @param[in] ctx Free context. + * @param[in] ext Parsed schema ext structure to free. Note that the structure itself is not freed. + */ +static void +lysp_ext_free(struct lysf_ctx *ctx, struct lysp_ext *ext) +{ + lydict_remove(ctx->ctx, ext->name); + lydict_remove(ctx->ctx, ext->argname); + lydict_remove(ctx->ctx, ext->dsc); + lydict_remove(ctx->ctx, ext->ref); + FREE_ARRAY(ctx, ext->exts, lysp_ext_instance_free); + if (ext->compiled) { + lysc_extension_free(ctx, &ext->compiled); + } +} + +/** + * @brief Free the parsed feature structure. + * + * @param[in] ctx Free context. + * @param[in] feat Parsed schema feature structure to free. Note that the structure itself is not freed. + */ +static void +lysp_feature_free(struct lysf_ctx *ctx, struct lysp_feature *feat) +{ + lydict_remove(ctx->ctx, feat->name); + FREE_ARRAY(ctx->ctx, feat->iffeatures, lysp_qname_free); + FREE_ARRAY(ctx, feat->iffeatures_c, lysc_iffeature_free); + LY_ARRAY_FREE(feat->depfeatures); + lydict_remove(ctx->ctx, feat->dsc); + lydict_remove(ctx->ctx, feat->ref); + FREE_ARRAY(ctx, feat->exts, lysp_ext_instance_free); +} + +/** + * @brief Free the parsed identity structure. + * + * @param[in] ctx Free context. + * @param[in] ident Parsed schema identity structure to free. Note that the structure itself is not freed. + */ +static void +lysp_ident_free(struct lysf_ctx *ctx, struct lysp_ident *ident) +{ + lydict_remove(ctx->ctx, ident->name); + FREE_ARRAY(ctx->ctx, ident->iffeatures, lysp_qname_free); + FREE_STRINGS(ctx->ctx, ident->bases); + lydict_remove(ctx->ctx, ident->dsc); + lydict_remove(ctx->ctx, ident->ref); + FREE_ARRAY(ctx, ident->exts, lysp_ext_instance_free); +} + +void +lysp_restr_free(struct lysf_ctx *ctx, struct lysp_restr *restr) +{ + lydict_remove(ctx->ctx, restr->arg.str); + lydict_remove(ctx->ctx, restr->emsg); + lydict_remove(ctx->ctx, restr->eapptag); + lydict_remove(ctx->ctx, restr->dsc); + lydict_remove(ctx->ctx, restr->ref); + FREE_ARRAY(ctx, restr->exts, lysp_ext_instance_free); +} + +/** + * @brief Free the parsed type enum item. + * + * @param[in] ctx Free context. + * @param[in] item Parsed schema type enum item to free. Note that the structure itself is not freed. + */ +static void +lysp_type_enum_free(struct lysf_ctx *ctx, struct lysp_type_enum *item) +{ + lydict_remove(ctx->ctx, item->name); + lydict_remove(ctx->ctx, item->dsc); + lydict_remove(ctx->ctx, item->ref); + FREE_ARRAY(ctx->ctx, item->iffeatures, lysp_qname_free); + FREE_ARRAY(ctx, item->exts, lysp_ext_instance_free); +} + +void +lysp_type_free(struct lysf_ctx *ctx, struct lysp_type *type) +{ + if (!type) { + return; + } + + lydict_remove(ctx->ctx, type->name); + FREE_MEMBER(ctx, type->range, lysp_restr_free); + FREE_MEMBER(ctx, type->length, lysp_restr_free); + FREE_ARRAY(ctx, type->patterns, lysp_restr_free); + FREE_ARRAY(ctx, type->enums, lysp_type_enum_free); + FREE_ARRAY(ctx, type->bits, lysp_type_enum_free); + lyxp_expr_free(ctx->ctx, type->path); + FREE_STRINGS(ctx->ctx, type->bases); + FREE_ARRAY(ctx, type->types, lysp_type_free); + FREE_ARRAY(ctx, type->exts, lysp_ext_instance_free); + if (type->compiled) { + lysc_type_free(ctx, type->compiled); + } +} + +/** + * @brief Free the parsed typedef structure. + * + * @param[in] ctx Free context. + * @param[in] tpdf Parsed schema typedef structure to free. Note that the structure itself is not freed. + */ +static void +lysp_tpdf_free(struct lysf_ctx *ctx, struct lysp_tpdf *tpdf) +{ + lydict_remove(ctx->ctx, tpdf->name); + lydict_remove(ctx->ctx, tpdf->units); + lydict_remove(ctx->ctx, tpdf->dflt.str); + lydict_remove(ctx->ctx, tpdf->dsc); + lydict_remove(ctx->ctx, tpdf->ref); + FREE_ARRAY(ctx, tpdf->exts, lysp_ext_instance_free); + + lysp_type_free(ctx, &tpdf->type); + +} + +/** + * @brief Free the parsed grouping structure. + * + * @param[in] ctx Free context. + * @param[in] grp Parsed schema grouping structure to free. Note that the structure itself is not freed. + */ +static void +lysp_grp_free(struct lysf_ctx *ctx, struct lysp_node_grp *grp) +{ + struct lysp_node *node, *next; + + FREE_ARRAY(ctx, grp->typedefs, lysp_tpdf_free); + LY_LIST_FOR_SAFE((struct lysp_node *)grp->groupings, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE(grp->child, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)grp->actions, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)grp->notifs, next, node) { + lysp_node_free(ctx, node); + } +} + +void +lysp_when_free(struct lysf_ctx *ctx, struct lysp_when *when) +{ + lydict_remove(ctx->ctx, when->cond); + lydict_remove(ctx->ctx, when->dsc); + lydict_remove(ctx->ctx, when->ref); + FREE_ARRAY(ctx, when->exts, lysp_ext_instance_free); +} + +/** + * @brief Free the parsed augment structure. + * + * @param[in] ctx Free context. + * @param[in] aug Parsed schema augment structure to free. Note that the structure itself is not freed. + */ +static void +lysp_augment_free(struct lysf_ctx *ctx, struct lysp_node_augment *aug) +{ + struct lysp_node *node, *next; + + LY_LIST_FOR_SAFE(aug->child, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)aug->actions, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)aug->notifs, next, node) { + lysp_node_free(ctx, node); + } +} + +void +lysp_deviate_free(struct lysf_ctx *ctx, struct lysp_deviate *d) +{ + struct lysp_deviate_add *add = (struct lysp_deviate_add *)d; + struct lysp_deviate_rpl *rpl = (struct lysp_deviate_rpl *)d; + + if (!d) { + return; + } + + FREE_ARRAY(ctx, d->exts, lysp_ext_instance_free); + switch (d->mod) { + case LYS_DEV_NOT_SUPPORTED: + /* nothing to do */ + break; + case LYS_DEV_ADD: + case LYS_DEV_DELETE: /* compatible for dynamically allocated data */ + lydict_remove(ctx->ctx, add->units); + FREE_ARRAY(ctx, add->musts, lysp_restr_free); + FREE_ARRAY(ctx->ctx, add->uniques, lysp_qname_free); + FREE_ARRAY(ctx->ctx, add->dflts, lysp_qname_free); + break; + case LYS_DEV_REPLACE: + FREE_MEMBER(ctx, rpl->type, lysp_type_free); + lydict_remove(ctx->ctx, rpl->units); + lysp_qname_free(ctx->ctx, &rpl->dflt); + break; + default: + LOGINT(ctx->ctx); + break; + } +} + +void +lysp_deviation_free(struct lysf_ctx *ctx, struct lysp_deviation *dev) +{ + struct lysp_deviate *next, *iter; + + lydict_remove(ctx->ctx, dev->nodeid); + lydict_remove(ctx->ctx, dev->dsc); + lydict_remove(ctx->ctx, dev->ref); + LY_LIST_FOR_SAFE(dev->deviates, next, iter) { + lysp_deviate_free(ctx, iter); + free(iter); + } + FREE_ARRAY(ctx, dev->exts, lysp_ext_instance_free); +} + +/** + * @brief Free the parsed refine structure. + * + * @param[in] ctx Free context. + * @param[in] ref Parsed schema refine structure to free. Note that the structure itself is not freed. + */ +static void +lysp_refine_free(struct lysf_ctx *ctx, struct lysp_refine *ref) +{ + lydict_remove(ctx->ctx, ref->nodeid); + lydict_remove(ctx->ctx, ref->dsc); + lydict_remove(ctx->ctx, ref->ref); + FREE_ARRAY(ctx->ctx, ref->iffeatures, lysp_qname_free); + FREE_ARRAY(ctx, ref->musts, lysp_restr_free); + lydict_remove(ctx->ctx, ref->presence); + FREE_ARRAY(ctx->ctx, ref->dflts, lysp_qname_free); + FREE_ARRAY(ctx, ref->exts, lysp_ext_instance_free); +} + +void +lysp_node_free(struct lysf_ctx *ctx, struct lysp_node *node) +{ + struct lysp_node *child, *next; + struct lysp_node_container *cont; + struct lysp_node_leaf *leaf; + struct lysp_node_leaflist *llist; + struct lysp_node_list *list; + struct lysp_node_choice *choice; + struct lysp_node_case *cas; + struct lysp_node_uses *uses; + struct lysp_node_action *act; + struct lysp_node_action_inout *inout; + struct lysp_node_notif *notif; + struct lysp_restr *musts = lysp_node_musts(node); + struct lysp_when *when = lysp_node_when(node); + + lydict_remove(ctx->ctx, node->name); + lydict_remove(ctx->ctx, node->dsc); + lydict_remove(ctx->ctx, node->ref); + FREE_ARRAY(ctx->ctx, node->iffeatures, lysp_qname_free); + FREE_ARRAY(ctx, node->exts, lysp_ext_instance_free); + + FREE_MEMBER(ctx, when, lysp_when_free); + FREE_ARRAY(ctx, musts, lysp_restr_free); + + switch (node->nodetype) { + case LYS_CONTAINER: + cont = (struct lysp_node_container *)node; + + lydict_remove(ctx->ctx, cont->presence); + FREE_ARRAY(ctx, cont->typedefs, lysp_tpdf_free); + if (cont->groupings) { + LY_LIST_FOR_SAFE(&cont->groupings->node, next, child) { + lysp_node_free(ctx, child); + } + } + LY_LIST_FOR_SAFE(cont->child, next, child) { + lysp_node_free(ctx, child); + } + if (cont->actions) { + LY_LIST_FOR_SAFE(&cont->actions->node, next, child) { + lysp_node_free(ctx, child); + } + } + if (cont->notifs) { + LY_LIST_FOR_SAFE(&cont->notifs->node, next, child) { + lysp_node_free(ctx, child); + } + } + break; + case LYS_LEAF: + leaf = (struct lysp_node_leaf *)node; + + lysp_type_free(ctx, &leaf->type); + lydict_remove(ctx->ctx, leaf->units); + lydict_remove(ctx->ctx, leaf->dflt.str); + break; + case LYS_LEAFLIST: + llist = (struct lysp_node_leaflist *)node; + + lysp_type_free(ctx, &llist->type); + lydict_remove(ctx->ctx, llist->units); + FREE_ARRAY(ctx->ctx, llist->dflts, lysp_qname_free); + break; + case LYS_LIST: + list = (struct lysp_node_list *)node; + + lydict_remove(ctx->ctx, list->key); + FREE_ARRAY(ctx, list->typedefs, lysp_tpdf_free); + if (list->groupings) { + LY_LIST_FOR_SAFE(&list->groupings->node, next, child) { + lysp_node_free(ctx, child); + } + } + LY_LIST_FOR_SAFE(list->child, next, child) { + lysp_node_free(ctx, child); + } + if (list->actions) { + LY_LIST_FOR_SAFE(&list->actions->node, next, child) { + lysp_node_free(ctx, child); + } + } + if (list->notifs) { + LY_LIST_FOR_SAFE(&list->notifs->node, next, child) { + lysp_node_free(ctx, child); + } + } + FREE_ARRAY(ctx->ctx, list->uniques, lysp_qname_free); + break; + case LYS_CHOICE: + choice = (struct lysp_node_choice *)node; + + LY_LIST_FOR_SAFE(choice->child, next, child) { + lysp_node_free(ctx, child); + } + lydict_remove(ctx->ctx, choice->dflt.str); + break; + case LYS_CASE: + cas = (struct lysp_node_case *)node; + + LY_LIST_FOR_SAFE(cas->child, next, child) { + lysp_node_free(ctx, child); + } + break; + case LYS_ANYDATA: + case LYS_ANYXML: + /* nothing special to do */ + break; + case LYS_USES: + uses = (struct lysp_node_uses *)node; + + FREE_ARRAY(ctx, uses->refines, lysp_refine_free); + if (uses->augments) { + LY_LIST_FOR_SAFE(&uses->augments->node, next, child) { + lysp_node_free(ctx, child); + } + } + break; + case LYS_RPC: + case LYS_ACTION: + act = (struct lysp_node_action *)node; + + FREE_ARRAY(ctx, act->typedefs, lysp_tpdf_free); + if (act->groupings) { + LY_LIST_FOR_SAFE(&act->groupings->node, next, child) { + lysp_node_free(ctx, child); + } + } + if (act->input.nodetype) { + lysp_node_free(ctx, &act->input.node); + } + if (act->output.nodetype) { + lysp_node_free(ctx, &act->output.node); + } + break; + case LYS_INPUT: + case LYS_OUTPUT: + inout = (struct lysp_node_action_inout *)node; + + FREE_ARRAY(ctx, inout->typedefs, lysp_tpdf_free); + if (inout->groupings) { + LY_LIST_FOR_SAFE(&inout->groupings->node, next, child) { + lysp_node_free(ctx, child); + } + } + LY_LIST_FOR_SAFE(inout->child, next, child) { + lysp_node_free(ctx, child); + } + /* do not free the node, it is never standalone but part of the action node */ + return; + case LYS_NOTIF: + notif = (struct lysp_node_notif *)node; + + FREE_ARRAY(ctx, notif->typedefs, lysp_tpdf_free); + if (notif->groupings) { + LY_LIST_FOR_SAFE(¬if->groupings->node, next, child) { + lysp_node_free(ctx, child); + } + } + LY_LIST_FOR_SAFE(notif->child, next, child) { + lysp_node_free(ctx, child); + } + break; + case LYS_GROUPING: + lysp_grp_free(ctx, (struct lysp_node_grp *)node); + break; + case LYS_AUGMENT: + lysp_augment_free(ctx, ((struct lysp_node_augment *)node)); + break; + default: + LOGINT(ctx->ctx); + } + + free(node); +} + +void +lysp_module_free(struct lysf_ctx *ctx, struct lysp_module *module) +{ + struct lysp_node *node, *next; + + if (!module) { + return; + } + + FREE_ARRAY(ctx, module->imports, lysp_import_free); + FREE_ARRAY(ctx, module->includes, module->is_submod ? lysp_include_free_submodule : lysp_include_free); + + FREE_ARRAY(ctx, module->revs, lysp_revision_free); + FREE_ARRAY(ctx, module->extensions, lysp_ext_free); + FREE_ARRAY(ctx, module->features, lysp_feature_free); + FREE_ARRAY(ctx, module->identities, lysp_ident_free); + FREE_ARRAY(ctx, module->typedefs, lysp_tpdf_free); + LY_LIST_FOR_SAFE((struct lysp_node *)module->groupings, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE(module->data, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)module->augments, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)module->rpcs, next, node) { + lysp_node_free(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysp_node *)module->notifs, next, node) { + lysp_node_free(ctx, node); + } + FREE_ARRAY(ctx, module->deviations, lysp_deviation_free); + FREE_ARRAY(ctx, module->exts, lysp_ext_instance_free); + + if (module->is_submod) { + struct lysp_submodule *submod = (struct lysp_submodule *)module; + + lydict_remove(ctx->ctx, submod->name); + lydict_remove(ctx->ctx, submod->filepath); + lydict_remove(ctx->ctx, submod->prefix); + lydict_remove(ctx->ctx, submod->org); + lydict_remove(ctx->ctx, submod->contact); + lydict_remove(ctx->ctx, submod->dsc); + lydict_remove(ctx->ctx, submod->ref); + } + + free(module); +} + +void +lysc_ext_instance_free(struct lysf_ctx *ctx, struct lysc_ext_instance *ext) +{ + if (ext->def && ext->def->plugin && ext->def->plugin->cfree) { + ext->def->plugin->cfree(ctx->ctx, ext); + } + lydict_remove(ctx->ctx, ext->argument); + FREE_ARRAY(ctx, ext->exts, lysc_ext_instance_free); +} + +void +lysc_iffeature_free(struct lysf_ctx *UNUSED(ctx), struct lysc_iffeature *iff) +{ + LY_ARRAY_FREE(iff->features); + free(iff->expr); +} + +/** + * @brief Free the compiled when structure (decrease refcount) and NULL the provided pointer. + * + * @param[in] ctx Free context. + * @param[in] grp Parsed schema grouping structure to free. Note that the structure itself is not freed. + */ +static void +lysc_when_free(struct lysf_ctx *ctx, struct lysc_when **w) +{ + if (--(*w)->refcount) { + return; + } + lyxp_expr_free(ctx->ctx, (*w)->cond); + ly_free_prefix_data(LY_VALUE_SCHEMA_RESOLVED, (*w)->prefixes); + lydict_remove(ctx->ctx, (*w)->dsc); + lydict_remove(ctx->ctx, (*w)->ref); + FREE_ARRAY(ctx, (*w)->exts, lysc_ext_instance_free); + free(*w); +} + +/** + * @brief Free the compiled must structure. + * + * @param[in] ctx Free context. + * @param[in,out] must Compiled must structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_must_free(struct lysf_ctx *ctx, struct lysc_must *must) +{ + if (!must) { + return; + } + + lyxp_expr_free(ctx->ctx, must->cond); + ly_free_prefix_data(LY_VALUE_SCHEMA_RESOLVED, must->prefixes); + lydict_remove(ctx->ctx, must->emsg); + lydict_remove(ctx->ctx, must->eapptag); + lydict_remove(ctx->ctx, must->dsc); + lydict_remove(ctx->ctx, must->ref); + FREE_ARRAY(ctx, must->exts, lysc_ext_instance_free); +} + +/** + * @brief Unlink the identity from all derived identity arrays. + * + * @param[in] ident Identity to unlink. + */ +static void +lysc_ident_derived_unlink(const struct lysc_ident *ident) +{ + LY_ARRAY_COUNT_TYPE u, v, w; + const struct lysp_submodule *submod; + const struct lysp_module *base_pmod = NULL; + const struct lysp_ident *identp = NULL; + const struct lys_module *mod, *iter; + const char *base_name; + uint32_t i; + + /* find the parsed identity */ + LY_ARRAY_FOR(ident->module->parsed->identities, u) { + if (ident->module->parsed->identities[u].name == ident->name) { + identp = &ident->module->parsed->identities[u]; + base_pmod = ident->module->parsed; + break; + } + } + if (!identp) { + LY_ARRAY_FOR(ident->module->parsed->includes, v) { + submod = ident->module->parsed->includes[v].submodule; + LY_ARRAY_FOR(submod->identities, u) { + if (submod->identities[u].name == ident->name) { + identp = &submod->identities[u]; + base_pmod = (struct lysp_module *)submod; + break; + } + } + } + } + assert(identp); + + /* remove link from all the foreign bases, it may not be there if identity compilation failed */ + LY_ARRAY_FOR(identp->bases, u) { + base_name = strchr(identp->bases[u], ':'); + if (!base_name) { + continue; + } + + /* prefixed identity */ + mod = ly_resolve_prefix(ident->module->ctx, identp->bases[u], base_name - identp->bases[u], LY_VALUE_SCHEMA, + (void *)base_pmod); + if (!mod) { + continue; + } + ++base_name; + + i = 0; + while ((iter = ly_ctx_get_module_iter(ident->module->ctx, &i))) { + if (iter == mod) { + break; + } + } + if (!iter) { + /* target module was freed already */ + continue; + } + + /* find the compiled base */ + LY_ARRAY_FOR(mod->identities, v) { + if (!strcmp(mod->identities[v].name, base_name)) { + /* find the derived link */ + LY_ARRAY_FOR(mod->identities[v].derived, w) { + if (mod->identities[v].derived[w] == ident) { + /* remove the link */ + LY_ARRAY_DECREMENT(mod->identities[v].derived); + if (!LY_ARRAY_COUNT(mod->identities[v].derived)) { + LY_ARRAY_FREE(mod->identities[v].derived); + mod->identities[v].derived = NULL; + } else if (w < LY_ARRAY_COUNT(mod->identities[v].derived)) { + memmove(mod->identities[v].derived + w, mod->identities[v].derived + w + 1, + (LY_ARRAY_COUNT(mod->identities[v].derived) - w) * sizeof ident); + } + break; + } + } + break; + } + } + } +} + +/** + * @brief Free the compiled identity structure. + * + * @param[in] ctx Free context. + * @param[in,out] ident Compiled identity structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_ident_free(struct lysf_ctx *ctx, struct lysc_ident *ident) +{ + lydict_remove(ctx->ctx, ident->name); + lydict_remove(ctx->ctx, ident->dsc); + lydict_remove(ctx->ctx, ident->ref); + LY_ARRAY_FREE(ident->derived); + FREE_ARRAY(ctx, ident->exts, lysc_ext_instance_free); +} + +/** + * @brief Free the compiled range structure. + * + * @param[in] ctx Free context. + * @param[in,out] range Compiled range structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_range_free(struct lysf_ctx *ctx, struct lysc_range *range) +{ + LY_ARRAY_FREE(range->parts); + lydict_remove(ctx->ctx, range->eapptag); + lydict_remove(ctx->ctx, range->emsg); + lydict_remove(ctx->ctx, range->dsc); + lydict_remove(ctx->ctx, range->ref); + FREE_ARRAY(ctx, range->exts, lysc_ext_instance_free); +} + +void +lysc_pattern_free(struct lysf_ctx *ctx, struct lysc_pattern **pattern) +{ + if (--(*pattern)->refcount) { + return; + } + pcre2_code_free((*pattern)->code); + lydict_remove(ctx->ctx, (*pattern)->expr); + lydict_remove(ctx->ctx, (*pattern)->eapptag); + lydict_remove(ctx->ctx, (*pattern)->emsg); + lydict_remove(ctx->ctx, (*pattern)->dsc); + lydict_remove(ctx->ctx, (*pattern)->ref); + FREE_ARRAY(ctx, (*pattern)->exts, lysc_ext_instance_free); + free(*pattern); +} + +void +lysc_enum_item_free(struct lysf_ctx *ctx, struct lysc_type_bitenum_item *item) +{ + lydict_remove(ctx->ctx, item->name); + lydict_remove(ctx->ctx, item->dsc); + lydict_remove(ctx->ctx, item->ref); + FREE_ARRAY(ctx, item->exts, lysc_ext_instance_free); +} + +/** + * @brief Free the compiled type structure. + * + * @param[in] ctx Free context. + * @param[in,out] type Pointer to compiled type structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_type2_free(struct lysf_ctx *ctx, struct lysc_type **type) +{ + lysc_type_free(ctx, *type); +} + +void +lysc_type_free(struct lysf_ctx *ctx, struct lysc_type *type) +{ + if (!type || (LY_ATOMIC_DEC_BARRIER(type->refcount) > 1)) { + return; + } + + switch (type->basetype) { + case LY_TYPE_BINARY: + FREE_MEMBER(ctx, ((struct lysc_type_bin *)type)->length, lysc_range_free); + break; + case LY_TYPE_BITS: + FREE_ARRAY(ctx, (struct lysc_type_bitenum_item *)((struct lysc_type_bits *)type)->bits, lysc_enum_item_free); + break; + case LY_TYPE_DEC64: + FREE_MEMBER(ctx, ((struct lysc_type_dec *)type)->range, lysc_range_free); + break; + case LY_TYPE_STRING: + FREE_MEMBER(ctx, ((struct lysc_type_str *)type)->length, lysc_range_free); + FREE_ARRAY(ctx, ((struct lysc_type_str *)type)->patterns, lysc_pattern_free); + break; + case LY_TYPE_ENUM: + FREE_ARRAY(ctx, ((struct lysc_type_enum *)type)->enums, lysc_enum_item_free); + break; + case LY_TYPE_INT8: + case LY_TYPE_UINT8: + case LY_TYPE_INT16: + case LY_TYPE_UINT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT32: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + FREE_MEMBER(ctx, ((struct lysc_type_num *)type)->range, lysc_range_free); + break; + case LY_TYPE_IDENT: + LY_ARRAY_FREE(((struct lysc_type_identityref *)type)->bases); + break; + case LY_TYPE_UNION: + FREE_ARRAY(ctx, ((struct lysc_type_union *)type)->types, lysc_type2_free); + break; + case LY_TYPE_LEAFREF: + lyxp_expr_free(ctx->ctx, ((struct lysc_type_leafref *)type)->path); + ly_free_prefix_data(LY_VALUE_SCHEMA_RESOLVED, ((struct lysc_type_leafref *)type)->prefixes); + lysc_type_free(ctx, ((struct lysc_type_leafref *)type)->realtype); + break; + case LY_TYPE_INST: + case LY_TYPE_BOOL: + case LY_TYPE_EMPTY: + case LY_TYPE_UNKNOWN: + /* nothing to do */ + break; + } + + FREE_ARRAY(ctx, type->exts, lysc_ext_instance_free); + free(type); +} + +/** + * @brief Free the compiled input/output structure. + * + * @param[in] ctx Free context. + * @param[in,out] inout Compiled inout structure to be freed. + * Since the structure is part of the RPC/action structure, it is not freed itself. + */ +static void +lysc_node_action_inout_free(struct lysf_ctx *ctx, struct lysc_node_action_inout *inout) +{ + struct lysc_node *child, *child_next; + + FREE_ARRAY(ctx, inout->musts, lysc_must_free); + LY_LIST_FOR_SAFE(inout->child, child_next, child) { + lysc_node_free_(ctx, child); + } +} + +/** + * @brief Free the compiled RPC/action structure. + * + * @param[in] ctx Free context. + * @param[in,out] action Compiled RPC/action structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_action_free(struct lysf_ctx *ctx, struct lysc_node_action *action) +{ + FREE_ARRAY(ctx, action->when, lysc_when_free); + if (action->input.nodetype) { + lysc_node_free_(ctx, &action->input.node); + } + if (action->output.nodetype) { + lysc_node_free_(ctx, &action->output.node); + } +} + +/** + * @brief Free the compiled notification structure. + * + * @param[in] ctx Free context. + * @param[in,out] notif Compiled notification structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_notif_free(struct lysf_ctx *ctx, struct lysc_node_notif *notif) +{ + struct lysc_node *child, *child_next; + + FREE_ARRAY(ctx, notif->when, lysc_when_free); + FREE_ARRAY(ctx, notif->musts, lysc_must_free); + LY_LIST_FOR_SAFE(notif->child, child_next, child) { + lysc_node_free_(ctx, child); + } +} + +void +lysc_node_container_free(struct lysf_ctx *ctx, struct lysc_node_container *node) +{ + struct lysc_node *child, *child_next; + + LY_LIST_FOR_SAFE(node->child, child_next, child) { + lysc_node_free_(ctx, child); + } + LY_LIST_FOR_SAFE((struct lysc_node *)node->actions, child_next, child) { + lysc_node_free_(ctx, child); + } + LY_LIST_FOR_SAFE((struct lysc_node *)node->notifs, child_next, child) { + lysc_node_free_(ctx, child); + } + FREE_ARRAY(ctx, node->when, lysc_when_free); + FREE_ARRAY(ctx, node->musts, lysc_must_free); +} + +/** + * @brief Free the compiled leaf structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled leaf structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_leaf_free(struct lysf_ctx *ctx, struct lysc_node_leaf *node) +{ + FREE_ARRAY(ctx, node->when, lysc_when_free); + FREE_ARRAY(ctx, node->musts, lysc_must_free); + if (node->type) { + lysc_type_free(ctx, node->type); + } + lydict_remove(ctx->ctx, node->units); + if (node->dflt) { + node->dflt->realtype->plugin->free(ctx->ctx, node->dflt); + lysc_type_free(ctx, (struct lysc_type *)node->dflt->realtype); + free(node->dflt); + } +} + +/** + * @brief Free the compiled leaflist structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled leaflist structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_leaflist_free(struct lysf_ctx *ctx, struct lysc_node_leaflist *node) +{ + LY_ARRAY_COUNT_TYPE u; + + FREE_ARRAY(ctx, node->when, lysc_when_free); + FREE_ARRAY(ctx, node->musts, lysc_must_free); + if (node->type) { + lysc_type_free(ctx, node->type); + } + lydict_remove(ctx->ctx, node->units); + LY_ARRAY_FOR(node->dflts, u) { + node->dflts[u]->realtype->plugin->free(ctx->ctx, node->dflts[u]); + lysc_type_free(ctx, (struct lysc_type *)node->dflts[u]->realtype); + free(node->dflts[u]); + } + LY_ARRAY_FREE(node->dflts); +} + +/** + * @brief Free the compiled list structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled list structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_list_free(struct lysf_ctx *ctx, struct lysc_node_list *node) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysc_node *child, *child_next; + + LY_LIST_FOR_SAFE(node->child, child_next, child) { + lysc_node_free_(ctx, child); + } + FREE_ARRAY(ctx, node->when, lysc_when_free); + FREE_ARRAY(ctx, node->musts, lysc_must_free); + + LY_ARRAY_FOR(node->uniques, u) { + LY_ARRAY_FREE(node->uniques[u]); + } + LY_ARRAY_FREE(node->uniques); + + LY_LIST_FOR_SAFE((struct lysc_node *)node->actions, child_next, child) { + lysc_node_free_(ctx, child); + } + LY_LIST_FOR_SAFE((struct lysc_node *)node->notifs, child_next, child) { + lysc_node_free_(ctx, child); + } +} + +/** + * @brief Free the compiled choice structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled choice structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_choice_free(struct lysf_ctx *ctx, struct lysc_node_choice *node) +{ + struct lysc_node *child, *child_next; + + FREE_ARRAY(ctx, node->when, lysc_when_free); + LY_LIST_FOR_SAFE((struct lysc_node *)node->cases, child_next, child) { + lysc_node_free_(ctx, child); + } +} + +/** + * @brief Free the compiled case structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled case structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_case_free(struct lysf_ctx *ctx, struct lysc_node_case *node) +{ + struct lysc_node *child, *child_next; + + FREE_ARRAY(ctx, node->when, lysc_when_free); + LY_LIST_FOR_SAFE(node->child, child_next, child) { + lysc_node_free_(ctx, child); + } +} + +/** + * @brief Free the compiled anyxml/anydata structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled anyxml/anydata structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_anydata_free(struct lysf_ctx *ctx, struct lysc_node_anydata *node) +{ + FREE_ARRAY(ctx, node->when, lysc_when_free); + FREE_ARRAY(ctx, node->musts, lysc_must_free); +} + +/** + * @brief Free the compiled node structure. + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled node structure to be freed. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +static void +lysc_node_free_(struct lysf_ctx *ctx, struct lysc_node *node) +{ + ly_bool inout = 0; + + /* common part */ + lydict_remove(ctx->ctx, node->name); + lydict_remove(ctx->ctx, node->dsc); + lydict_remove(ctx->ctx, node->ref); + + /* nodetype-specific part */ + switch (node->nodetype) { + case LYS_CONTAINER: + lysc_node_container_free(ctx, (struct lysc_node_container *)node); + break; + case LYS_LEAF: + lysc_node_leaf_free(ctx, (struct lysc_node_leaf *)node); + break; + case LYS_LEAFLIST: + lysc_node_leaflist_free(ctx, (struct lysc_node_leaflist *)node); + break; + case LYS_LIST: + lysc_node_list_free(ctx, (struct lysc_node_list *)node); + break; + case LYS_CHOICE: + lysc_node_choice_free(ctx, (struct lysc_node_choice *)node); + break; + case LYS_CASE: + lysc_node_case_free(ctx, (struct lysc_node_case *)node); + break; + case LYS_ANYDATA: + case LYS_ANYXML: + lysc_node_anydata_free(ctx, (struct lysc_node_anydata *)node); + break; + case LYS_RPC: + case LYS_ACTION: + lysc_node_action_free(ctx, (struct lysc_node_action *)node); + break; + case LYS_INPUT: + case LYS_OUTPUT: + lysc_node_action_inout_free(ctx, (struct lysc_node_action_inout *)node); + inout = 1; + break; + case LYS_NOTIF: + lysc_node_notif_free(ctx, (struct lysc_node_notif *)node); + break; + default: + LOGINT(ctx->ctx); + } + + FREE_ARRAY(ctx, node->exts, lysc_ext_instance_free); + + if (!inout) { + free(node); + } +} + +void +lysc_node_free(struct lysf_ctx *ctx, struct lysc_node *node, ly_bool unlink) +{ + struct lysc_node *next, *iter, **child_p; + + if (node->nodetype & (LYS_INPUT | LYS_OUTPUT)) { + /* inouts are part of actions and cannot be unlinked/freed separately, we can only free all the children */ + struct lysc_node_action_inout *inout = (struct lysc_node_action_inout *)node; + + LY_LIST_FOR_SAFE(inout->child, next, iter) { + lysc_node_free_(ctx, iter); + } + inout->child = NULL; + return; + } + + if (unlink) { + /* unlink from siblings */ + if (node->prev->next) { + node->prev->next = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } else { + /* unlinking the last node */ + if (node->parent) { + if (node->nodetype == LYS_ACTION) { + iter = (struct lysc_node *)lysc_node_actions(node->parent); + } else if (node->nodetype == LYS_NOTIF) { + iter = (struct lysc_node *)lysc_node_notifs(node->parent); + } else { + iter = (struct lysc_node *)lysc_node_child(node->parent); + } + LY_CHECK_ERR_RET(!iter, LOGINT(ctx->ctx), ); + } else if (node->nodetype == LYS_RPC) { + iter = (struct lysc_node *)node->module->compiled->rpcs; + } else if (node->nodetype == LYS_NOTIF) { + iter = (struct lysc_node *)node->module->compiled->notifs; + } else { + iter = node->module->compiled->data; + } + /* update the "last" pointer from the first node */ + iter->prev = node->prev; + } + + /* unlink from parent */ + if (node->parent) { + if (node->nodetype == LYS_ACTION) { + child_p = (struct lysc_node **)lysc_node_actions_p(node->parent); + } else if (node->nodetype == LYS_NOTIF) { + child_p = (struct lysc_node **)lysc_node_notifs_p(node->parent); + } else { + child_p = lysc_node_child_p(node->parent); + } + } else if (node->nodetype == LYS_RPC) { + child_p = (struct lysc_node **)&node->module->compiled->rpcs; + } else if (node->nodetype == LYS_NOTIF) { + child_p = (struct lysc_node **)&node->module->compiled->notifs; + } else { + child_p = &node->module->compiled->data; + } + if (child_p && (*child_p == node)) { + /* the node is the first child */ + *child_p = node->next; + } + } + + lysc_node_free_(ctx, node); +} + +void +lysc_module_free(struct lysf_ctx *ctx, struct lysc_module *module) +{ + struct lysc_node *node, *node_next; + + if (!module) { + return; + } + + LY_LIST_FOR_SAFE(module->data, node_next, node) { + lysc_node_free_(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysc_node *)module->rpcs, node_next, node) { + lysc_node_free_(ctx, node); + } + LY_LIST_FOR_SAFE((struct lysc_node *)module->notifs, node_next, node) { + lysc_node_free_(ctx, node); + } + FREE_ARRAY(ctx, module->exts, lysc_ext_instance_free); + + free(module); +} + +void +lys_module_free(struct lysf_ctx *ctx, struct lys_module *module, ly_bool remove_links) +{ + LY_ARRAY_COUNT_TYPE u; + + if (!module) { + return; + } + + assert(!module->implemented); + assert(!module->compiled); + + if (remove_links) { + /* remove derived identity links */ + LY_ARRAY_FOR(module->identities, u) { + lysc_ident_derived_unlink(&module->identities[u]); + } + } + FREE_ARRAY(ctx, module->identities, lysc_ident_free); + lysp_module_free(ctx, module->parsed); + + LY_ARRAY_FREE(module->augmented_by); + LY_ARRAY_FREE(module->deviated_by); + + lydict_remove(ctx->ctx, module->name); + lydict_remove(ctx->ctx, module->revision); + lydict_remove(ctx->ctx, module->ns); + lydict_remove(ctx->ctx, module->prefix); + lydict_remove(ctx->ctx, module->filepath); + lydict_remove(ctx->ctx, module->org); + lydict_remove(ctx->ctx, module->contact); + lydict_remove(ctx->ctx, module->dsc); + lydict_remove(ctx->ctx, module->ref); + + free(module); +} + +void +lysf_ctx_erase(struct lysf_ctx *ctx) +{ + struct lysc_ext *ext; + uint32_t i; + + for (i = 0; i < ctx->ext_set.count; ++i) { + ext = ctx->ext_set.objs[i]; + + lydict_remove(ctx->ctx, ext->name); + lydict_remove(ctx->ctx, ext->argname); + free(ext); + } + ly_set_erase(&ctx->ext_set, NULL); +} + +LIBYANG_API_DEF void +lyplg_ext_pfree_instance_substatements(const struct ly_ctx *ctx, struct lysp_ext_substmt *substmts) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysf_ctx fctx = {.ctx = (struct ly_ctx *)ctx}; + ly_bool node_free; + + LY_ARRAY_FOR(substmts, u) { + if (!substmts[u].storage) { + continue; + } + + switch (substmts[u].stmt) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_AUGMENT: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_GROUPING: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: + case LY_STMT_USES: { + struct lysp_node *child, *child_next; + + LY_LIST_FOR_SAFE(*((struct lysp_node **)substmts[u].storage), child_next, child) { + node_free = (child->nodetype & (LYS_INPUT | LYS_OUTPUT)) ? 1 : 0; + lysp_node_free(&fctx, child); + if (node_free) { + free(child); + } + } + *((struct lysc_node **)substmts[u].storage) = NULL; + break; + } + case LY_STMT_BASE: + /* multiple strings */ + FREE_ARRAY(ctx, **(const char ***)substmts[u].storage, lydict_remove); + break; + + case LY_STMT_BIT: + case LY_STMT_ENUM: + /* single enum */ + lysp_type_enum_free(&fctx, *(struct lysp_type_enum **)substmts[u].storage); + break; + + case LY_STMT_DEVIATE: + /* single deviate */ + lysp_deviate_free(&fctx, *(struct lysp_deviate **)substmts[u].storage); + break; + + case LY_STMT_DEVIATION: + /* single deviation */ + lysp_deviation_free(&fctx, *(struct lysp_deviation **)substmts[u].storage); + break; + + case LY_STMT_EXTENSION: + /* single extension */ + lysp_ext_free(&fctx, *(struct lysp_ext **)substmts[u].storage); + break; + + case LY_STMT_EXTENSION_INSTANCE: + /* multiple extension instances */ + FREE_ARRAY(&fctx, *(struct lysp_ext_instance **)substmts[u].storage, lysp_ext_instance_free); + break; + + case LY_STMT_FEATURE: + /* multiple features */ + FREE_ARRAY(&fctx, *(struct lysp_feature **)substmts[u].storage, lysp_feature_free); + break; + + case LY_STMT_IDENTITY: + /* multiple identities */ + FREE_ARRAY(&fctx, *(struct lysp_ident **)substmts[u].storage, lysp_ident_free); + break; + + case LY_STMT_IMPORT: + /* multiple imports */ + FREE_ARRAY(&fctx, *(struct lysp_import **)substmts[u].storage, lysp_import_free); + break; + + case LY_STMT_INCLUDE: + /* multiple includes */ + FREE_ARRAY(&fctx, *(struct lysp_include **)substmts[u].storage, lysp_include_free); + break; + + case LY_STMT_REFINE: + /* multiple refines */ + FREE_ARRAY(&fctx, *(struct lysp_refine **)substmts[u].storage, lysp_refine_free); + break; + + case LY_STMT_REVISION: + /* multiple revisions */ + FREE_ARRAY(&fctx, *(struct lysp_revision **)substmts[u].storage, lysp_revision_free); + break; + + case LY_STMT_CONFIG: + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_MANDATORY: + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + case LY_STMT_ORDERED_BY: + case LY_STMT_POSITION: + case LY_STMT_REQUIRE_INSTANCE: + case LY_STMT_STATUS: + case LY_STMT_VALUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + /* nothing to do */ + break; + + case LY_STMT_ARGUMENT: + case LY_STMT_BELONGS_TO: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_KEY: + case LY_STMT_MODIFIER: + case LY_STMT_NAMESPACE: + case LY_STMT_ORGANIZATION: + case LY_STMT_PREFIX: + case LY_STMT_PRESENCE: + case LY_STMT_REFERENCE: + case LY_STMT_REVISION_DATE: + case LY_STMT_UNITS: + /* single string */ + lydict_remove(ctx, *(const char **)substmts[u].storage); + break; + + case LY_STMT_LENGTH: + case LY_STMT_MUST: + case LY_STMT_PATTERN: + case LY_STMT_RANGE: + /* multiple restrictions */ + FREE_ARRAY(&fctx, *(struct lysp_restr **)substmts[u].storage, lysp_restr_free); + break; + + case LY_STMT_WHEN: + /* multiple whens */ + FREE_ARRAY(&fctx, *(struct lysp_when **)substmts[u].storage, lysp_when_free); + break; + + case LY_STMT_PATH: + /* single expression */ + lyxp_expr_free(ctx, *(struct lyxp_expr **)substmts[u].storage); + break; + + case LY_STMT_DEFAULT: + case LY_STMT_IF_FEATURE: + case LY_STMT_UNIQUE: + /* multiple qnames */ + FREE_ARRAY(ctx, *(struct lysp_qname **)substmts[u].storage, lysp_qname_free); + break; + + case LY_STMT_TYPEDEF: + /* multiple typedefs */ + FREE_ARRAY(&fctx, *(struct lysp_tpdf **)substmts[u].storage, lysp_tpdf_free); + break; + + case LY_STMT_TYPE: { + /* single type */ + struct lysp_type **type_p = substmts[u].storage; + + lysp_type_free(&fctx, *type_p); + free(*type_p); + break; + } + case LY_STMT_MODULE: + case LY_STMT_SUBMODULE: + /* single (sub)module */ + lysp_module_free(&fctx, *(struct lysp_module **)substmts[u].storage); + break; + + default: + LOGINT(ctx); + } + } + + LY_ARRAY_FREE(substmts); +} + +LIBYANG_API_DEF void +lyplg_ext_cfree_instance_substatements(const struct ly_ctx *ctx, struct lysc_ext_substmt *substmts) +{ + LY_ARRAY_COUNT_TYPE u; + struct lysf_ctx fctx = {.ctx = (struct ly_ctx *)ctx}; + ly_bool node_free; + + LY_ARRAY_FOR(substmts, u) { + if (!substmts[u].storage) { + continue; + } + + switch (substmts[u].stmt) { + case LY_STMT_NOTIFICATION: + case LY_STMT_INPUT: + case LY_STMT_OUTPUT: + case LY_STMT_ACTION: + case LY_STMT_RPC: + case LY_STMT_ANYDATA: + case LY_STMT_ANYXML: + case LY_STMT_CASE: + case LY_STMT_CHOICE: + case LY_STMT_CONTAINER: + case LY_STMT_LEAF: + case LY_STMT_LEAF_LIST: + case LY_STMT_LIST: { + struct lysc_node *child, *child_next; + + LY_LIST_FOR_SAFE(*((struct lysc_node **)substmts[u].storage), child_next, child) { + node_free = (child->nodetype & (LYS_INPUT | LYS_OUTPUT)) ? 1 : 0; + lysc_node_free_(&fctx, child); + if (node_free) { + free(child); + } + } + *((struct lysc_node **)substmts[u].storage) = NULL; + break; + } + case LY_STMT_USES: + case LY_STMT_CONFIG: + case LY_STMT_FRACTION_DIGITS: + case LY_STMT_MANDATORY: + case LY_STMT_MAX_ELEMENTS: + case LY_STMT_MIN_ELEMENTS: + case LY_STMT_ORDERED_BY: + case LY_STMT_POSITION: + case LY_STMT_REQUIRE_INSTANCE: + case LY_STMT_STATUS: + case LY_STMT_VALUE: + /* nothing to do */ + break; + + case LY_STMT_ARGUMENT: + case LY_STMT_CONTACT: + case LY_STMT_DESCRIPTION: + case LY_STMT_ERROR_APP_TAG: + case LY_STMT_ERROR_MESSAGE: + case LY_STMT_KEY: + case LY_STMT_MODIFIER: + case LY_STMT_NAMESPACE: + case LY_STMT_ORGANIZATION: + case LY_STMT_PRESENCE: + case LY_STMT_REFERENCE: + case LY_STMT_UNITS: { + /* single item */ + const char *str = *((const char **)substmts[u].storage); + + lydict_remove(ctx, str); + break; + } + case LY_STMT_BIT: + case LY_STMT_ENUM: { + /* sized array */ + struct lysc_type_bitenum_item *items = *((struct lysc_type_bitenum_item **)substmts[u].storage); + + FREE_ARRAY(&fctx, items, lysc_enum_item_free); + break; + } + case LY_STMT_LENGTH: + case LY_STMT_RANGE: { + /* single item */ + struct lysc_range *range = *((struct lysc_range **)substmts[u].storage); + + lysc_range_free(&fctx, range); + break; + } + case LY_STMT_MUST: { + /* sized array */ + struct lysc_must *musts = *((struct lysc_must **)substmts[u].storage); + + FREE_ARRAY(&fctx, musts, lysc_must_free); + break; + } + case LY_STMT_WHEN: + /* single item, expects a pointer */ + lysc_when_free(&fctx, substmts[u].storage); + break; + + case LY_STMT_PATTERN: { + /* sized array of pointers */ + struct lysc_pattern **patterns = *((struct lysc_pattern ***)substmts[u].storage); + + FREE_ARRAY(&fctx, patterns, lysc_pattern_free); + break; + } + case LY_STMT_TYPE: { + /* single item */ + struct lysc_type *type = *((struct lysc_type **)substmts[u].storage); + + lysc_type_free(&fctx, type); + break; + } + case LY_STMT_IDENTITY: { + /* sized array */ + struct lysc_ident *idents = *((struct lysc_ident **)substmts[u].storage); + + FREE_ARRAY(&fctx, idents, lysc_ident_free); + break; + } + case LY_STMT_EXTENSION_INSTANCE: { + /* sized array */ + struct lysc_ext_instance *exts = *((struct lysc_ext_instance **)substmts[u].storage); + + FREE_ARRAY(&fctx, exts, lysc_ext_instance_free); + break; + } + case LY_STMT_AUGMENT: + case LY_STMT_BASE: + case LY_STMT_BELONGS_TO: + case LY_STMT_DEFAULT: + case LY_STMT_DEVIATE: + case LY_STMT_DEVIATION: + case LY_STMT_EXTENSION: + case LY_STMT_FEATURE: + case LY_STMT_GROUPING: + case LY_STMT_IF_FEATURE: + case LY_STMT_IMPORT: + case LY_STMT_INCLUDE: + case LY_STMT_MODULE: + case LY_STMT_PATH: + case LY_STMT_PREFIX: + case LY_STMT_REFINE: + case LY_STMT_REVISION: + case LY_STMT_REVISION_DATE: + case LY_STMT_SUBMODULE: + case LY_STMT_TYPEDEF: + case LY_STMT_UNIQUE: + case LY_STMT_YANG_VERSION: + case LY_STMT_YIN_ELEMENT: + /* it is not possible to compile these statements */ + break; + + default: + LOGINT(ctx); + } + } + + LY_ARRAY_FREE(substmts); +} + +void +lysp_yang_ctx_free(struct lysp_yang_ctx *ctx) +{ + if (ctx) { + if (ctx->main_ctx == (struct lysp_ctx *)ctx) { + ly_set_erase(&ctx->tpdfs_nodes, NULL); + ly_set_erase(&ctx->grps_nodes, NULL); + } + assert(!ctx->tpdfs_nodes.count && !ctx->grps_nodes.count); + ly_set_erase(&ctx->ext_inst, NULL); + ly_set_rm_index(ctx->parsed_mods, ctx->parsed_mods->count - 1, NULL); + if (!ctx->parsed_mods->count) { + ly_set_free(ctx->parsed_mods, NULL); + } + free(ctx); + } +} + +void +lysp_yin_ctx_free(struct lysp_yin_ctx *ctx) +{ + if (ctx) { + if (ctx->main_ctx == (struct lysp_ctx *)ctx) { + ly_set_erase(&ctx->tpdfs_nodes, NULL); + ly_set_erase(&ctx->grps_nodes, NULL); + } + assert(!ctx->tpdfs_nodes.count && !ctx->grps_nodes.count); + ly_set_erase(&ctx->ext_inst, NULL); + ly_set_rm_index(ctx->parsed_mods, ctx->parsed_mods->count - 1, NULL); + if (!ctx->parsed_mods->count) { + ly_set_free(ctx->parsed_mods, NULL); + } + lyxml_ctx_free(ctx->xmlctx); + free(ctx); + } +} diff --git a/src/tree_schema_free.h b/src/tree_schema_free.h new file mode 100644 index 0000000..d79164b --- /dev/null +++ b/src/tree_schema_free.h @@ -0,0 +1,221 @@ +/** + * @file tree_schema_free.h + * @author Michal Vasko + * @brief internal freeing functions for YANG schema trees. + * + * Copyright (c) 2015 - 2022 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_TREE_SCHEMA_FREE_H_ +#define LY_TREE_SCHEMA_FREE_H_ + +#include "set.h" +#include "tree_schema.h" + +struct lysp_yang_ctx; +struct lysp_yin_ctx; + +struct lysf_ctx { + struct ly_ctx *ctx; + struct lys_module *mod; + struct ly_set ext_set; +}; + +/** + * @brief Macro to free [sized array](@ref sizedarrays) of items using the provided free function. The ARRAY itself is also freed, + * but the memory is not sanitized. + */ +#define FREE_ARRAY(CTX, ARRAY, FUNC) {LY_ARRAY_COUNT_TYPE c__; LY_ARRAY_FOR(ARRAY, c__){(FUNC)(CTX, &(ARRAY)[c__]);}LY_ARRAY_FREE(ARRAY);} + +/** + * @brief Macro to free the specified MEMBER of a structure using the provided free function. The memory is not sanitized. + */ +#define FREE_MEMBER(CTX, MEMBER, FUNC) if (MEMBER) {(FUNC)(CTX, MEMBER);free(MEMBER);} + +/** + * @brief Macro to free [sized array](@ref sizedarrays) of strings stored in the context's dictionary. The ARRAY itself is also freed, + * but the memory is not sanitized. + */ +#define FREE_STRINGS(CTX, ARRAY) {LY_ARRAY_COUNT_TYPE c__; LY_ARRAY_FOR(ARRAY, c__){lydict_remove(CTX, ARRAY[c__]);}LY_ARRAY_FREE(ARRAY);} + +/** + * @brief Free a parsed qualified name. + * + * @param[in] ctx libyang context. + * @param[in] qname Qualified name to free. + */ +void lysp_qname_free(const struct ly_ctx *ctx, struct lysp_qname *qname); + +/** + * @brief Free the parsed extension instance structure. + * + * @param[in] ctx Free context. + * @param[in] ext Parsed extension instance structure to free. Note that the instance itself is not freed. + */ +void lysp_ext_instance_free(struct lysf_ctx *ctx, struct lysp_ext_instance *ext); + +/** + * @brief Free a parsed restriction. + * + * @param[in] ctx Free context. + * @param[in] restr Restriction to free. + */ +void lysp_restr_free(struct lysf_ctx *ctx, struct lysp_restr *restr); + +/** + * @brief Free the parsed type structure. + * + * @param[in] ctx Free context. + * @param[in] type Parsed schema type structure to free. Note that the type itself is not freed. + */ +void lysp_type_free(struct lysf_ctx *ctx, struct lysp_type *type); + +/** + * @brief Free the parsed when structure. + * + * @param[in] ctx Free context. + * @param[in] when Parsed schema when structure to free. Note that the structure itself is not freed. + */ +void lysp_when_free(struct lysf_ctx *ctx, struct lysp_when *when); + +/** + * @brief Free the parsed deviate structure. + * + * @param[in] ctx Free context. + * @param[in] d Parsed schema deviate structure to free. Note that the structure itself is not freed. + */ +void lysp_deviate_free(struct lysf_ctx *ctx, struct lysp_deviate *d); + +/** + * @brief Free the parsed deviation structure. + * + * @param[in] ctx Free context. + * @param[in] dev Parsed schema deviation structure to free. Note that the structure itself is not freed. + */ +void lysp_deviation_free(struct lysf_ctx *ctx, struct lysp_deviation *dev); + +/** + * @brief Free a parsed node. + * + * @param[in] ctx Free context. + * @param[in] node Node to free. + */ +void lysp_node_free(struct lysf_ctx *ctx, struct lysp_node *node); + +/** + * @brief Free the parsed YANG schema tree structure. Works for both modules and submodules. + * + * @param[in] ctx Free context. + * @param[in] module Parsed YANG schema tree structure to free. + */ +void lysp_module_free(struct lysf_ctx *ctx, struct lysp_module *module); + +/** + * @brief Free the compiled extension instance structure. + * + * @param[in] ctx Free context. + * @param[in,out] ext Compiled extension instance structure to be cleaned. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +void lysc_ext_instance_free(struct lysf_ctx *ctx, struct lysc_ext_instance *ext); + +/** + * @brief Free the compiled if-feature structure. + * + * @param[in] ctx Free context. + * @param[in,out] iff Compiled if-feature structure to be cleaned. + * Since the structure is typically part of the sized array, the structure itself is not freed. + */ +void lysc_iffeature_free(struct lysf_ctx *ctx, struct lysc_iffeature *iff); + +/** + * @brief Free a compiled pattern. + * + * @param[in] ctx Free context. + * @param[in] pattern Pointer to the pattern to free. + */ +void lysc_pattern_free(struct lysf_ctx *ctx, struct lysc_pattern **pattern); + +/** + * @brief Free a bit/enum item. + * + * @param[in] ctx Free context. + * @param[in] item Bit/enum item to free. + */ +void lysc_enum_item_free(struct lysf_ctx *ctx, struct lysc_type_bitenum_item *item); + +/** + * @brief Free the compiled type structure. + * + * @param[in] ctx Free context. + * @param[in,out] type Compiled type structure to be freed. The structure has refcount, so it is freed only in case + * the value is decreased to 0. + */ +void lysc_type_free(struct lysf_ctx *ctx, struct lysc_type *type); + +/** + * @brief Free the compiled container node structure. + * + * Only the container-specific members are freed, for generic node free function, + * use ::lysc_node_free(). + * + * @param[in] ctx Free context. + * @param[in,out] node Compiled container node structure to be freed. + */ +void lysc_node_container_free(struct lysf_ctx *ctx, struct lysc_node_container *node); + +/** + * @brief Free the compiled node structure. + * + * @param[in] ctx Free context. + * @param[in] node Compiled node structure to be freed. + * @param[in] unlink Whether to first unlink the node before freeing. + */ +void lysc_node_free(struct lysf_ctx *ctx, struct lysc_node *node, ly_bool unlink); + +/** + * @brief Free the compiled schema structure. + * + * @param[in] ctx Free context. + * @param[in,out] module Compiled schema module structure to free. + */ +void lysc_module_free(struct lysf_ctx *ctx, struct lysc_module *module); + +/** + * @brief Free the schema structure. It just frees, it does not remove the schema from its context. + * + * @param[in] ctx Free context. + * @param[in,out] module Schema module structure to free. + * @param[in] remove_links Whether to remove links in other modules to structures in this module. Not needed if + * the whole context is being freed. + */ +void lys_module_free(struct lysf_ctx *ctx, struct lys_module *module, ly_bool remove_links); + +/** + * @brief Erase free context. + * + * @param[in] ctx Free context to erase. + */ +void lysf_ctx_erase(struct lysf_ctx *ctx); + +/** + * @brief Free lys parser context. + * + * @param[in] ctx Context to free. + */ +void lysp_yang_ctx_free(struct lysp_yang_ctx *ctx); + +/** + * @brief Free yin parser context + * + * @param[in] ctx Context to free. + */ +void lysp_yin_ctx_free(struct lysp_yin_ctx *ctx); + +#endif /* LY_TREE_SCHEMA_FREE_H_ */ diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h new file mode 100644 index 0000000..95dee0b --- /dev/null +++ b/src/tree_schema_internal.h @@ -0,0 +1,735 @@ +/** + * @file tree_schema_internal.h + * @author Radek Krejci + * @brief internal functions for YANG schema trees. + * + * Copyright (c) 2015 - 2022 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_TREE_SCHEMA_INTERNAL_H_ +#define LY_TREE_SCHEMA_INTERNAL_H_ + +#include + +#include "common.h" +#include "set.h" +#include "tree_schema.h" + +struct lysc_ctx; +struct lys_glob_unres; + +#define LY_YANG_SUFFIX ".yang" +#define LY_YANG_SUFFIX_LEN 5 +#define LY_YIN_SUFFIX ".yin" +#define LY_YIN_SUFFIX_LEN 4 + +#define YIN_NS_URI "urn:ietf:params:xml:ns:yang:yin:1" + +#define LY_PCRE2_MSG_LIMIT 256 + +/** + * @brief The maximum depth at which the last nested block is located. + * Designed to protect against corrupted input that causes a stack-overflow error. + * For yang language and json format, the block is bounded by "{ }". + * For the xml format, the opening and closing element tag is considered as the block. + */ +#define LY_MAX_BLOCK_DEPTH 500 + +/* list of the deviate modifications strings */ +extern const char * const ly_devmod_list[]; +#define ly_devmod2str(TYPE) ly_devmod_list[TYPE] + +/** + * @brief Check module version is at least 2 (YANG 1.1) because of the keyword presence. + * Logs error message and returns LY_EVALID in case of module in YANG version 1.0. + * @param[in] CTX yang parser context to get current module and for logging. + * @param[in] KW keyword allowed only in YANG version 1.1 (or later) - for logging. + * @param[in] PARENT parent statement where the KW is present - for logging. + */ +#define PARSER_CHECK_STMTVER2_RET(CTX, KW, PARENT) \ + if (PARSER_CUR_PMOD(CTX)->version < LYS_VERSION_1_1) {LOGVAL_PARSER((CTX), LY_VCODE_INCHILDSTMT2, KW, PARENT); return LY_EVALID;} + +/* These 2 macros checks YANG's identifier grammar rule */ +#define is_yangidentstartchar(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') +#define is_yangidentchar(c) ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || \ + c == '_' || c == '-' || c == '.') + +/* Macro to check YANG's yang-char grammar rule */ +#define is_yangutf8char(c) ((c >= 0x20 && c <= 0xd7ff) || c == 0x09 || c == 0x0a || c == 0x0d || \ + (c >= 0xe000 && c <= 0xfdcf) || (c >= 0xfdf0 && c <= 0xfffd) || \ + (c >= 0x10000 && c <= 0x1fffd) || (c >= 0x20000 && c <= 0x2fffd) || \ + (c >= 0x30000 && c <= 0x3fffd) || (c >= 0x40000 && c <= 0x2fffd) || \ + (c >= 0x50000 && c <= 0x5fffd) || (c >= 0x60000 && c <= 0x6fffd) || \ + (c >= 0x70000 && c <= 0x7fffd) || (c >= 0x80000 && c <= 0x8fffd) || \ + (c >= 0x90000 && c <= 0x9fffd) || (c >= 0xa0000 && c <= 0xafffd) || \ + (c >= 0xb0000 && c <= 0xbfffd) || (c >= 0xc0000 && c <= 0xcfffd) || \ + (c >= 0xd0000 && c <= 0xdfffd) || (c >= 0xe0000 && c <= 0xefffd) || \ + (c >= 0xf0000 && c <= 0xffffd) || (c >= 0x100000 && c <= 0x10fffd)) + +/** + * @brief Try to find object with MEMBER string matching the IDENT in the given ARRAY. + * Macro logs an error message and returns LY_EVALID in case of existence of a matching object. + * + * @param[in] CTX yang parser context for logging. + * @param[in] ARRAY [sized array](@ref sizedarrays) of a generic objects with member named MEMBER to search. + * @param[in] MEMBER Name of the member of the objects in the ARRAY to compare. + * @param[in] STMT Name of the compared YANG statements for logging. + * @param[in] IDENT String trying to find in the ARRAY's objects inside the MEMBER member. + */ +#define CHECK_UNIQUENESS(CTX, ARRAY, MEMBER, STMT, IDENT) \ + if (ARRAY) { \ + for (LY_ARRAY_COUNT_TYPE u_ = 0; u_ < LY_ARRAY_COUNT(ARRAY) - 1; ++u_) { \ + if (!strcmp((ARRAY)[u_].MEMBER, IDENT)) { \ + LOGVAL_PARSER(CTX, LY_VCODE_DUPIDENT, IDENT, STMT); \ + return LY_EVALID; \ + } \ + } \ + } + +#define CHECK_NONEMPTY(CTX, VALUE_LEN, STMT) \ + if (!VALUE_LEN) { \ + LOGWRN(PARSER_CTX(CTX), "Empty argument of %s statement does not make sense.", STMT); \ + } + +/* + * Additional YANG constants + */ +#define Y_TAB_SPACES 8 /**< number of spaces instead of tab character */ +#define LY_TYPE_DEC64_FD_MAX 18 /**< Maximal value of decimal64's fraction-digits */ + +/** + * @brief List of YANG statement groups - the (sub)module's substatements + */ +enum yang_module_stmt { + Y_MOD_MODULE_HEADER, + Y_MOD_LINKAGE, + Y_MOD_META, + Y_MOD_REVISION, + Y_MOD_BODY +}; + +/** + * @brief Types of arguments of YANG statements + */ +enum yang_arg { + Y_IDENTIF_ARG, /**< YANG "identifier-arg-str" rule */ + Y_PREF_IDENTIF_ARG, /**< YANG "identifier-ref-arg-str" or node-identifier rule */ + Y_STR_ARG, /**< YANG "string" rule */ + Y_MAYBE_STR_ARG /**< optional YANG "string" rule */ +}; + +#define PARSER_CUR_PMOD(CTX) ((struct lysp_module *)(CTX)->parsed_mods->objs[(CTX)->parsed_mods->count - 1]) +#define PARSER_CTX(CTX) ((CTX) ? PARSER_CUR_PMOD(CTX)->mod->ctx : NULL) +#define LOGVAL_PARSER(CTX, ...) LOGVAL(PARSER_CTX(CTX), __VA_ARGS__) + +struct lysp_ctx { + LYS_INFORMAT format; /**< parser format */ + struct ly_set tpdfs_nodes; /**< Set of nodes that contain typedef(s). Invalid in case of + submodule, use ::lysp_ctx.main_ctx instead. */ + struct ly_set grps_nodes; /**< Set of nodes that contain grouping(s). Invalid in case of + submodule, use ::lysp_ctx.main_ctx instead. */ + struct ly_set ext_inst; /**< parsed extension instances to finish parsing */ + + struct ly_set *parsed_mods; /**< (sub)modules being parsed, the last one is the current */ + struct lysp_ctx *main_ctx; /**< This pointer must not be NULL. If this context deals with the submodule, + then should be set to the context of the module to which it belongs, + otherwise it points to the beginning of this structure. */ +}; + +/** + * @brief Internal context for yang schema parser. + */ +struct lysp_yang_ctx { + LYS_INFORMAT format; /**< parser format */ + struct ly_set tpdfs_nodes; /**< Set of nodes that contain typedef(s). Invalid in case of + submodule, use ::lysp_ctx.main_ctx instead. */ + struct ly_set grps_nodes; /**< Set of nodes that contain grouping(s). Invalid in case of + submodule, use ::lysp_ctx.main_ctx instead. */ + struct ly_set ext_inst; /**< parsed extension instances to finish parsing */ + + struct ly_set *parsed_mods; /**< (sub)modules being parsed, the last one is the current */ + struct lysp_ctx *main_ctx; /**< This pointer must not be NULL. If this context deals with the submodule, + then should be set to the context of the module to which it belongs, + otherwise it points to the beginning of this structure. */ + struct ly_in *in; /**< input handler for the parser */ + uint64_t indent; /**< current position on the line for YANG indentation */ + uint32_t depth; /**< current number of nested blocks, see ::LY_MAX_BLOCK_DEPTH */ +}; + +/** + * @brief Internal context for yin schema parser. + */ +struct lysp_yin_ctx { + LYS_INFORMAT format; /**< parser format */ + struct ly_set tpdfs_nodes; /**< Set of nodes that contain typedef(s). Invalid in case of + submodule, use ::lysp_ctx.main_ctx instead. */ + struct ly_set grps_nodes; /**< Set of nodes that contain grouping(s). Invalid in case of + submodule, use ::lysp_ctx.main_ctx instead. */ + struct ly_set ext_inst; /**< parsed extension instances to finish parsing */ + + struct ly_set *parsed_mods; /**< (sub)modules being parsed, the last one is the current */ + struct lysp_ctx *main_ctx; /**< This pointer must not be NULL. If this context deals with the submodule, + then should be set to the context of the module to which it belongs, + otherwise it points to the beginning of this structure. */ + struct lyxml_ctx *xmlctx; /**< context for xml parser */ +}; + +/** + * @brief Check that @p c is valid UTF8 code point for YANG string. + * + * @param[in] ctx parser context for logging. + * @param[in] c UTF8 code point of a character to check. + * @return LY_ERR values. + */ +LY_ERR lysp_check_stringchar(struct lysp_ctx *ctx, uint32_t c); + +/** + * @brief Check that @p c is valid UTF8 code point for YANG identifier. + * + * @param[in] ctx parser context for logging. If NULL, does not log. + * @param[in] c UTF8 code point of a character to check. + * @param[in] first Flag to check the first character of an identifier, which is more restricted. + * @param[in,out] prefix Storage for internally used flag in case of possible prefixed identifiers: + * 0 - colon not yet found (no prefix) + * 1 - @p c is the colon character + * 2 - prefix already processed, now processing the identifier + * + * If the identifier cannot be prefixed, NULL is expected. + * @return LY_ERR values. + */ +LY_ERR lysp_check_identifierchar(struct lysp_ctx *ctx, uint32_t c, ly_bool first, uint8_t *prefix); + +/** + * @brief Check the currently present prefixes in the module for collision with the new one. + * + * @param[in] ctx Context for logging. + * @param[in] imports List of current imports of the module to check prefix collision. + * @param[in] module_prefix Prefix of the module to check collision. + * @param[in] value Newly added prefix value (including its location to distinguish collision with itself). + * @return LY_EEXIST when prefix is already used in the module, LY_SUCCESS otherwise + */ +LY_ERR lysp_check_prefix(struct lysp_ctx *ctx, struct lysp_import *imports, const char *module_prefix, const char **value); + +/** + * @brief Check date string (4DIGIT "-" 2DIGIT "-" 2DIGIT) + * + * @param[in] ctx Optional context for logging. + * @param[in] date Date string to check (non-necessarily terminated by \0) + * @param[in] date_len Length of the date string, 10 expected. + * @param[in] stmt Statement name for error message. + * @return LY_ERR value. + */ +LY_ERR lysp_check_date(struct lysp_ctx *ctx, const char *date, size_t date_len, const char *stmt); + +/** + * @brief Find type specified type definition. + * + * @param[in] id Name of the type including possible prefix. Module where the prefix is being searched is start_module. + * @param[in] start_node Context node where the type is being instantiated to be able to search typedefs in parents. + * @param[in] start_module Module where the type is being instantiated for search for typedefs. + * @param[in] ext Extension where the type is being instantiated, if any. + * @param[out] type Built-in type identifier of the id. If #LY_TYPE_UNKNOWN, tpdf is expected to contain found YANG schema typedef statement. + * @param[out] tpdf Found type definition. + * @param[out] node Node where the found typedef is defined, NULL in case of a top-level typedef. + * @return LY_ERR value. + */ +LY_ERR lysp_type_find(const char *id, struct lysp_node *start_node, const struct lysp_module *start_module, + const struct lysc_ext_instance *ext, LY_DATA_TYPE *type, const struct lysp_tpdf **tpdf, struct lysp_node **node); + +/** + * @brief Check names of typedefs in the parsed module to detect collisions. + * + * @param[in] ctx Parser context for logging and to maintain tpdfs_nodes + * @param[in] mod Module where the type is being defined. + * @return LY_ERR value. + */ +LY_ERR lysp_check_dup_typedefs(struct lysp_ctx *ctx, struct lysp_module *mod); + +/** + * @brief Check names of groupings in the parsed module to detect collisions. + * + * @param[in] ctx Parser context for logging and to maintain grps_nodes. + * @param[in] mod Module where the type is being defined. + * @return LY_ERR value. + */ +LY_ERR lysp_check_dup_groupings(struct lysp_ctx *ctx, struct lysp_module *mod); + +/** + * @brief Check names of features in the parsed module and submodules to detect collisions. + * + * @param[in] ctx Parser context. + * @param[in] mod Module where the type is being defined. + * @return LY_ERR value. + */ +LY_ERR lysp_check_dup_features(struct lysp_ctx *ctx, struct lysp_module *mod); + +/** + * @brief Check names of identities in the parsed module and submodules to detect collisions. + * + * @param[in] ctx Parser context. + * @param[in] mod Module where the type is being defined. + * @return LY_ERR value. + */ +LY_ERR lysp_check_dup_identities(struct lysp_ctx *ctx, struct lysp_module *mod); + +/** + * @brief Just move the newest revision into the first position, does not sort the rest + * @param[in] revs Sized-array of the revisions in a printable schema tree. + */ +void lysp_sort_revisions(struct lysp_revision *revs); + +/** + * @brief Validate enum name. + * + * @param[in] ctx yang parser context for logging. + * @param[in] name String to check. + * @param[in] name_len Length of name. + * + * @return LY_ERR values + */ +LY_ERR lysp_check_enum_name(struct lysp_ctx *ctx, const char *name, size_t name_len); + +/** + * @brief Find source data for a specific module, parse it, and add into the context. + * + * @param[in] ctx libyang context. + * @param[in] name Name of the module to load. + * @param[in] revision Optional revision of the module to load. If NULL, the newest revision is loaded. + * @param[in,out] new_mods Set of all the new mods added to the context. Includes this module and all of its imports. + * @param[out] mod Created module structure. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +LY_ERR lys_parse_load(struct ly_ctx *ctx, const char *name, const char *revision, struct ly_set *new_mods, + struct lys_module **mod); + +/** + * @brief Parse included submodules into the simply parsed YANG module. + * + * YANG 1.0 does not require the main module to include all the submodules. Therefore, parsing submodules can cause + * reallocating and extending the includes array in the main module by the submodules included only in submodules. + * + * @param[in] pctx main parser context + * @param[in] pmod Parsed module with the includes array to be processed. + * @param[in,out] new_mods Set of all the new mods added to the context. Includes this module and all of its imports. + * @return LY_ERR value. + */ +LY_ERR lysp_load_submodules(struct lysp_ctx *pctx, struct lysp_module *pmod, struct ly_set *new_mods); + +/** + * @brief Get address of a node's actions list if any. + * Decides the node's type and in case it has an actions list, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's actions member if any, NULL otherwise. + */ +struct lysp_node_action **lysp_node_actions_p(struct lysp_node *node); + +/** + * @brief Get address of a node's notifications list if any. + * Decides the node's type and in case it has a notifications list, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's notifs member if any, NULL otherwise. + */ +struct lysp_node_notif **lysp_node_notifs_p(struct lysp_node *node); + +/** + * @brief Get address of a node's child pointer if any. + * Decides the node's type and in case it has a children list, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's child member if any, NULL otherwise. + */ +struct lysp_node **lysp_node_child_p(struct lysp_node *node); + +/** + * @brief Get the address of the node's musts member, if any. + * Decides the node's type and in case it has a musts member, returns its address. + * + * @param[in] node Node to examine. + * @return The address of the node's musts member if any, NULL otherwise. + */ +struct lysp_restr **lysp_node_musts_p(const struct lysp_node *node); + +/** + * @brief Get the node's musts member, if any. + * Decides the node's type and in case it has a musts member, returns its address. + * + * @param[in] node Node to examine. + * @return The node's musts member if any, NULL otherwise. + */ +struct lysp_restr *lysp_node_musts(const struct lysp_node *node); + +/** + * @brief Get the address of the node's when member, if any. + * Decides the node's type and in case it has a when, returns it. + * + * @param[in] node Node to examine. + * @return The address of the node's when member if any, NULL otherwise. + */ +struct lysp_when **lysp_node_when_p(const struct lysp_node *node); + +/** + * @brief Get the node's when member, if any. + * Decides the node's type and in case it has a when, returns it. + * + * @param[in] node Node to examine. + * @return The node's when member if any, NULL otherwise. + */ +struct lysp_when *lysp_node_when(const struct lysp_node *node); + +/** + * @brief Get address of a node's child pointer if any. + * Decides the node's type and in case it has a children list, returns its address. + * + * Do not use for RPC and action nodes. + * + * @param[in] node Node to check. + * @return Address of the node's child member if any, NULL otherwise. + */ +struct lysc_node **lysc_node_child_p(const struct lysc_node *node); + +/** + * @brief Get address of a node's notifs pointer if any. + * Decides the node's type and in case it has a notifs array, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's notifs member if any, NULL otherwise. + */ +struct lysc_node_notif **lysc_node_notifs_p(struct lysc_node *node); + +/** + * @brief Get address of a node's actions pointer if any. + * Decides the node's type and in case it has a actions array, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's actions member if any, NULL otherwise. + */ +struct lysc_node_action **lysc_node_actions_p(struct lysc_node *node); + +/** + * @brief Get address of a node's when member if any. + * Decides the node's type and in case it has a when member, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's when member if any, NULL otherwise. + */ +struct lysc_when ***lysc_node_when_p(const struct lysc_node *node); + +/** + * @brief Get address of a node's musts member if any. + * Decides the node's type and in case it has a musts member, returns its address. + * + * @param[in] node Node to check. + * @return Address of the node's musts member if any, NULL otherwise. + */ +struct lysc_must **lysc_node_musts_p(const struct lysc_node *node); + +/** + * @brief Find parsed extension definition for the given extension instance. + * + * @param[in] ctx libyang context. + * @param[in] ext Extension instance for which the definition will be searched. + * @param[out] ext_mod Module of the extension definition of @p ext. + * @param[out] ext_def Optional found extension definition. + * @return LY_SUCCESS when the definition was found. + * @return LY_EVALID when the extension instance is invalid and/or the definition not found. + */ +LY_ERR lysp_ext_find_definition(const struct ly_ctx *ctx, const struct lysp_ext_instance *ext, const struct lys_module **ext_mod, + struct lysp_ext **ext_def); + +/** + * @brief Get schema node in extension instance according to the given parameters. + * + * Wraps ::lys_getnext_ext() and match according to the given arguments. + * + * @param[in] ext Extension instance which top-level schema node is being searched. + * @param[in] module Optional parameter to match the extension instance's (and its data) module. + * @param[in] name Name of the schema node to find, if the string is not NULL-terminated, the @p name_len must be set. + * @param[in] name_len Length of the @p name string, use in case the @p name is not NULL-terminated string. + * @param[in] nodetype Allowed [type of the node](@ref schemanodetypes). + * @param[in] options ORed [lys_getnext options](@ref sgetnextflags). + * @return Found schema node if there is some satisfy the provided requirements. + */ +const struct lysc_node *lysc_ext_find_node(const struct lysc_ext_instance *ext, const struct lys_module *module, + const char *name, size_t name_len, uint16_t nodetype, uint32_t options); + +/** + * @brief When the module comes from YIN format, the argument name is unknown because of missing extension definition + * (it might come from import modules which is not yet parsed at that time). Therefore, all the attributes are stored + * as substatements and resolving argument is postponed. + * + * @param[in] ctx libyang context + * @param[in] ext_p Parsed extension to be updated. + * @return LY_ERR value. + */ +LY_ERR lysp_ext_instance_resolve_argument(struct ly_ctx *ctx, struct lysp_ext_instance *ext_p); + +/** + * @brief Iterate over the specified type of the extension instances + * + * @param[in] ext ([Sized array](@ref sizedarrays)) of extensions to explore + * @param[in] index Index in the @p ext array where to start searching (first call with 0, the consequent calls with + * the returned index increased by 1 (until the iteration is not terminated by returning LY_ARRAY_COUNT(ext). + * @param[in] substmt The statement the extension is supposed to belong to. + * @result index in the ext array, LY_ARRAY_COUNT(ext) value if not present. + */ +LY_ARRAY_COUNT_TYPE lysp_ext_instance_iter(struct lysp_ext_instance *ext, LY_ARRAY_COUNT_TYPE index, enum ly_stmt substmt); + +/** + * @brief Stringify YANG built-in type. + * + * @param[in] basetype Built-in type ID to stringify. + * @return Constant string with the name of the built-in type. + */ +const char *lys_datatype2str(LY_DATA_TYPE basetype); + +/** + * @brief Implement a module and resolve all global unres. + * + * @param[in] mod Module to implement. + * @param[in] features Features to set, see ::lys_set_features(). + * @param[in] unres Global unres with all the created modules. + * @return LY_SUCCESS on success. + * @return LY_ERR on error. + */ +LY_ERR _lys_set_implemented(struct lys_module *mod, const char **features, struct lys_glob_unres *unres); + +/** + * @brief Create dependency sets for all modules in a context. + * Also sets to_compile flags for all the modules that should be (re)compiled. + * + * @param[in] ctx Context to use. + * @param[in,out] main_set Set of dependency module sets. + * @param[in] mod Optional only module whose dependency set is needed, otherwise all sets are created. + * @return LY_ERR value. + */ +LY_ERR lys_unres_dep_sets_create(struct ly_ctx *ctx, struct ly_set *main_set, struct lys_module *mod); + +/** + * @brief Revert changes stored in global compile context after a failed compilation. + * + * @param[in] ctx libyang context. + * @param[in] unres Global unres to use. + */ +void lys_unres_glob_revert(struct ly_ctx *ctx, struct lys_glob_unres *unres); + +/** + * @brief Erase the global compile context. + * + * @param[in] unres Global unres to erase. + */ +void lys_unres_glob_erase(struct lys_glob_unres *unres); + +typedef LY_ERR (*lys_custom_check)(const struct ly_ctx *ctx, struct lysp_module *mod, struct lysp_submodule *submod, + void *check_data); + +/** + * @brief Parse a module and add it into the context. + * + * @param[in] ctx libyang context where to process the data model. + * @param[in] in Input structure. + * @param[in] format Format of the input data (YANG or YIN). + * @param[in] custom_check Callback to check the parsed schema before it is accepted. + * @param[in] check_data Caller's data to pass to the custom_check callback. + * @param[in,out] new_mods Set of all the new mods added to the context. Includes this module and all of its imports. + * @param[out] module Created module. + * @return LY_SUCCESS on success. + * @return LY_ERR on error, @p new_mods may be modified. + */ +LY_ERR lys_parse_in(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, lys_custom_check custom_check, + void *check_data, struct ly_set *new_mods, struct lys_module **module); + +/** + * @brief Parse submodule. + * + * The latest_revision flag of submodule is updated. + * + * @param[in] ctx libyang context where to process the data model. + * @param[in] in Input structure. + * @param[in] format Format of the input data (YANG or YIN). + * @param[in] main_ctx Parser context of the main module. + * @param[in] custom_check Callback to check the parsed schema before it is accepted. + * @param[in] check_data Caller's data to pass to the custom_check callback. + * @param[in] new_mods Set of all the new mods added to the context. Includes this module and all of its imports. + * @param[out] submodule Parsed submodule. + * @return LY_ERR value. + */ +LY_ERR lys_parse_submodule(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, struct lysp_ctx *main_ctx, + lys_custom_check custom_check, void *check_data, struct ly_set *new_mods, struct lysp_submodule **submodule); + +/** + * @brief Fill filepath value if available in input handler @p in + * + * @param[in] ctx Context with dictionary where the filepath value will be stored. + * @param[in] in Input handler to examine (filepath is not available for all the input types). + * @param[out] filepath Address of the variable where the filepath is stored. + */ +void lys_parser_fill_filepath(struct ly_ctx *ctx, struct ly_in *in, const char **filepath); + +/** + * @brief Get the @ref ifftokens from the given position in the 2bits array + * (libyang format of the if-feature expression). + * @param[in] list The 2bits array with the compiled if-feature expression. + * @param[in] pos Position (0-based) to specify from which position get the operator. + */ +uint8_t lysc_iff_getop(uint8_t *list, size_t pos); + +/** + * @brief match yang keyword + * + * @param[in,out] in Input structure, is updated. + * @param[in,out] indent Pointer to the counter of current position on the line for YANG indentation (optional). + * @return yang_keyword values. + */ +enum ly_stmt lysp_match_kw(struct ly_in *in, uint64_t *indent); + +/** + * @brief Generate path of the given node in the requested format. + * + * @param[in] node Schema path of this node will be generated. + * @param[in] parent Build relative path only until this parent is found. If NULL, the full absolute path is printed. + * @param[in] pathtype Format of the path to generate. + * @param[in,out] buffer Prepared buffer of the @p buflen length to store the generated path. + * If NULL, memory for the complete path is allocated. + * @param[in] buflen Size of the provided @p buffer. + * @return NULL in case of memory allocation error, path of the node otherwise. + * In case the @p buffer is NULL, the returned string is dynamically allocated and caller is responsible to free it. + */ +char *lysc_path_until(const struct lysc_node *node, const struct lysc_node *parent, LYSC_PATH_TYPE pathtype, char *buffer, + size_t buflen); + +/** + * @brief Get format-specific prefix for a module. + * + * This function is available for type plugins via ::lyplg_type_get_prefix() API function. + * + * @param[in] mod Module whose prefix to get. + * @param[in] format Format of the prefix. + * @param[in] prefix_data Format-specific data based on @p format: + * LY_VALUE_CANON - NULL + * 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_JSON - NULL + * LY_VALUE_LYB - NULL + * @return Module prefix to print. + * @return NULL on error. + */ +const char *ly_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data); + +/** + * @brief Resolve format-specific prefixes to modules. + * + * @param[in] ctx libyang context. + * @param[in] prefix Prefix to resolve. + * @param[in] prefix_len Length of @p prefix. + * @param[in] format Format of the prefix. + * @param[in] prefix_data Format-specific data based on @p format: + * LY_VALUE_CANON - NULL + * 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_JSON - NULL + * LY_VALUE_LYB - NULL + * @return Resolved prefix module, + * @return NULL otherwise. + */ +const struct lys_module *ly_resolve_prefix(const struct ly_ctx *ctx, const void *prefix, size_t prefix_len, + LY_VALUE_FORMAT format, const void *prefix_data); + +/** + * @brief Learn whether @p PMOD needs to be recompiled if it is implemented. + * + * @param[in] PMOD Parsed module or submodule. + * @return Whether it has statements that are recompiled or not. + */ +#define LYSP_HAS_RECOMPILED(PMOD) \ + (PMOD->data || PMOD->rpcs || PMOD->notifs || PMOD->exts) + +/** + * @brief Learn whether the module has statements that need to be recompiled or not. + * + * @param[in] mod Module to examine. + * @return Whether it has statements that are recompiled or not. + */ +ly_bool lys_has_recompiled(const struct lys_module *mod); + +/** + * @brief Learn whether @p PMOD needs to be compiled if it is implemented. + * + * @param[in] PMOD Parsed module or submodule. + * @return Whether it needs (has) a compiled module or not. + */ +#define LYSP_HAS_COMPILED(PMOD) \ + (LYSP_HAS_RECOMPILED(PMOD) || PMOD->augments || PMOD->deviations) + +/** + * @brief Learn whether the module has statements that need to be compiled or not. + * + * @param[in] mod Module to examine. + * @return Whether it needs compiled module or not. + */ +ly_bool lys_has_compiled(const struct lys_module *mod); + +/** + * @brief Learn whether the module has any grouping statements or not. + * + * @param[in] mod Module to examine. + * @return Whether it has groupings or not. + */ +ly_bool lys_has_dep_mods(const struct lys_module *mod); + +/** + * @brief Get YANG string keyword of a statement. + * + * @return YANG string keyword of the statement. + */ +const char *lys_stmt_str(enum ly_stmt stmt); + +/** + * @brief Get YIN argument (attribute) name of a statement. + * + * @return YIN argument name, if any. + */ +const char *lys_stmt_arg(enum ly_stmt stmt); + +#define LY_STMT_FLAG_YIN 0x1 /**< has YIN element */ +#define LY_STMT_FLAG_ID 0x2 /**< the value is identifier -> no quotes */ + +/** + * @brief Get statement printer flags. + * + * @return Additional statement information as LY_STMT_FLAG_* flags. + */ +uint8_t lys_stmt_flags(enum ly_stmt stmt); + +/** + * @brief Learn whether the module qualifies for a single dep set with only this module or not. + * + * @param[in] mod Module to examine. + * @return Whether it qualifies as a single dep set or not. + */ +#define LYS_IS_SINGLE_DEP_SET(mod) \ + (!(mod)->parsed->features && (!lys_has_compiled(mod) || ((mod)->compiled && !lys_has_recompiled(mod)))) + +/** + * @brief Get pointer to a compiled ext instance storage for a specific statement. + * + * @param[in] ext Compiled ext instance. + * @param[in] stmt Compiled statement. Can be a mask when the first match is returned, it is expected the storage is + * the same for all the masked statements. + * @param[out] storage_p Pointer to a compiled ext instance substatement storage, NULL if was not compiled. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the substatement is not supported. + */ +LY_ERR lyplg_ext_get_storage_p(const struct lysc_ext_instance *ext, int stmt, const void ***storage_p); + +#endif /* LY_TREE_SCHEMA_INTERNAL_H_ */ diff --git a/src/validation.c b/src/validation.c new file mode 100644 index 0000000..6db020a --- /dev/null +++ b/src/validation.c @@ -0,0 +1,2029 @@ +/** + * @file validation.c + * @author Michal Vasko + * @brief Validation + * + * Copyright (c) 2019 - 2022 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 /* asprintf, strdup */ + +#include "validation.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "diff.h" +#include "hash_table.h" +#include "log.h" +#include "parser_data.h" +#include "parser_internal.h" +#include "plugins_exts.h" +#include "plugins_exts/metadata.h" +#include "plugins_types.h" +#include "set.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_schema.h" +#include "tree_schema_internal.h" +#include "xpath.h" + +LY_ERR +lyd_val_diff_add(const struct lyd_node *node, enum lyd_diff_op op, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *new_diff = NULL; + const struct lyd_node *prev_inst; + char *key = NULL, *value = NULL, *position = NULL; + size_t buflen = 0, bufused = 0; + uint32_t pos; + + assert((op == LYD_DIFF_OP_DELETE) || (op == LYD_DIFF_OP_CREATE)); + + if ((op == LYD_DIFF_OP_CREATE) && lysc_is_userordered(node->schema)) { + if (lysc_is_dup_inst_list(node->schema)) { + pos = lyd_list_pos(node); + + /* generate position meta */ + if (pos > 1) { + if (asprintf(&position, "%" PRIu32, pos - 1) == -1) { + LOGMEM(LYD_CTX(node)); + ret = LY_EMEM; + goto cleanup; + } + } else { + position = strdup(""); + LY_CHECK_ERR_GOTO(!position, LOGMEM(LYD_CTX(node)); ret = LY_EMEM, cleanup); + } + } else { + if (node->prev->next && (node->prev->schema == node->schema)) { + prev_inst = node->prev; + } else { + /* first instance */ + prev_inst = NULL; + } + + if (node->schema->nodetype == LYS_LIST) { + /* generate key meta */ + if (prev_inst) { + LY_CHECK_GOTO(ret = lyd_path_list_predicate(prev_inst, &key, &buflen, &bufused, 0), cleanup); + } else { + key = strdup(""); + LY_CHECK_ERR_GOTO(!key, LOGMEM(LYD_CTX(node)); ret = LY_EMEM, cleanup); + } + } else { + /* generate value meta */ + if (prev_inst) { + value = strdup(lyd_get_value(prev_inst)); + LY_CHECK_ERR_GOTO(!value, LOGMEM(LYD_CTX(node)); ret = LY_EMEM, cleanup); + } else { + value = strdup(""); + LY_CHECK_ERR_GOTO(!value, LOGMEM(LYD_CTX(node)); ret = LY_EMEM, cleanup); + } + } + } + } + + /* create new diff tree */ + LY_CHECK_GOTO(ret = lyd_diff_add(node, op, NULL, NULL, key, value, position, NULL, NULL, &new_diff), cleanup); + + /* merge into existing diff */ + ret = lyd_diff_merge_all(diff, new_diff, 0); + +cleanup: + lyd_free_tree(new_diff); + free(key); + free(value); + free(position); + return ret; +} + +/** + * @brief Evaluate all relevant "when" conditions of a node. + * + * @param[in] tree Data tree. + * @param[in] node Node whose relevant when conditions will be evaluated. + * @param[in] schema Schema node of @p node. It may not be possible to use directly if @p node is opaque. + * @param[in] xpath_options Additional XPath options to use. + * @param[out] disabled First when that evaluated false, if any. + * @return LY_SUCCESS on success. + * @return LY_EINCOMPLETE if a referenced node does not have its when evaluated. + * @return LY_ERR value on error. + */ +static LY_ERR +lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, const struct lysc_node *schema, + uint32_t xpath_options, const struct lysc_when **disabled) +{ + LY_ERR ret; + const struct lyd_node *ctx_node; + struct lyxp_set xp_set; + LY_ARRAY_COUNT_TYPE u; + + assert(!node->schema || (node->schema == schema)); + + *disabled = NULL; + + do { + const struct lysc_when *when; + struct lysc_when **when_list = lysc_node_when(schema); + + LY_ARRAY_FOR(when_list, u) { + when = when_list[u]; + + /* get context node */ + if (when->context == schema) { + ctx_node = node; + } else { + assert((!when->context && !node->parent) || (when->context == node->parent->schema)); + ctx_node = lyd_parent(node); + } + + /* evaluate when */ + memset(&xp_set, 0, sizeof xp_set); + ret = lyxp_eval(LYD_CTX(node), when->cond, schema->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, + ctx_node, ctx_node, tree, NULL, &xp_set, LYXP_SCHEMA | xpath_options); + lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN); + + /* return error or LY_EINCOMPLETE for dependant unresolved when */ + LY_CHECK_RET(ret); + + if (!xp_set.val.bln) { + /* false when */ + *disabled = when; + return LY_SUCCESS; + } + } + + schema = schema->parent; + } while (schema && (schema->nodetype & (LYS_CASE | LYS_CHOICE))); + + return LY_SUCCESS; +} + +/** + * @brief Evaluate when conditions of collected unres nodes. + * + * @param[in,out] tree Data tree, is updated if some nodes are autodeleted. + * @param[in] mod Module of the @p tree to take into consideration when deleting @p tree and moving it. + * If set, it is expected @p tree should point to the first node of @p mod. Otherwise it will simply be + * the first top-level sibling. + * @param[in] node_when Set with nodes with "when" conditions. + * @param[in] xpath_options Additional XPath options to use. + * @param[in,out] node_types Set with nodes with unresolved types, remove any with false "when" parents. + * @param[in,out] diff Validation diff. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +static LY_ERR +lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, struct ly_set *node_when, + uint32_t xpath_options, struct ly_set *node_types, struct lyd_node **diff) +{ + LY_ERR rc, r; + uint32_t i, idx; + const struct lysc_when *disabled; + struct lyd_node *node = NULL, *elem; + + if (!node_when->count) { + return LY_SUCCESS; + } + + i = node_when->count; + do { + --i; + node = node_when->dnodes[i]; + LOG_LOCSET(node->schema, node, NULL, NULL); + + /* evaluate all when expressions that affect this node's existence */ + r = lyd_validate_node_when(*tree, node, node->schema, xpath_options, &disabled); + if (!r) { + if (disabled) { + /* when false */ + if (node->flags & LYD_WHEN_TRUE) { + /* autodelete */ + lyd_del_move_root(tree, node, mod); + if (diff) { + /* add into diff */ + LY_CHECK_GOTO(rc = lyd_val_diff_add(node, LYD_DIFF_OP_DELETE, diff), error); + } + + /* remove from node types set, if present */ + if (node_types && node_types->count) { + LYD_TREE_DFS_BEGIN(node, elem) { + /* only term nodes with a validation callback can be in node_types */ + if ((elem->schema->nodetype & LYD_NODE_TERM) && + ((struct lysc_node_leaf *)elem->schema)->type->plugin->validate && + ly_set_contains(node_types, elem, &idx)) { + LY_CHECK_GOTO(rc = ly_set_rm_index(node_types, idx, NULL), error); + } + LYD_TREE_DFS_END(node, elem); + } + } + + /* free */ + lyd_free_tree(node); + } else { + /* invalid data */ + LOGVAL(LYD_CTX(node), LY_VCODE_NOWHEN, disabled->cond->expr); + rc = LY_EVALID; + goto error; + } + } else { + /* when true */ + node->flags |= LYD_WHEN_TRUE; + } + + /* remove this node from the set keeping the order, its when was resolved */ + ly_set_rm_index_ordered(node_when, i, NULL); + } else if (r != LY_EINCOMPLETE) { + /* error */ + rc = r; + goto error; + } + + LOG_LOCBACK(1, 1, 0, 0); + } while (i); + + return LY_SUCCESS; + +error: + LOG_LOCBACK(1, 1, 0, 0); + return rc; +} + +LY_ERR +lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum lyd_type data_type, struct ly_set *node_when, + uint32_t when_xp_opts, struct ly_set *node_types, struct ly_set *meta_types, struct ly_set *ext_node, + struct ly_set *ext_val, uint32_t val_opts, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + uint32_t i; + + if (ext_val && ext_val->count) { + /* first validate parsed extension data */ + i = ext_val->count; + do { + --i; + + struct lyd_ctx_ext_val *ext_v = ext_val->objs[i]; + + /* validate extension data */ + ret = ext_v->ext->def->plugin->validate(ext_v->ext, ext_v->sibling, *tree, data_type, val_opts, diff); + LY_CHECK_RET(ret); + + /* remove this item from the set */ + ly_set_rm_index(ext_val, i, free); + } while (i); + } + + if (ext_node && ext_node->count) { + /* validate data nodes with extension instances */ + i = ext_node->count; + do { + --i; + + struct lyd_ctx_ext_node *ext_n = ext_node->objs[i]; + + /* validate the node */ + ret = ext_n->ext->def->plugin->node(ext_n->ext, ext_n->node, val_opts); + LY_CHECK_RET(ret); + + /* remove this item from the set */ + ly_set_rm_index(ext_node, i, free); + } while (i); + } + + if (node_when) { + /* evaluate all when conditions */ + uint32_t prev_count; + + do { + prev_count = node_when->count; + LY_CHECK_RET(lyd_validate_unres_when(tree, mod, node_when, when_xp_opts, node_types, diff)); + /* there must have been some when conditions resolved */ + } while (prev_count > node_when->count); + + /* there could have been no cyclic when dependencies, checked during compilation */ + assert(!node_when->count); + } + + if (node_types && node_types->count) { + /* finish incompletely validated terminal values (traverse from the end for efficient set removal) */ + i = node_types->count; + do { + --i; + + struct lyd_node_term *node = node_types->objs[i]; + struct lysc_type *type = ((struct lysc_node_leaf *)node->schema)->type; + + /* resolve the value of the node */ + LOG_LOCSET(NULL, &node->node, NULL, NULL); + ret = lyd_value_validate_incomplete(LYD_CTX(node), type, &node->value, &node->node, *tree); + LOG_LOCBACK(0, 1, 0, 0); + LY_CHECK_RET(ret); + + /* remove this node from the set */ + ly_set_rm_index(node_types, i, NULL); + } while (i); + } + + if (meta_types && meta_types->count) { + /* ... and metadata values */ + i = meta_types->count; + do { + --i; + + struct lyd_meta *meta = meta_types->objs[i]; + struct lysc_type *type; + + /* validate and store the value of the metadata */ + lyplg_ext_get_storage(meta->annotation, LY_STMT_TYPE, sizeof type, (const void **)&type); + ret = lyd_value_validate_incomplete(LYD_CTX(meta->parent), type, &meta->value, meta->parent, *tree); + LY_CHECK_RET(ret); + + /* remove this attr from the set */ + ly_set_rm_index(meta_types, i, NULL); + } while (i); + } + + return ret; +} + +/** + * @brief Validate instance duplication. + * + * @param[in] first First sibling to search in. + * @param[in] node Data node instance to check. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *node) +{ + struct lyd_node **match_p; + ly_bool fail = 0; + + assert(node->flags & LYD_NEW); + + /* key-less list or non-configuration leaf-list */ + if (lysc_is_dup_inst_list(node->schema)) { + /* duplicate instances allowed */ + return LY_SUCCESS; + } + + /* find exactly the same next instance using hashes if possible */ + if (node->parent && node->parent->children_ht) { + if (!lyht_find_next(node->parent->children_ht, &node, node->hash, (void **)&match_p)) { + fail = 1; + } + } else { + for ( ; first; first = first->next) { + if (first == node) { + continue; + } + + if (node->schema->nodetype & (LYD_NODE_ANY | LYS_LEAF)) { + if (first->schema == node->schema) { + fail = 1; + break; + } + } else if (!lyd_compare_single(first, node, 0)) { + fail = 1; + break; + } + } + } + + if (fail) { + LOGVAL(node->schema->module->ctx, LY_VCODE_DUP, node->schema->name); + return LY_EVALID; + } + return LY_SUCCESS; +} + +/** + * @brief Validate multiple case data existence with possible autodelete. + * + * @param[in,out] first First sibling to search in, is updated if needed. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in] choic Choice node whose cases to check. + * @param[in,out] diff Validation diff. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_cases(struct lyd_node **first, const struct lys_module *mod, const struct lysc_node_choice *choic, + struct lyd_node **diff) +{ + const struct lysc_node *scase, *iter, *old_case = NULL, *new_case = NULL; + struct lyd_node *match, *to_del; + ly_bool found; + + LOG_LOCSET(&choic->node, NULL, NULL, NULL); + + LY_LIST_FOR((struct lysc_node *)choic->cases, scase) { + found = 0; + iter = NULL; + match = NULL; + while ((match = lys_getnext_data(match, *first, &iter, scase, NULL))) { + if (match->flags & LYD_NEW) { + /* a new case data found, nothing more to look for */ + found = 2; + break; + } else { + /* and old case data found */ + if (found == 0) { + found = 1; + } + } + } + + if (found == 1) { + /* there should not be 2 old cases */ + if (old_case) { + /* old data from 2 cases */ + LOGVAL(choic->module->ctx, LY_VCODE_DUPCASE, old_case->name, scase->name); + LOG_LOCBACK(1, 0, 0, 0); + return LY_EVALID; + } + + /* remember an old existing case */ + old_case = scase; + } else if (found == 2) { + if (new_case) { + /* new data from 2 cases */ + LOGVAL(choic->module->ctx, LY_VCODE_DUPCASE, new_case->name, scase->name); + LOG_LOCBACK(1, 0, 0, 0); + return LY_EVALID; + } + + /* remember a new existing case */ + new_case = scase; + } + } + + LOG_LOCBACK(1, 0, 0, 0); + + if (old_case && new_case) { + /* auto-delete old case */ + iter = NULL; + match = NULL; + to_del = NULL; + while ((match = lys_getnext_data(match, *first, &iter, old_case, NULL))) { + lyd_del_move_root(first, to_del, mod); + + /* free previous node */ + lyd_free_tree(to_del); + if (diff) { + /* add into diff */ + LY_CHECK_RET(lyd_val_diff_add(match, LYD_DIFF_OP_DELETE, diff)); + } + to_del = match; + } + lyd_del_move_root(first, to_del, mod); + lyd_free_tree(to_del); + } + + return LY_SUCCESS; +} + +/** + * @brief Check whether a schema node can have some default values (true for NP containers as well). + * + * @param[in] schema Schema node to check. + * @return non-zero if yes, + * @return 0 otherwise. + */ +static int +lyd_val_has_default(const struct lysc_node *schema) +{ + switch (schema->nodetype) { + case LYS_LEAF: + if (((struct lysc_node_leaf *)schema)->dflt) { + return 1; + } + break; + case LYS_LEAFLIST: + if (((struct lysc_node_leaflist *)schema)->dflts) { + return 1; + } + break; + case LYS_CONTAINER: + if (!(schema->flags & LYS_PRESENCE)) { + return 1; + } + break; + default: + break; + } + + return 0; +} + +/** + * @brief Properly delete a node as part of auto-delete validation tasks. + * + * @param[in,out] first First sibling, is updated if needed. + * @param[in] del Node instance to delete. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in,out] node Current iteration node, update it if it is deleted. + * @param[in,out] diff Validation diff. + * @return 1 if @p node auto-deleted and updated to its next sibling. + * @return 0 if @p node was not auto-deleted. + */ +static ly_bool +lyd_validate_autodel_node_del(struct lyd_node **first, struct lyd_node *del, const struct lys_module *mod, + struct lyd_node **node, struct lyd_node **diff) +{ + struct lyd_node *iter; + ly_bool node_autodel = 0; + + lyd_del_move_root(first, del, mod); + if (del == *node) { + *node = (*node)->next; + node_autodel = 1; + } + if (diff) { + /* add into diff */ + if ((del->schema->nodetype == LYS_CONTAINER) && !(del->schema->flags & LYS_PRESENCE)) { + /* we do not want to track NP container changes, but remember any removed children */ + LY_LIST_FOR(lyd_child(del), iter) { + lyd_val_diff_add(iter, LYD_DIFF_OP_DELETE, diff); + } + } else { + lyd_val_diff_add(del, LYD_DIFF_OP_DELETE, diff); + } + } + lyd_free_tree(del); + + return node_autodel; +} + +/** + * @brief Auto-delete leaf-list default instances to prevent validation errors. + * + * @param[in,out] first First sibling to search in, is updated if needed. + * @param[in,out] node New data node instance to check, is updated if auto-deleted. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in,out] diff Validation diff. + * @return 1 if @p node auto-deleted and updated to its next sibling. + * @return 0 if @p node was not auto-deleted. + */ +static ly_bool +lyd_validate_autodel_leaflist_dflt(struct lyd_node **first, struct lyd_node **node, const struct lys_module *mod, + struct lyd_node **diff) +{ + const struct lysc_node *schema; + struct lyd_node *iter, *next; + ly_bool found = 0, node_autodel = 0; + + assert((*node)->flags & LYD_NEW); + + schema = (*node)->schema; + assert(schema->nodetype == LYS_LEAFLIST); + + /* check whether there is any explicit instance */ + LYD_LIST_FOR_INST(*first, schema, iter) { + if (!(iter->flags & LYD_DEFAULT)) { + found = 1; + break; + } + } + if (!found) { + /* no explicit instance, keep defaults as they are */ + return 0; + } + + LYD_LIST_FOR_INST_SAFE(*first, schema, next, iter) { + if (iter->flags & LYD_DEFAULT) { + /* default instance found, remove it */ + if (lyd_validate_autodel_node_del(first, iter, mod, node, diff)) { + node_autodel = 1; + } + } + } + + return node_autodel; +} + +/** + * @brief Auto-delete container or leaf default instances to prevent validation errors. + * + * @param[in,out] first First sibling to search in, is updated if needed. + * @param[in,out] node New data node instance to check, is updated if auto-deleted. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in,out] diff Validation diff. + * @return 1 if @p node auto-deleted and updated to its next sibling. + * @return 0 if @p node was not auto-deleted. + */ +static ly_bool +lyd_validate_autodel_cont_leaf_dflt(struct lyd_node **first, struct lyd_node **node, const struct lys_module *mod, + struct lyd_node **diff) +{ + const struct lysc_node *schema; + struct lyd_node *iter, *next; + ly_bool found = 0, node_autodel = 0; + + assert((*node)->flags & LYD_NEW); + + schema = (*node)->schema; + assert(schema->nodetype & (LYS_LEAF | LYS_CONTAINER)); + + /* check whether there is any explicit instance */ + LYD_LIST_FOR_INST(*first, schema, iter) { + if (!(iter->flags & LYD_DEFAULT)) { + found = 1; + break; + } + } + + if (found) { + /* remove all default instances */ + LYD_LIST_FOR_INST_SAFE(*first, schema, next, iter) { + if (iter->flags & LYD_DEFAULT) { + /* default instance, remove it */ + if (lyd_validate_autodel_node_del(first, iter, mod, node, diff)) { + node_autodel = 1; + } + } + } + } else { + /* remove a single old default instance, if any */ + LYD_LIST_FOR_INST(*first, schema, iter) { + if ((iter->flags & LYD_DEFAULT) && !(iter->flags & LYD_NEW)) { + /* old default instance, remove it */ + if (lyd_validate_autodel_node_del(first, iter, mod, node, diff)) { + node_autodel = 1; + } + break; + } + } + } + + return node_autodel; +} + +/** + * @brief Auto-delete leftover default nodes of deleted cases (that have no existing explicit data). + * + * @param[in,out] first First sibling to search in, is updated if needed. + * @param[in,out] node Default data node instance to check. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in,out] diff Validation diff. + * @return 1 if @p node auto-deleted and updated to its next sibling. + * @return 0 if @p node was not auto-deleted. + */ +static ly_bool +lyd_validate_autodel_case_dflt(struct lyd_node **first, struct lyd_node **node, const struct lys_module *mod, + struct lyd_node **diff) +{ + const struct lysc_node *schema; + struct lysc_node_choice *choic; + struct lyd_node *iter = NULL; + const struct lysc_node *slast = NULL; + ly_bool node_autodel = 0; + + assert((*node)->flags & LYD_DEFAULT); + + schema = (*node)->schema; + + if (!schema->parent || (schema->parent->nodetype != LYS_CASE)) { + /* the default node is not a descendant of a case */ + return 0; + } + + choic = (struct lysc_node_choice *)schema->parent->parent; + assert(choic->nodetype == LYS_CHOICE); + + if (choic->dflt && (choic->dflt == (struct lysc_node_case *)schema->parent)) { + /* data of a default case, keep them */ + return 0; + } + + /* try to find an explicit node of the case */ + while ((iter = lys_getnext_data(iter, *first, &slast, schema->parent, NULL))) { + if (!(iter->flags & LYD_DEFAULT)) { + break; + } + } + + if (!iter) { + /* there are only default nodes of the case meaning it does not exist and neither should any default nodes + * of the case, remove this one default node */ + if (lyd_validate_autodel_node_del(first, *node, mod, node, diff)) { + node_autodel = 1; + } + } + + return node_autodel; +} + +/** + * @brief Validate new siblings in choices, recursively for nested choices. + * + * @param[in,out] first First sibling. + * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in,out] diff Validation diff. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_choice_r(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod, + struct lyd_node **diff) +{ + const struct lysc_node *snode = NULL; + + while (*first && (snode = lys_getnext(snode, sparent, mod ? mod->compiled : NULL, LYS_GETNEXT_WITHCHOICE))) { + /* check case duplicites */ + if (snode->nodetype == LYS_CHOICE) { + LY_CHECK_RET(lyd_validate_cases(first, mod, (struct lysc_node_choice *)snode, diff)); + + /* check for nested choice */ + LY_CHECK_RET(lyd_validate_choice_r(first, snode, mod, diff)); + } + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod, + struct lyd_node **diff) +{ + LY_ERR r; + struct lyd_node *node; + const struct lysc_node *last_dflt_schema = NULL; + + assert(first && (sparent || mod)); + + /* validate choices */ + LY_CHECK_RET(lyd_validate_choice_r(first, sparent, mod, diff)); + + node = *first; + while (node) { + if (!node->schema || (mod && (lyd_owner_module(node) != mod))) { + /* opaque node or all top-level data from this module checked */ + break; + } + + if (!(node->flags & (LYD_NEW | LYD_DEFAULT))) { + /* check only new and default nodes */ + node = node->next; + continue; + } + + if (lyd_val_has_default(node->schema) && (node->schema != last_dflt_schema) && (node->flags & LYD_NEW)) { + /* remove old default(s) of the new node if an explicit instance exists */ + last_dflt_schema = node->schema; + if (node->schema->nodetype == LYS_LEAFLIST) { + if (lyd_validate_autodel_leaflist_dflt(first, &node, mod, diff)) { + continue; + } + } else { + if (lyd_validate_autodel_cont_leaf_dflt(first, &node, mod, diff)) { + continue; + } + } + } + + if (node->flags & LYD_NEW) { + /* then check new node instance duplicities */ + LOG_LOCSET(NULL, node, NULL, NULL); + r = lyd_validate_duplicates(*first, node); + LOG_LOCBACK(0, 1, 0, 0); + LY_CHECK_RET(r); + + /* this node is valid */ + node->flags &= ~LYD_NEW; + } + + if (node->flags & LYD_DEFAULT) { + /* remove leftover default nodes from a no-longer existing case */ + if (lyd_validate_autodel_case_dflt(first, &node, mod, diff)) { + continue; + } + } + + /* next iter */ + node = node->next; + } + + return LY_SUCCESS; +} + +/** + * @brief Evaluate any "when" conditions of a non-existent data node with existing parent. + * + * @param[in] first First data sibling of the non-existing node. + * @param[in] parent Data parent of the non-existing node. + * @param[in] snode Schema node of the non-existing node. + * @param[out] disabled First when that evaluated false, if any. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_dummy_when(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode, + const struct lysc_when **disabled) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *tree, *dummy = NULL; + uint32_t xp_opts; + + /* find root */ + if (parent) { + tree = (struct lyd_node *)parent; + while (tree->parent) { + tree = lyd_parent(tree); + } + tree = lyd_first_sibling(tree); + } else { + /* is the first sibling from the same module, but may not be the actual first */ + tree = lyd_first_sibling(first); + } + + /* create dummy opaque node */ + ret = lyd_new_opaq((struct lyd_node *)parent, snode->module->ctx, snode->name, NULL, NULL, snode->module->name, &dummy); + LY_CHECK_GOTO(ret, cleanup); + + /* connect it if needed */ + if (!parent) { + if (first) { + lyd_insert_sibling((struct lyd_node *)first, dummy, &tree); + } else { + assert(!tree); + tree = dummy; + } + } + + /* explicitly specified accesible tree */ + if (snode->flags & LYS_CONFIG_W) { + xp_opts = LYXP_ACCESS_TREE_CONFIG; + } else { + xp_opts = LYXP_ACCESS_TREE_ALL; + } + + /* evaluate all when */ + ret = lyd_validate_node_when(tree, dummy, snode, xp_opts, disabled); + if (ret == LY_EINCOMPLETE) { + /* all other when must be resolved by now */ + LOGINT(snode->module->ctx); + ret = LY_EINT; + goto cleanup; + } else if (ret) { + /* error */ + goto cleanup; + } + +cleanup: + lyd_free_tree(dummy); + return ret; +} + +/** + * @brief Validate mandatory node existence. + * + * @param[in] first First sibling to search in. + * @param[in] parent Data parent. + * @param[in] snode Schema node to validate. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode) +{ + const struct lysc_when *disabled; + + if (snode->nodetype == LYS_CHOICE) { + /* some data of a choice case exist */ + if (lys_getnext_data(NULL, first, NULL, snode, NULL)) { + return LY_SUCCESS; + } + } else { + assert(snode->nodetype & (LYS_LEAF | LYS_CONTAINER | LYD_NODE_ANY)); + + if (!lyd_find_sibling_val(first, snode, NULL, 0, NULL)) { + /* data instance found */ + return LY_SUCCESS; + } + } + + disabled = NULL; + if (lysc_has_when(snode)) { + /* if there are any when conditions, they must be true for a validation error */ + LY_CHECK_RET(lyd_validate_dummy_when(first, parent, snode, &disabled)); + } + + if (!disabled) { + /* node instance not found */ + if (snode->nodetype == LYS_CHOICE) { + LOGVAL_APPTAG(snode->module->ctx, "missing-choice", LY_VCODE_NOMAND_CHOIC, snode->name); + } else { + LOGVAL(snode->module->ctx, LY_VCODE_NOMAND, snode->name); + } + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Validate min/max-elements constraints, if any. + * + * @param[in] first First sibling to search in. + * @param[in] parent Data parent. + * @param[in] snode Schema node to validate. + * @param[in] min Minimum number of elements, 0 for no restriction. + * @param[in] max Max number of elements, 0 for no restriction. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_minmax(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode, + uint32_t min, uint32_t max) +{ + uint32_t count = 0; + struct lyd_node *iter; + const struct lysc_when *disabled; + ly_bool invalid_instance = 0; + + assert(min || max); + + LYD_LIST_FOR_INST(first, snode, iter) { + ++count; + + if (min && (count == min)) { + /* satisfied */ + min = 0; + if (!max) { + /* nothing more to check */ + break; + } + } + if (max && (count > max)) { + /* not satisifed */ + LOG_LOCSET(NULL, iter, NULL, NULL); + invalid_instance = 1; + break; + } + } + + if (min) { + assert(count < min); + + disabled = NULL; + if (lysc_has_when(snode)) { + /* if there are any when conditions, they must be true for a validation error */ + LY_CHECK_RET(lyd_validate_dummy_when(first, parent, snode, &disabled)); + } + + if (!disabled) { + LOGVAL_APPTAG(snode->module->ctx, "too-few-elements", LY_VCODE_NOMIN, snode->name); + goto failure; + } + } else if (max && (count > max)) { + LOGVAL_APPTAG(snode->module->ctx, "too-many-elements", LY_VCODE_NOMAX, snode->name); + goto failure; + } + + return LY_SUCCESS; + +failure: + LOG_LOCBACK(0, invalid_instance, 0, 0); + return LY_EVALID; +} + +/** + * @brief Find node referenced by a list unique statement. + * + * @param[in] uniq_leaf Unique leaf to find. + * @param[in] list List instance to use for the search. + * @return Found leaf, + * @return NULL if no leaf found. + */ +static struct lyd_node * +lyd_val_uniq_find_leaf(const struct lysc_node_leaf *uniq_leaf, const struct lyd_node *list) +{ + struct lyd_node *node; + const struct lysc_node *iter; + size_t depth = 0, i; + + /* get leaf depth */ + for (iter = &uniq_leaf->node; iter && (iter != list->schema); iter = lysc_data_parent(iter)) { + ++depth; + } + + node = (struct lyd_node *)list; + while (node && depth) { + /* find schema node with this depth */ + for (i = depth - 1, iter = &uniq_leaf->node; i; iter = lysc_data_parent(iter)) { + --i; + } + + /* find iter instance in children */ + assert(iter->nodetype & (LYS_CONTAINER | LYS_LEAF)); + lyd_find_sibling_val(lyd_child(node), iter, NULL, 0, &node); + --depth; + } + + return node; +} + +/** + * @brief Callback for comparing 2 list unique leaf values. + * + * Implementation of ::lyht_value_equal_cb. + * + * @param[in] cb_data 0 to compare all uniques, n to compare only n-th unique. + */ +static ly_bool +lyd_val_uniq_list_equal(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data) +{ + struct ly_ctx *ctx; + struct lysc_node_list *slist; + struct lyd_node *diter, *first, *second; + struct lyd_value *val1, *val2; + char *path1, *path2, *uniq_str, *ptr; + LY_ARRAY_COUNT_TYPE u, v, action; + + assert(val1_p && val2_p); + + first = *((struct lyd_node **)val1_p); + second = *((struct lyd_node **)val2_p); + action = (uintptr_t)cb_data; + + assert(first && (first->schema->nodetype == LYS_LIST)); + assert(second && (second->schema == first->schema)); + + ctx = first->schema->module->ctx; + + slist = (struct lysc_node_list *)first->schema; + + /* compare unique leaves */ + if (action > 0) { + u = action - 1; + if (u < LY_ARRAY_COUNT(slist->uniques)) { + goto uniquecheck; + } + } + LY_ARRAY_FOR(slist->uniques, u) { +uniquecheck: + LY_ARRAY_FOR(slist->uniques[u], v) { + /* first */ + diter = lyd_val_uniq_find_leaf(slist->uniques[u][v], first); + if (diter) { + val1 = &((struct lyd_node_term *)diter)->value; + } else { + /* use default value */ + val1 = slist->uniques[u][v]->dflt; + } + + /* second */ + diter = lyd_val_uniq_find_leaf(slist->uniques[u][v], second); + if (diter) { + val2 = &((struct lyd_node_term *)diter)->value; + } else { + /* use default value */ + val2 = slist->uniques[u][v]->dflt; + } + + if (!val1 || !val2 || val1->realtype->plugin->compare(val1, val2)) { + /* values differ or either one is not set */ + break; + } + } + if (v && (v == LY_ARRAY_COUNT(slist->uniques[u]))) { + /* all unique leafs are the same in this set, create this nice error */ + path1 = lyd_path(first, LYD_PATH_STD, NULL, 0); + path2 = lyd_path(second, LYD_PATH_STD, NULL, 0); + + /* use buffer to rebuild the unique string */ +#define UNIQ_BUF_SIZE 1024 + uniq_str = malloc(UNIQ_BUF_SIZE); + uniq_str[0] = '\0'; + ptr = uniq_str; + LY_ARRAY_FOR(slist->uniques[u], v) { + if (v) { + strcpy(ptr, " "); + ++ptr; + } + ptr = lysc_path_until((struct lysc_node *)slist->uniques[u][v], &slist->node, LYSC_PATH_LOG, + ptr, UNIQ_BUF_SIZE - (ptr - uniq_str)); + if (!ptr) { + /* path will be incomplete, whatever */ + break; + } + + ptr += strlen(ptr); + } + LOG_LOCSET(NULL, second, NULL, NULL); + LOGVAL_APPTAG(ctx, "data-not-unique", LY_VCODE_NOUNIQ, uniq_str, path1, path2); + LOG_LOCBACK(0, 1, 0, 0); + + free(path1); + free(path2); + free(uniq_str); +#undef UNIQ_BUF_SIZE + + return 1; + } + + if (action > 0) { + /* done */ + return 0; + } + } + + return 0; +} + +/** + * @brief Validate list unique leaves. + * + * @param[in] first First sibling to search in. + * @param[in] snode Schema node to validate. + * @param[in] uniques List unique arrays to validate. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, const struct lysc_node_leaf ***uniques) +{ + const struct lyd_node *diter; + struct ly_set *set; + LY_ARRAY_COUNT_TYPE u, v, x = 0; + LY_ERR ret = LY_SUCCESS; + uint32_t hash, i; + size_t key_len; + ly_bool dyn; + const void *hash_key; + void *cb_data; + struct hash_table **uniqtables = NULL; + struct lyd_value *val; + struct ly_ctx *ctx = snode->module->ctx; + + assert(uniques); + + /* get all list instances */ + LY_CHECK_RET(ly_set_new(&set)); + LY_LIST_FOR(first, diter) { + if (diter->schema == snode) { + ret = ly_set_add(set, (void *)diter, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + + if (set->count == 2) { + /* simple comparison */ + if (lyd_val_uniq_list_equal(&set->objs[0], &set->objs[1], 0, (void *)0)) { + /* instance duplication */ + ret = LY_EVALID; + goto cleanup; + } + } else if (set->count > 2) { + /* use hashes for comparison */ + uniqtables = malloc(LY_ARRAY_COUNT(uniques) * sizeof *uniqtables); + LY_CHECK_ERR_GOTO(!uniqtables, LOGMEM(ctx); ret = LY_EMEM, cleanup); + x = LY_ARRAY_COUNT(uniques); + for (v = 0; v < x; v++) { + cb_data = (void *)(uintptr_t)(v + 1L); + uniqtables[v] = lyht_new(lyht_get_fixed_size(set->count), sizeof(struct lyd_node *), + lyd_val_uniq_list_equal, cb_data, 0); + LY_CHECK_ERR_GOTO(!uniqtables[v], LOGMEM(ctx); ret = LY_EMEM, cleanup); + } + + for (i = 0; i < set->count; i++) { + /* loop for unique - get the hash for the instances */ + for (u = 0; u < x; u++) { + val = NULL; + for (v = hash = 0; v < LY_ARRAY_COUNT(uniques[u]); v++) { + diter = lyd_val_uniq_find_leaf(uniques[u][v], set->objs[i]); + if (diter) { + val = &((struct lyd_node_term *)diter)->value; + } else { + /* use default value */ + val = uniques[u][v]->dflt; + } + if (!val) { + /* unique item not present nor has default value */ + break; + } + + /* get hash key */ + hash_key = val->realtype->plugin->print(NULL, val, LY_VALUE_LYB, NULL, &dyn, &key_len); + hash = dict_hash_multi(hash, hash_key, key_len); + if (dyn) { + free((void *)hash_key); + } + } + if (!val) { + /* skip this list instance since its unique set is incomplete */ + continue; + } + + /* finish the hash value */ + hash = dict_hash_multi(hash, NULL, 0); + + /* insert into the hashtable */ + ret = lyht_insert(uniqtables[u], &set->objs[i], hash, NULL); + if (ret == LY_EEXIST) { + /* instance duplication */ + ret = LY_EVALID; + } + LY_CHECK_GOTO(ret != LY_SUCCESS, cleanup); + } + } + } + +cleanup: + ly_set_free(set, NULL); + for (v = 0; v < x; v++) { + if (!uniqtables[v]) { + /* failed when allocating uniquetables[j], following j are not allocated */ + break; + } + lyht_free(uniqtables[v]); + } + free(uniqtables); + + return ret; +} + +/** + * @brief Validate data siblings based on generic schema node restrictions, recursively for schema-only nodes. + * + * @param[in] first First sibling to search in. + * @param[in] parent Data parent. + * @param[in] sparent Schema parent of the nodes to check. + * @param[in] mod Module of the nodes to check. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[in] int_opts Internal parser options. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_siblings_schema_r(const struct lyd_node *first, const struct lyd_node *parent, + const struct lysc_node *sparent, const struct lysc_module *mod, uint32_t val_opts, uint32_t int_opts) +{ + LY_ERR ret = LY_SUCCESS; + const struct lysc_node *snode = NULL, *scase; + struct lysc_node_list *slist; + struct lysc_node_leaflist *sllist; + uint32_t getnext_opts; + + getnext_opts = LYS_GETNEXT_WITHCHOICE | (int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0); + + /* disabled nodes are skipped by lys_getnext */ + while ((snode = lys_getnext(snode, sparent, mod, getnext_opts))) { + if ((val_opts & LYD_VALIDATE_NO_STATE) && (snode->flags & LYS_CONFIG_R)) { + continue; + } + + LOG_LOCSET(snode, NULL, NULL, NULL); + + /* check min-elements and max-elements */ + if (snode->nodetype == LYS_LIST) { + slist = (struct lysc_node_list *)snode; + if (slist->min || slist->max) { + ret = lyd_validate_minmax(first, parent, snode, slist->min, slist->max); + LY_CHECK_GOTO(ret, error); + } + } else if (snode->nodetype == LYS_LEAFLIST) { + sllist = (struct lysc_node_leaflist *)snode; + if (sllist->min || sllist->max) { + ret = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max); + LY_CHECK_GOTO(ret, error); + } + + } else if (snode->flags & LYS_MAND_TRUE) { + /* check generic mandatory existence */ + ret = lyd_validate_mandatory(first, parent, snode); + LY_CHECK_GOTO(ret, error); + } + + /* check unique */ + if (snode->nodetype == LYS_LIST) { + slist = (struct lysc_node_list *)snode; + if (slist->uniques) { + ret = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques); + LY_CHECK_GOTO(ret, error); + } + } + + if (snode->nodetype == LYS_CHOICE) { + /* find the existing case, if any */ + LY_LIST_FOR(lysc_node_child(snode), scase) { + if (lys_getnext_data(NULL, first, NULL, scase, NULL)) { + /* validate only this case */ + ret = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts); + LY_CHECK_GOTO(ret, error); + break; + } + } + } + + LOG_LOCBACK(1, 0, 0, 0); + } + + return LY_SUCCESS; + +error: + LOG_LOCBACK(1, 0, 0, 0); + return ret; +} + +/** + * @brief Validate obsolete nodes, only warnings are printed. + * + * @param[in] node Node to check. + */ +static void +lyd_validate_obsolete(const struct lyd_node *node) +{ + const struct lysc_node *snode; + + snode = node->schema; + do { + if (snode->flags & LYS_STATUS_OBSLT) { + LOGWRN(snode->module->ctx, "Obsolete schema node \"%s\" instantiated in data.", snode->name); + break; + } + + snode = snode->parent; + } while (snode && (snode->nodetype & (LYS_CHOICE | LYS_CASE))); +} + +/** + * @brief Validate must conditions of a data node. + * + * @param[in] node Node to validate. + * @param[in] int_opts Internal parser options. + * @param[in] xpath_options Additional XPath options to use. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_must(const struct lyd_node *node, uint32_t int_opts, uint32_t xpath_options) +{ + LY_ERR ret; + struct lyxp_set xp_set; + struct lysc_must *musts; + const struct lyd_node *tree; + const struct lysc_node *schema; + const char *emsg, *eapptag; + LY_ARRAY_COUNT_TYPE u; + + assert((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) != (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)); + assert((int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) != (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)); + + if (node->schema->nodetype & (LYS_ACTION | LYS_RPC)) { + if (int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION)) { + schema = &((struct lysc_node_action *)node->schema)->input.node; + } else if (int_opts & LYD_INTOPT_REPLY) { + schema = &((struct lysc_node_action *)node->schema)->output.node; + } else { + LOGINT_RET(LYD_CTX(node)); + } + } else { + schema = node->schema; + } + musts = lysc_node_musts(schema); + if (!musts) { + /* no must to evaluate */ + return LY_SUCCESS; + } + + /* find first top-level node */ + for (tree = node; tree->parent; tree = lyd_parent(tree)) {} + tree = lyd_first_sibling(tree); + + LY_ARRAY_FOR(musts, u) { + memset(&xp_set, 0, sizeof xp_set); + + /* evaluate must */ + ret = lyxp_eval(LYD_CTX(node), musts[u].cond, node->schema->module, LY_VALUE_SCHEMA_RESOLVED, + musts[u].prefixes, node, node, tree, NULL, &xp_set, LYXP_SCHEMA | xpath_options); + if (ret == LY_EINCOMPLETE) { + LOGINT_RET(LYD_CTX(node)); + } else if (ret) { + return ret; + } + + /* check the result */ + lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN); + if (!xp_set.val.bln) { + /* use specific error information */ + emsg = musts[u].emsg; + eapptag = musts[u].eapptag ? musts[u].eapptag : "must-violation"; + if (emsg) { + LOGVAL_APPTAG(LYD_CTX(node), eapptag, LYVE_DATA, "%s", emsg); + } else { + LOGVAL_APPTAG(LYD_CTX(node), eapptag, LY_VCODE_NOMUST, musts[u].cond->expr); + } + return LY_EVALID; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Perform all remaining validation tasks, the data tree must be final when calling this function. + * + * @param[in] first First sibling. + * @param[in] parent Data parent. + * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in] val_opts Validation options (@ref datavalidationoptions). + * @param[in] int_opts Internal parser options. + * @param[in] must_xp_opts Additional XPath options to use for evaluating "must". + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_final_r(struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *sparent, + const struct lys_module *mod, uint32_t val_opts, uint32_t int_opts, uint32_t must_xp_opts) +{ + LY_ERR r; + const char *innode; + struct lyd_node *next = NULL, *node; + + /* validate all restrictions of nodes themselves */ + LY_LIST_FOR_SAFE(first, next, node) { + if (node->flags & LYD_EXT) { + /* ext instance data should have already been validated */ + continue; + } + + LOG_LOCSET(node->schema, node, NULL, NULL); + + /* opaque data */ + if (!node->schema) { + r = lyd_parse_opaq_error(node); + LOG_LOCBACK(0, 1, 0, 0); + return r; + } + + if (!node->parent && mod && (lyd_owner_module(node) != mod)) { + /* all top-level data from this module checked */ + LOG_LOCBACK(1, 1, 0, 0); + break; + } + + /* no state/input/output/op data */ + innode = NULL; + if ((val_opts & LYD_VALIDATE_NO_STATE) && (node->schema->flags & LYS_CONFIG_R)) { + innode = "state"; + } else if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION)) && (node->schema->flags & LYS_IS_OUTPUT)) { + innode = "output"; + } else if ((int_opts & LYD_INTOPT_REPLY) && (node->schema->flags & LYS_IS_INPUT)) { + innode = "input"; + } else if (!(int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_REPLY)) && (node->schema->nodetype == LYS_RPC)) { + innode = "rpc"; + } else if (!(int_opts & (LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) && (node->schema->nodetype == LYS_ACTION)) { + innode = "action"; + } else if (!(int_opts & LYD_INTOPT_NOTIF) && (node->schema->nodetype == LYS_NOTIF)) { + innode = "notification"; + } + if (innode) { + LOGVAL(LYD_CTX(node), LY_VCODE_UNEXPNODE, innode, node->schema->name); + LOG_LOCBACK(1, 1, 0, 0); + return LY_EVALID; + } + + /* obsolete data */ + lyd_validate_obsolete(node); + + /* node's musts */ + if ((r = lyd_validate_must(node, int_opts, must_xp_opts))) { + LOG_LOCBACK(1, 1, 0, 0); + return r; + } + + /* node value was checked by plugins */ + + /* next iter */ + LOG_LOCBACK(1, 1, 0, 0); + } + + /* validate schema-based restrictions */ + LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts)); + + LY_LIST_FOR(first, node) { + if (!node->parent && mod && (lyd_owner_module(node) != mod)) { + /* all top-level data from this module checked */ + break; + } + + /* validate all children recursively */ + LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts, must_xp_opts)); + + /* set default for containers */ + lyd_cont_set_dflt(node); + } + + return LY_SUCCESS; +} + +/** + * @brief Validate extension instance data by storing it in its unres set. + * + * @param[in] sibling First sibling with ::LYD_EXT flag, all the following ones are expected to have it, too. + * @param[in,out] ext_val Set with parsed extension instance data to validate. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_nested_ext(struct lyd_node *sibling, struct ly_set *ext_val) +{ + struct lyd_node *node; + struct lyd_ctx_ext_val *ext_v; + struct lysc_ext_instance *nested_exts, *ext = NULL; + LY_ARRAY_COUNT_TYPE u; + + /* check of basic assumptions */ + if (!sibling->parent || !sibling->parent->schema) { + LOGINT_RET(LYD_CTX(sibling)); + } + LY_LIST_FOR(sibling, node) { + if (!(node->flags & LYD_EXT)) { + LOGINT_RET(LYD_CTX(sibling)); + } + } + + /* try to find the extension instance */ + nested_exts = sibling->parent->schema->exts; + LY_ARRAY_FOR(nested_exts, u) { + if (nested_exts[u].def->plugin->validate) { + if (ext) { + /* more extension instances with validate callback */ + LOGINT_RET(LYD_CTX(sibling)); + } + ext = &nested_exts[u]; + } + } + if (!ext) { + /* no extension instance with validate callback */ + LOGINT_RET(LYD_CTX(sibling)); + } + + /* store for validation */ + ext_v = malloc(sizeof *ext_v); + LY_CHECK_ERR_RET(!ext_v, LOGMEM(LYD_CTX(sibling)), LY_EMEM); + ext_v->ext = ext; + ext_v->sibling = sibling; + LY_CHECK_RET(ly_set_add(ext_val, ext_v, 1, NULL)); + + return LY_SUCCESS; +} + +LY_ERR +lyd_validate_node_ext(struct lyd_node *node, struct ly_set *ext_node) +{ + struct lyd_ctx_ext_node *ext_n; + struct lysc_ext_instance *exts; + LY_ARRAY_COUNT_TYPE u; + + /* try to find a relevant extension instance with node callback */ + exts = node->schema->exts; + LY_ARRAY_FOR(exts, u) { + if (exts[u].def->plugin && exts[u].def->plugin->node) { + /* store for validation */ + ext_n = malloc(sizeof *ext_n); + LY_CHECK_ERR_RET(!ext_n, LOGMEM(LYD_CTX(node)), LY_EMEM); + ext_n->ext = &exts[u]; + ext_n->node = node; + LY_CHECK_RET(ly_set_add(ext_node, ext_n, 1, NULL)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Validate the whole data subtree. + * + * @param[in] root Subtree root. + * @param[in,out] node_when Set for nodes with when conditions. + * @param[in,out] node_types Set for unres node types. + * @param[in,out] meta_types Set for unres metadata types. + * @param[in,out] ext_node Set with nodes with extensions to validate. + * @param[in,out] ext_val Set for parsed extension data to validate. + * @param[in] impl_opts Implicit options, see @ref implicitoptions. + * @param[in,out] diff Validation diff. + * @return LY_ERR value. + */ +static LY_ERR +lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_when, struct ly_set *node_types, + struct ly_set *meta_types, struct ly_set *ext_node, struct ly_set *ext_val, uint32_t impl_opts, + struct lyd_node **diff) +{ + const struct lyd_meta *meta; + const struct lysc_type *type; + struct lyd_node *node; + + LYD_TREE_DFS_BEGIN(root, node) { + if (node->flags & LYD_EXT) { + /* validate using the extension instance callback */ + return lyd_validate_nested_ext(node, ext_val); + } + + if (!node->schema) { + /* do not validate opaque nodes */ + goto next_node; + } + + LY_LIST_FOR(node->meta, meta) { + lyplg_ext_get_storage(meta->annotation, LY_STMT_TYPE, sizeof type, (const void **)&type); + if (type->plugin->validate) { + /* metadata type resolution */ + LY_CHECK_RET(ly_set_add(meta_types, (void *)meta, 1, NULL)); + } + } + + if ((node->schema->nodetype & LYD_NODE_TERM) && ((struct lysc_node_leaf *)node->schema)->type->plugin->validate) { + /* node type resolution */ + LY_CHECK_RET(ly_set_add(node_types, (void *)node, 1, NULL)); + } else if (node->schema->nodetype & LYD_NODE_INNER) { + /* new node validation, autodelete */ + LY_CHECK_RET(lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, diff)); + + /* add nested defaults */ + LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, NULL, impl_opts, diff)); + } + + if (lysc_has_when(node->schema)) { + /* when evaluation */ + LY_CHECK_RET(ly_set_add(node_when, (void *)node, 1, NULL)); + } + + /* store for ext instance node validation, if needed */ + LY_CHECK_RET(lyd_validate_node_ext(node, ext_node)); + +next_node: + LYD_TREE_DFS_END(root, node); + } + + return LY_SUCCESS; +} + +LY_ERR +lyd_validate(struct lyd_node **tree, const struct lys_module *module, const struct ly_ctx *ctx, uint32_t val_opts, + ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p, + struct ly_set *ext_node_p, struct ly_set *ext_val_p, struct lyd_node **diff) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *first, *next, **first2, *iter; + const struct lys_module *mod; + struct ly_set node_types = {0}, meta_types = {0}, node_when = {0}, ext_node = {0}, ext_val = {0}; + uint32_t i = 0; + + assert(tree && ctx); + assert((node_when_p && node_types_p && meta_types_p && ext_node_p && ext_val_p) || + (!node_when_p && !node_types_p && !meta_types_p && !ext_node_p && !ext_val_p)); + + if (!node_when_p) { + node_when_p = &node_when; + node_types_p = &node_types; + meta_types_p = &meta_types; + ext_node_p = &ext_node; + ext_val_p = &ext_val; + } + + next = *tree; + while (1) { + if (val_opts & LYD_VALIDATE_PRESENT) { + mod = lyd_data_next_module(&next, &first); + } else { + mod = lyd_mod_next_module(next, module, ctx, &i, &first); + } + if (!mod) { + break; + } + if (!first || (first == *tree)) { + /* make sure first2 changes are carried to tree */ + first2 = tree; + } else { + first2 = &first; + } + + /* validate new top-level nodes of this module, autodelete */ + ret = lyd_validate_new(first2, NULL, mod, diff); + LY_CHECK_GOTO(ret, cleanup); + + /* add all top-level defaults for this module, if going to validate subtree, do not add into unres sets + * (lyd_validate_subtree() adds all the nodes in that case) */ + ret = lyd_new_implicit_r(NULL, first2, NULL, mod, validate_subtree ? NULL : node_when_p, + validate_subtree ? NULL : node_types_p, validate_subtree ? NULL : ext_node_p, + (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff); + LY_CHECK_GOTO(ret, cleanup); + + /* our first module node pointer may no longer be the first */ + first = *first2; + lyd_first_module_sibling(&first, mod); + if (!first || (first == *tree)) { + first2 = tree; + } else { + first2 = &first; + } + + if (validate_subtree) { + /* process nested nodes */ + LY_LIST_FOR(*first2, iter) { + if (lyd_owner_module(iter) != mod) { + break; + } + + ret = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p, + (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff); + LY_CHECK_GOTO(ret, cleanup); + } + } + + /* finish incompletely validated terminal values/attributes and when conditions */ + ret = lyd_validate_unres(first2, mod, LYD_TYPE_DATA_YANG, node_when_p, 0, node_types_p, meta_types_p, + ext_node_p, ext_val_p, val_opts, diff); + LY_CHECK_GOTO(ret, cleanup); + + /* perform final validation that assumes the data tree is final */ + ret = lyd_validate_final_r(*first2, NULL, NULL, mod, val_opts, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + ly_set_erase(&node_when, NULL); + ly_set_erase(&node_types, NULL); + ly_set_erase(&meta_types, NULL); + ly_set_erase(&ext_node, free); + ly_set_erase(&ext_val, free); + return ret; +} + +LIBYANG_API_DEF LY_ERR +lyd_validate_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t val_opts, struct lyd_node **diff) +{ + LY_CHECK_ARG_RET(NULL, tree, *tree || ctx, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(*tree ? LYD_CTX(*tree) : NULL, ctx, LY_EINVAL); + if (!ctx) { + ctx = LYD_CTX(*tree); + } + if (diff) { + *diff = NULL; + } + + return lyd_validate(tree, NULL, ctx, val_opts, 1, NULL, NULL, NULL, NULL, NULL, diff); +} + +LIBYANG_API_DEF LY_ERR +lyd_validate_module(struct lyd_node **tree, const struct lys_module *module, uint32_t val_opts, struct lyd_node **diff) +{ + LY_CHECK_ARG_RET(NULL, tree, *tree || module, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(*tree ? LYD_CTX(*tree) : NULL, module ? module->ctx : NULL, LY_EINVAL); + if (diff) { + *diff = NULL; + } + + return lyd_validate(tree, module, (*tree) ? LYD_CTX(*tree) : module->ctx, val_opts, 1, NULL, NULL, NULL, NULL, NULL, + diff); +} + +/** + * @brief Find nodes for merging an operation into data tree for validation. + * + * @param[in] op_tree Full operation data tree. + * @param[in] op_node Operation node itself. + * @param[in] tree Data tree to be merged into. + * @param[out] op_subtree Operation subtree to merge. + * @param[out] tree_sibling Data tree sibling to merge next to, is set if @p tree_parent is NULL. + * @param[out] tree_parent Data tree parent to merge into, is set if @p tree_sibling is NULL. + */ +static void +lyd_val_op_merge_find(const struct lyd_node *op_tree, const struct lyd_node *op_node, const struct lyd_node *tree, + struct lyd_node **op_subtree, struct lyd_node **tree_sibling, struct lyd_node **tree_parent) +{ + const struct lyd_node *tree_iter, *op_iter; + struct lyd_node *match = NULL; + uint32_t i, cur_depth, op_depth; + + *op_subtree = NULL; + *tree_sibling = NULL; + *tree_parent = NULL; + + /* learn op depth (top-level being depth 0) */ + op_depth = 0; + for (op_iter = op_node; op_iter != op_tree; op_iter = lyd_parent(op_iter)) { + ++op_depth; + } + + /* find where to merge op */ + tree_iter = tree; + cur_depth = op_depth; + while (cur_depth && tree_iter) { + /* find op iter in tree */ + lyd_find_sibling_first(tree_iter, op_iter, &match); + if (!match) { + break; + } + + /* move tree_iter */ + tree_iter = lyd_child(match); + + /* move depth */ + --cur_depth; + + /* find next op parent */ + op_iter = op_node; + for (i = 0; i < cur_depth; ++i) { + op_iter = lyd_parent(op_iter); + } + } + + assert(op_iter); + *op_subtree = (struct lyd_node *)op_iter; + if (!tree || tree_iter) { + /* there is no tree whatsoever or this is the last found sibling */ + *tree_sibling = (struct lyd_node *)tree_iter; + } else { + /* matching parent was found but it has no children to insert next to */ + assert(match); + *tree_parent = match; + } +} + +/** + * @brief Validate an RPC/action request, reply, or notification. + * + * @param[in] op_tree Full operation data tree. + * @param[in] op_node Operation node itself. + * @param[in] dep_tree Tree to be used for validating references from the operation subtree. + * @param[in] int_opts Internal parser options. + * @param[in] data_type Type of validated data. + * @param[in] validate_subtree Whether subtree was already validated (as part of data parsing) or not (separate validation). + * @param[in] node_when_p Set of nodes with when conditions, if NULL a local set is used. + * @param[in] node_types_p Set of unres node types, if NULL a local set is used. + * @param[in] meta_types_p Set of unres metadata types, if NULL a local set is used. + * @param[in] ext_node_p Set of unres nodes with extensions to validate, if NULL a local set is used. + * @param[in] ext_val_p Set of parsed extension data to validate, if NULL a local set is used. + * @param[out] diff Optional diff with any changes made by the validation. + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +static LY_ERR +_lyd_validate_op(struct lyd_node *op_tree, struct lyd_node *op_node, const struct lyd_node *dep_tree, enum lyd_type data_type, + uint32_t int_opts, ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, + struct ly_set *meta_types_p, struct ly_set *ext_node_p, struct ly_set *ext_val_p, struct lyd_node **diff) +{ + LY_ERR rc = LY_SUCCESS; + struct lyd_node *tree_sibling, *tree_parent, *op_subtree, *op_parent, *op_sibling_before, *op_sibling_after, *child; + struct ly_set node_types = {0}, meta_types = {0}, node_when = {0}, ext_node = {0}, ext_val = {0}; + + assert(op_tree && op_node); + assert((node_when_p && node_types_p && meta_types_p && ext_node_p && ext_val_p) || + (!node_when_p && !node_types_p && !meta_types_p && !ext_node_p && !ext_val_p)); + + if (!node_when_p) { + node_when_p = &node_when; + node_types_p = &node_types; + meta_types_p = &meta_types; + ext_node_p = &ext_node; + ext_val_p = &ext_val; + } + + /* merge op_tree into dep_tree */ + lyd_val_op_merge_find(op_tree, op_node, dep_tree, &op_subtree, &tree_sibling, &tree_parent); + op_sibling_before = op_subtree->prev->next ? op_subtree->prev : NULL; + op_sibling_after = op_subtree->next; + op_parent = lyd_parent(op_subtree); + + lyd_unlink_tree(op_subtree); + lyd_insert_node(tree_parent, &tree_sibling, op_subtree, 0); + if (!dep_tree) { + dep_tree = tree_sibling; + } + + LOG_LOCSET(NULL, op_node, NULL, NULL); + + if (int_opts & LYD_INTOPT_REPLY) { + /* add output children defaults */ + rc = lyd_new_implicit_r(op_node, lyd_node_child_p(op_node), NULL, NULL, node_when_p, node_types_p, + ext_node_p, LYD_IMPLICIT_OUTPUT, diff); + LY_CHECK_GOTO(rc, cleanup); + + if (validate_subtree) { + /* skip validating the operation itself, go to children directly */ + LY_LIST_FOR(lyd_child(op_node), child) { + rc = lyd_validate_subtree(child, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p, 0, diff); + LY_CHECK_GOTO(rc, cleanup); + } + } + } else { + if (validate_subtree) { + /* prevalidate whole operation subtree */ + rc = lyd_validate_subtree(op_node, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p, 0, diff); + LY_CHECK_GOTO(rc, cleanup); + } + } + + /* finish incompletely validated terminal values/attributes and when conditions on the full tree, + * account for unresolved 'when' that may appear in the non-validated dependency data tree */ + LY_CHECK_GOTO(rc = lyd_validate_unres((struct lyd_node **)&dep_tree, NULL, data_type, node_when_p, LYXP_IGNORE_WHEN, + node_types_p, meta_types_p, ext_node_p, ext_val_p, 0, diff), cleanup); + + /* perform final validation of the operation/notification */ + lyd_validate_obsolete(op_node); + LY_CHECK_GOTO(rc = lyd_validate_must(op_node, int_opts, LYXP_IGNORE_WHEN), cleanup); + + /* final validation of all the descendants */ + rc = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, int_opts, LYXP_IGNORE_WHEN); + LY_CHECK_GOTO(rc, cleanup); + +cleanup: + LOG_LOCBACK(0, 1, 0, 0); + + /* restore operation tree */ + lyd_unlink_tree(op_subtree); + if (op_sibling_before) { + lyd_insert_after_node(op_sibling_before, op_subtree); + } else if (op_sibling_after) { + lyd_insert_before_node(op_sibling_after, op_subtree); + } else if (op_parent) { + lyd_insert_node(op_parent, NULL, op_subtree, 0); + } + + ly_set_erase(&node_when, NULL); + ly_set_erase(&node_types, NULL); + ly_set_erase(&meta_types, NULL); + ly_set_erase(&ext_node, free); + ly_set_erase(&ext_val, free); + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_validate_op(struct lyd_node *op_tree, const struct lyd_node *dep_tree, enum lyd_type data_type, struct lyd_node **diff) +{ + struct lyd_node *op_node; + uint32_t int_opts; + struct ly_set ext_val = {0}; + LY_ERR rc; + + LY_CHECK_ARG_RET(NULL, op_tree, !dep_tree || !dep_tree->parent, (data_type == LYD_TYPE_RPC_YANG) || + (data_type == LYD_TYPE_NOTIF_YANG) || (data_type == LYD_TYPE_REPLY_YANG), LY_EINVAL); + if (diff) { + *diff = NULL; + } + if (data_type == LYD_TYPE_RPC_YANG) { + int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION; + } else if (data_type == LYD_TYPE_NOTIF_YANG) { + int_opts = LYD_INTOPT_NOTIF; + } else { + int_opts = LYD_INTOPT_REPLY; + } + + if (op_tree->schema && (op_tree->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF))) { + /* we have the operation/notification, adjust the pointers */ + op_node = op_tree; + while (op_tree->parent) { + op_tree = lyd_parent(op_tree); + } + } else { + /* find the operation/notification */ + while (op_tree->parent) { + op_tree = lyd_parent(op_tree); + } + LYD_TREE_DFS_BEGIN(op_tree, op_node) { + if (!op_node->schema) { + return lyd_parse_opaq_error(op_node); + } else if (op_node->flags & LYD_EXT) { + /* fully validate the rest using the extension instance callback */ + LY_CHECK_RET(lyd_validate_nested_ext(op_node, &ext_val)); + rc = lyd_validate_unres((struct lyd_node **)&dep_tree, NULL, data_type, NULL, 0, NULL, NULL, NULL, + &ext_val, 0, diff); + ly_set_erase(&ext_val, free); + return rc; + } + + if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) && + (op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) { + break; + } else if ((int_opts & LYD_INTOPT_NOTIF) && (op_node->schema->nodetype == LYS_NOTIF)) { + break; + } + LYD_TREE_DFS_END(op_tree, op_node); + } + } + + if (int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_REPLY)) { + if (!op_node || !(op_node->schema->nodetype & (LYS_RPC | LYS_ACTION))) { + LOGERR(LYD_CTX(op_tree), LY_EINVAL, "No RPC/action to validate found."); + return LY_EINVAL; + } + } else { + if (!op_node || (op_node->schema->nodetype != LYS_NOTIF)) { + LOGERR(LYD_CTX(op_tree), LY_EINVAL, "No notification to validate found."); + return LY_EINVAL; + } + } + + /* validate */ + return _lyd_validate_op(op_tree, op_node, dep_tree, data_type, int_opts, 1, NULL, NULL, NULL, NULL, NULL, diff); +} diff --git a/src/validation.h b/src/validation.h new file mode 100644 index 0000000..c9f5da0 --- /dev/null +++ b/src/validation.h @@ -0,0 +1,107 @@ +/** + * @file validation.h + * @author Michal Vasko + * @brief Validation routines. + * + * Copyright (c) 2019 - 2022 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_VALIDATION_H_ +#define LY_VALIDATION_H_ + +#include + +#include "diff.h" +#include "log.h" +#include "parser_data.h" + +struct ly_ctx; +struct ly_set; +struct lyd_node; +struct lys_module; +struct lysc_node; + +/** + * @brief Add new changes into a diff. They are always merged. + * + * @param[in] node Node/subtree to add. + * @param[in] op Operation of the change. + * @param[in,out] diff Diff to update. + * @return LY_ERR value. + */ +LY_ERR lyd_val_diff_add(const struct lyd_node *node, enum lyd_diff_op op, struct lyd_node **diff); + +/** + * @brief Finish validation of nodes and attributes. Specifically, when (is processed first) and type validation. + * + * !! It is assumed autodeleted nodes cannot be in the unresolved node type set !! + * + * @param[in,out] tree Data tree, is updated if some nodes are autodeleted. + * @param[in] mod Module of the @p tree to take into consideration when deleting @p tree and moving it. + * If set, it is expected @p tree should point to the first node of @p mod. Otherwise it will simply be + * the first top-level sibling. + * @param[in] data_type Validate data type. + * @param[in] node_when Set with nodes with "when" conditions, can be NULL. + * @param[in] when_xp_opts Additional XPath options to use for evaluating "when". + * @param[in] node_types Set with nodes with unresolved types, can be NULL + * @param[in] meta_types Set with metadata with unresolved types, can be NULL. + * @param[in] ext_node Set with nodes with extensions to validate, can be NULL. + * @param[in] ext_val Set with extension data to validate, can be NULL. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[in,out] diff Validation diff. + * @return LY_ERR value. + */ +LY_ERR lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum lyd_type data_type, + struct ly_set *node_when, uint32_t when_xp_opts, struct ly_set *node_types, struct ly_set *meta_types, + struct ly_set *ext_node, struct ly_set *ext_val, uint32_t val_opts, struct lyd_node **diff); + +/** + * @brief Validate new siblings. Specifically, check duplicated instances, autodelete default values and cases. + * + * !! It is assumed autodeleted nodes cannot yet be in the unresolved node type set !! + * + * @param[in,out] first First sibling. + * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in,out] diff Validation diff. + * @return LY_ERR value. + */ +LY_ERR lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod, + struct lyd_node **diff); + +/** + * @brief Validate data node with an extension instance, if any, by storing it in its unres set. + * + * @param[in] node Node to check for an extension instance with a node callback. + * @param[in,out] ext_node Set with data nodes to validate. + * @return LY_ERR value. + */ +LY_ERR lyd_validate_node_ext(struct lyd_node *node, struct ly_set *ext_node); + +/** + * @brief Validate a data tree. + * + * @param[in,out] tree Data tree to validate, nodes may be autodeleted. + * @param[in] module Module whose data (and schema restrictions) to validate, NULL for all modules. + * @param[in] ctx libyang context. + * @param[in] val_opts Validation options, see @ref datavalidationoptions. + * @param[in] validate_subtree Whether subtree was already validated (as part of data parsing) or not (separate validation). + * @param[in] node_when_p Set of nodes with when conditions, if NULL a local set is used. + * @param[in] node_types_p Set of unres node types, if NULL a local set is used. + * @param[in] meta_types_p Set of unres metadata types, if NULL a local set is used. + * @param[in] ext_node_p Set of unres nodes with extensions to validate, if NULL a local set is used. + * @param[in] ext_val_p Set of unres extension data to validate, if NULL a local set is used. + * @param[out] diff Generated validation diff, not generated if NULL. + * @return LY_ERR value. + */ +LY_ERR lyd_validate(struct lyd_node **tree, const struct lys_module *module, const struct ly_ctx *ctx, uint32_t val_opts, + ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p, + struct ly_set *ext_node_p, struct ly_set *ext_val_p, struct lyd_node **diff); + +#endif /* LY_VALIDATION_H_ */ diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 0000000..1f56e10 --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,23 @@ +/** + * @file version.h + * @author Radek Krejci + * @brief libyang version definitions + * + * Copyright (c) 2020 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_VERSION_H_ +#define LY_VERSION_H_ + +#define LY_VERSION_MAJOR @LIBYANG_MAJOR_SOVERSION@ /**< libyang major version number */ +#define LY_VERSION_MINOR @LIBYANG_MINOR_SOVERSION@ /**< libyang minor version number */ +#define LY_VERSION_MICRO @LIBYANG_MICRO_SOVERSION@ /**< libyang micro version number */ +#define LY_VERSION "@LIBYANG_SOVERSION_FULL@" /**< libyang version string */ + +#endif /* LY_VERSION_H_ */ diff --git a/src/xml.c b/src/xml.c new file mode 100644 index 0000000..97e6abb --- /dev/null +++ b/src/xml.c @@ -0,0 +1,1402 @@ +/** + * @file xml.c + * @author Radek Krejci + * @author Michal Vasko + * @brief Generic XML parser implementation for libyang + * + * Copyright (c) 2015 - 2021 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 + +#include "xml.h" + +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "in_internal.h" +#include "out_internal.h" +#include "tree.h" +#include "tree_schema_internal.h" + +/* Move input p by s characters, if EOF log with lyxml_ctx c */ +#define move_input(c, s) \ + ly_in_skip(c->in, s); \ + LY_CHECK_ERR_RET(!c->in->current[0], LOGVAL(c->ctx, LY_VCODE_EOF), LY_EVALID) + +/* Ignore whitespaces in the input string p */ +#define ign_xmlws(c) \ + while (is_xmlws(*(c)->in->current)) { \ + if (*(c)->in->current == '\n') { \ + LY_IN_NEW_LINE((c)->in); \ + } \ + ly_in_skip(c->in, 1); \ + } + +static LY_ERR lyxml_next_attr_content(struct lyxml_ctx *xmlctx, const char **value, size_t *value_len, ly_bool *ws_only, + ly_bool *dynamic); + +/** + * @brief Ignore and skip any characters until the delim of the size delim_len is read, including the delim + * + * @param[in] xmlctx XML parser context to provide input handler and libyang context + * @param[in] in input handler to read the data, it is updated only in case the section is correctly terminated. + * @param[in] delim Delimiter to detect end of the section. + * @param[in] delim_len Length of the delimiter string to use. + * @param[in] sectname Section name to refer in error message. + */ +LY_ERR +skip_section(struct lyxml_ctx *xmlctx, const char *delim, size_t delim_len, const char *sectname) +{ + size_t i; + register const char *input, *a, *b; + uint64_t parsed = 0, newlines = 0; + + for (input = xmlctx->in->current; *input; ++input, ++parsed) { + if (*input != *delim) { + if (*input == '\n') { + ++newlines; + } + continue; + } + a = input; + b = delim; + for (i = 0; i < delim_len; ++i) { + if (*a++ != *b++) { + break; + } + } + if (i == delim_len) { + /* delim found */ + xmlctx->in->line += newlines; + ly_in_skip(xmlctx->in, parsed + delim_len); + return LY_SUCCESS; + } + } + + /* delim not found, + * do not update input handler to refer to the beginning of the section in error message */ + LOGVAL(xmlctx->ctx, LY_VCODE_NTERM, sectname); + return LY_EVALID; +} + +/** + * @brief Check/Get an XML identifier from the input string. + * + * The identifier must have at least one valid character complying the name start character constraints. + * The identifier is terminated by the first character, which does not comply to the name character constraints. + * + * See https://www.w3.org/TR/xml-names/#NT-NCName + * + * @param[in] xmlctx XML context. + * @param[out] start Pointer to the start of the identifier. + * @param[out] end Pointer ot the end of the identifier. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_parse_identifier(struct lyxml_ctx *xmlctx, const char **start, const char **end) +{ + const char *s, *in; + uint32_t c; + size_t parsed; + LY_ERR rc; + + in = s = xmlctx->in->current; + + /* check NameStartChar (minus colon) */ + LY_CHECK_ERR_RET(ly_getutf8(&in, &c, &parsed), + LOGVAL(xmlctx->ctx, LY_VCODE_INCHAR, in[0]), + LY_EVALID); + LY_CHECK_ERR_RET(!is_xmlqnamestartchar(c), + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Identifier \"%s\" starts with an invalid character.", in - parsed), + LY_EVALID); + + /* check rest of the identifier */ + do { + /* move only successfully parsed bytes */ + ly_in_skip(xmlctx->in, parsed); + + rc = ly_getutf8(&in, &c, &parsed); + LY_CHECK_ERR_RET(rc, LOGVAL(xmlctx->ctx, LY_VCODE_INCHAR, in[0]), LY_EVALID); + } while (is_xmlqnamechar(c)); + + *start = s; + *end = xmlctx->in->current; + return LY_SUCCESS; +} + +/** + * @brief Add namespace definition into XML context. + * + * Namespaces from a single element are supposed to be added sequentially together (not interleaved by a namespace from other + * element). This mimic namespace visibility, since the namespace defined in element E is not visible from its parents or + * siblings. On the other hand, namespace from a parent element can be redefined in a child element. This is also reflected + * by lyxml_ns_get() which returns the most recent namespace definition for the given prefix. + * + * When leaving processing of a subtree of some element (after it is removed from xmlctx->elements), caller is supposed to call + * lyxml_ns_rm() to remove all the namespaces defined in such an element from the context. + * + * @param[in] xmlctx XML context to work with. + * @param[in] prefix Pointer to the namespace prefix. Can be NULL for default namespace. + * @param[in] prefix_len Length of the prefix. + * @param[in] uri Namespace URI (value) to store directly. Value is always spent. + * @return LY_ERR values. + */ +LY_ERR +lyxml_ns_add(struct lyxml_ctx *xmlctx, const char *prefix, size_t prefix_len, char *uri) +{ + LY_ERR rc = LY_SUCCESS; + struct lyxml_ns *ns; + uint32_t i; + + /* check for duplicates */ + if (xmlctx->ns.count) { + i = xmlctx->ns.count; + do { + --i; + ns = xmlctx->ns.objs[i]; + if (ns->depth < xmlctx->elements.count) { + /* only namespaces of parents, no need to check further */ + break; + } else if (prefix && ns->prefix && !ly_strncmp(ns->prefix, prefix, prefix_len)) { + if (!strcmp(ns->uri, uri)) { + /* exact same prefix and namespace, ignore */ + goto cleanup; + } + + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Duplicate XML NS prefix \"%s\" used for namespaces \"%s\" and \"%s\".", + ns->prefix, ns->uri, uri); + rc = LY_EVALID; + goto cleanup; + } else if (!prefix && !ns->prefix) { + if (!strcmp(ns->uri, uri)) { + /* exact same default namespace, ignore */ + goto cleanup; + } + + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Duplicate default XML namespaces \"%s\" and \"%s\".", ns->uri, uri); + rc = LY_EVALID; + goto cleanup; + } + } while (i); + } + + ns = malloc(sizeof *ns); + LY_CHECK_ERR_GOTO(!ns, LOGMEM(xmlctx->ctx); rc = LY_EMEM, cleanup); + + /* we need to connect the depth of the element where the namespace is defined with the + * namespace record to be able to maintain (remove) the record when the parser leaves + * (to its sibling or back to the parent) the element where the namespace was defined */ + ns->depth = xmlctx->elements.count; + + ns->uri = uri; + if (prefix) { + ns->prefix = strndup(prefix, prefix_len); + LY_CHECK_ERR_GOTO(!ns->prefix, LOGMEM(xmlctx->ctx); free(ns); rc = LY_EMEM, cleanup); + } else { + ns->prefix = NULL; + } + + rc = ly_set_add(&xmlctx->ns, ns, 1, NULL); + LY_CHECK_ERR_GOTO(rc, free(ns->prefix); free(ns), cleanup); + + /* successfully stored */ + uri = NULL; + +cleanup: + free(uri); + return rc; +} + +void +lyxml_ns_rm(struct lyxml_ctx *xmlctx) +{ + for (uint32_t u = xmlctx->ns.count - 1; u + 1 > 0; --u) { + if (((struct lyxml_ns *)xmlctx->ns.objs[u])->depth != xmlctx->elements.count + 1) { + /* we are done, the namespaces from a single element are supposed to be together */ + break; + } + /* remove the ns structure */ + free(((struct lyxml_ns *)xmlctx->ns.objs[u])->prefix); + free(((struct lyxml_ns *)xmlctx->ns.objs[u])->uri); + free(xmlctx->ns.objs[u]); + --xmlctx->ns.count; + } + + if (!xmlctx->ns.count) { + /* cleanup the xmlctx's namespaces storage */ + ly_set_erase(&xmlctx->ns, NULL); + } +} + +const struct lyxml_ns * +lyxml_ns_get(const struct ly_set *ns_set, const char *prefix, size_t prefix_len) +{ + struct lyxml_ns *ns; + + for (uint32_t u = ns_set->count - 1; u + 1 > 0; --u) { + ns = (struct lyxml_ns *)ns_set->objs[u]; + if (prefix && prefix_len) { + if (ns->prefix && !ly_strncmp(ns->prefix, prefix, prefix_len)) { + return ns; + } + } else if (!ns->prefix) { + /* default namespace */ + return ns; + } + } + + return NULL; +} + +/** + * @brief Skip in the input until EOF or just after the opening tag. + * Handles special XML constructs (comment, cdata, doctype). + * + * @param[in] xmlctx XML context to use. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_skip_until_end_or_after_otag(struct lyxml_ctx *xmlctx) +{ + const struct ly_ctx *ctx = xmlctx->ctx; /* shortcut */ + const char *endtag, *sectname; + size_t endtag_len; + + while (1) { + ign_xmlws(xmlctx); + + if (xmlctx->in->current[0] == '\0') { + /* EOF */ + if (xmlctx->elements.count) { + LOGVAL(ctx, LY_VCODE_EOF); + return LY_EVALID; + } + return LY_SUCCESS; + } else if (xmlctx->in->current[0] != '<') { + LOGVAL(ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current), xmlctx->in->current, + "element tag start ('<')"); + return LY_EVALID; + } + move_input(xmlctx, 1); + + if (xmlctx->in->current[0] == '!') { + move_input(xmlctx, 1); + /* sections to ignore */ + if (!strncmp(xmlctx->in->current, "--", 2)) { + /* comment */ + move_input(xmlctx, 2); + sectname = "Comment"; + endtag = "-->"; + endtag_len = ly_strlen_const("-->"); + } else if (!strncmp(xmlctx->in->current, "DOCTYPE", ly_strlen_const("DOCTYPE"))) { + /* Document type declaration - not supported */ + LOGVAL(ctx, LY_VCODE_NSUPP, "Document Type Declaration"); + return LY_EVALID; + } else { + LOGVAL(ctx, LYVE_SYNTAX, "Unknown XML section \"%.20s\".", &xmlctx->in->current[-2]); + return LY_EVALID; + } + LY_CHECK_RET(skip_section(xmlctx, endtag, endtag_len, sectname)); + } else if (xmlctx->in->current[0] == '?') { + LY_CHECK_RET(skip_section(xmlctx, "?>", 2, "Declaration")); + } else { + /* other non-WS character */ + break; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse QName. + * + * @param[in] xmlctx XML context to use. + * @param[out] prefix Parsed prefix, may be NULL. + * @param[out] prefix_len Length of @p prefix. + * @param[out] name Parsed name. + * @param[out] name_len Length of @p name. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_parse_qname(struct lyxml_ctx *xmlctx, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len) +{ + const char *start, *end; + + *prefix = NULL; + *prefix_len = 0; + + LY_CHECK_RET(lyxml_parse_identifier(xmlctx, &start, &end)); + if (end[0] == ':') { + /* we have prefixed identifier */ + *prefix = start; + *prefix_len = end - start; + + move_input(xmlctx, 1); + LY_CHECK_RET(lyxml_parse_identifier(xmlctx, &start, &end)); + } + + *name = start; + *name_len = end - start; + return LY_SUCCESS; +} + +/** + * @brief Prepare buffer for new data. + * + * @param[in] ctx Context for logging. + * @param[in,out] in XML input data. + * @param[in,out] offset Current offset in @p in. + * @param[in] need_space Needed additional free space that is allocated. + * @param[in,out] buf Dynamic buffer. + * @param[in,out] len Current @p buf length (used characters). + * @param[in,out] size Current @p buf size (allocated characters). + * @return LY_ERR value. + */ +static LY_ERR +lyxml_parse_value_use_buf(const struct ly_ctx *ctx, const char **in, size_t *offset, size_t need_space, char **buf, + size_t *len, size_t *size) +{ +#define BUFSIZE 24 +#define BUFSIZE_STEP 128 + + if (!*buf) { + /* prepare output buffer */ + *buf = malloc(BUFSIZE); + LY_CHECK_ERR_RET(!*buf, LOGMEM(ctx), LY_EMEM); + *size = BUFSIZE; + } + + /* allocate needed space */ + while (*len + *offset + need_space >= *size) { + *buf = ly_realloc(*buf, *size + BUFSIZE_STEP); + LY_CHECK_ERR_RET(!*buf, LOGMEM(ctx), LY_EMEM); + *size += BUFSIZE_STEP; + } + + if (*offset) { + /* store what we have so far */ + memcpy(&(*buf)[*len], *in, *offset); + *len += *offset; + *in += *offset; + *offset = 0; + } + + return LY_SUCCESS; + +#undef BUFSIZE +#undef BUFSIZE_STEP +} + +/** + * @brief Parse XML text content (value). + * + * @param[in] xmlctx XML context to use. + * @param[in] endchar Expected character to mark value end. + * @param[out] value Parsed value. + * @param[out] length Length of @p value. + * @param[out] ws_only Whether the value is empty/white-spaces only. + * @param[out] dynamic Whether the value was dynamically allocated. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_parse_value(struct lyxml_ctx *xmlctx, char endchar, char **value, size_t *length, ly_bool *ws_only, ly_bool *dynamic) +{ + const struct ly_ctx *ctx = xmlctx->ctx; /* shortcut */ + const char *in = xmlctx->in->current, *start, *in_aux; + char *buf = NULL; + size_t offset; /* read offset in input buffer */ + size_t len; /* length of the output string (write offset in output buffer) */ + size_t size = 0; /* size of the output buffer */ + void *p; + uint32_t n; + size_t u; + ly_bool ws = 1; + + assert(xmlctx); + + /* init */ + start = in; + offset = len = 0; + + /* parse */ + while (in[offset]) { + if (in[offset] == '&') { + /* non WS */ + ws = 0; + + /* use buffer and allocate enough for the offset and next character, + * we will need 4 bytes at most since we support only the predefined + * (one-char) entities and character references */ + LY_CHECK_RET(lyxml_parse_value_use_buf(ctx, &in, &offset, 4, &buf, &len, &size)); + + ++offset; + if (in[offset] != '#') { + /* entity reference - only predefined references are supported */ + if (!strncmp(&in[offset], "lt;", ly_strlen_const("lt;"))) { + buf[len++] = '<'; + in += ly_strlen_const("<"); + } else if (!strncmp(&in[offset], "gt;", ly_strlen_const("gt;"))) { + buf[len++] = '>'; + in += ly_strlen_const(">"); + } else if (!strncmp(&in[offset], "amp;", ly_strlen_const("amp;"))) { + buf[len++] = '&'; + in += ly_strlen_const("&"); + } else if (!strncmp(&in[offset], "apos;", ly_strlen_const("apos;"))) { + buf[len++] = '\''; + in += ly_strlen_const("'"); + } else if (!strncmp(&in[offset], "quot;", ly_strlen_const("quot;"))) { + buf[len++] = '\"'; + in += ly_strlen_const("""); + } else { + LOGVAL(ctx, LYVE_SYNTAX, "Entity reference \"%.*s\" not supported, only predefined references allowed.", + 10, &in[offset - 1]); + goto error; + } + offset = 0; + } else { + p = (void *)&in[offset - 1]; + /* character reference */ + ++offset; + if (isdigit(in[offset])) { + for (n = 0; isdigit(in[offset]); offset++) { + n = (LY_BASE_DEC * n) + (in[offset] - '0'); + } + } else if ((in[offset] == 'x') && isxdigit(in[offset + 1])) { + for (n = 0, ++offset; isxdigit(in[offset]); offset++) { + if (isdigit(in[offset])) { + u = (in[offset] - '0'); + } else if (in[offset] > 'F') { + u = LY_BASE_DEC + (in[offset] - 'a'); + } else { + u = LY_BASE_DEC + (in[offset] - 'A'); + } + n = (LY_BASE_HEX * n) + u; + } + } else { + LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\".", 12, p); + goto error; + + } + + if (in[offset] != ';') { + LOGVAL(ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(&in[offset]), &in[offset], ";"); + goto error; + } + ++offset; + if (ly_pututf8(&buf[len], n, &u)) { + LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\" (0x%08x).", 12, p, n); + goto error; + } + len += u; + in += offset; + offset = 0; + } + } else if (!strncmp(in + offset, ""); + if (!in_aux) { + LOGVAL(xmlctx->ctx, LY_VCODE_NTERM, "CDATA"); + goto error; + } + u = in_aux - (in + offset + ly_strlen_const("in); + } else if (!is_xmlws(in[n])) { + ws = 0; + } + } + + /* copy CDATA */ + memcpy(buf + len, in, u); + len += u; + + /* move input skipping the end tag */ + in += u + ly_strlen_const("]]>"); + } else if (in[offset] == endchar) { + /* end of string */ + if (buf) { + /* realloc exact size string */ + buf = ly_realloc(buf, len + offset + 1); + LY_CHECK_ERR_RET(!buf, LOGMEM(ctx), LY_EMEM); + size = len + offset + 1; + if (offset) { + memcpy(&buf[len], in, offset); + } + + /* set terminating NULL byte */ + buf[len + offset] = '\0'; + } + len += offset; + in += offset; + goto success; + } else { + if (!is_xmlws(in[offset])) { + /* non WS */ + ws = 0; + } + + /* log lines */ + if (in[offset] == '\n') { + LY_IN_NEW_LINE(xmlctx->in); + } + + /* continue */ + in_aux = &in[offset]; + LY_CHECK_ERR_GOTO(ly_getutf8(&in_aux, &n, &u), + LOGVAL(ctx, LY_VCODE_INCHAR, in[offset]), error); + offset += u; + } + } + + /* EOF reached before endchar */ + LOGVAL(ctx, LY_VCODE_EOF); + +error: + free(buf); + return LY_EVALID; + +success: + if (buf) { + *value = buf; + *dynamic = 1; + } else { + *value = (char *)start; + *dynamic = 0; + } + *length = len; + *ws_only = ws; + + xmlctx->in->current = in; + return LY_SUCCESS; +} + +/** + * @brief Parse XML closing element and match it to a stored starting element. + * + * @param[in] xmlctx XML context to use. + * @param[in] prefix Expected closing element prefix. + * @param[in] prefix_len Length of @p prefix. + * @param[in] name Expected closing element name. + * @param[in] name_len Length of @p name. + * @param[in] empty Whether we are parsing a special "empty" element (with joined starting and closing tag) with no value. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_close_element(struct lyxml_ctx *xmlctx, const char *prefix, size_t prefix_len, const char *name, size_t name_len, + ly_bool empty) +{ + struct lyxml_elem *e; + + /* match opening and closing element tags */ + if (!xmlctx->elements.count) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Stray closing element tag (\"%.*s\").", + (int)name_len, name); + return LY_EVALID; + } + + e = (struct lyxml_elem *)xmlctx->elements.objs[xmlctx->elements.count - 1]; + if ((e->prefix_len != prefix_len) || (e->name_len != name_len) || + (prefix_len && strncmp(prefix, e->prefix, e->prefix_len)) || strncmp(name, e->name, e->name_len)) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Opening (\"%.*s%s%.*s\") and closing (\"%.*s%s%.*s\") elements tag mismatch.", + (int)e->prefix_len, e->prefix ? e->prefix : "", e->prefix ? ":" : "", (int)e->name_len, e->name, + (int)prefix_len, prefix ? prefix : "", prefix ? ":" : "", (int)name_len, name); + return LY_EVALID; + } + + /* opening and closing element tags matches, remove record from the opening tags list */ + ly_set_rm_index(&xmlctx->elements, xmlctx->elements.count - 1, free); + + /* remove also the namespaces connected with the element */ + lyxml_ns_rm(xmlctx); + + /* skip WS */ + ign_xmlws(xmlctx); + + /* special "" element */ + if (empty && (xmlctx->in->current[0] == '/')) { + move_input(xmlctx, 1); + } + + /* parse closing tag */ + if (xmlctx->in->current[0] != '>') { + LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current), + xmlctx->in->current, "element tag termination ('>')"); + return LY_EVALID; + } + + /* move after closing tag without checking for EOF */ + ly_in_skip(xmlctx->in, 1); + + return LY_SUCCESS; +} + +/** + * @brief Store parsed opening element and parse any included namespaces. + * + * @param[in] xmlctx XML context to use. + * @param[in] prefix Parsed starting element prefix. + * @param[in] prefix_len Length of @p prefix. + * @param[in] name Parsed starting element name. + * @param[in] name_len Length of @p name. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_open_element(struct lyxml_ctx *xmlctx, const char *prefix, size_t prefix_len, const char *name, size_t name_len) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxml_elem *e; + const char *prev_input; + uint64_t prev_line; + char *value; + size_t parsed, value_len; + ly_bool ws_only, dynamic, is_ns; + uint32_t c; + + /* store element opening tag information */ + e = malloc(sizeof *e); + LY_CHECK_ERR_RET(!e, LOGMEM(xmlctx->ctx), LY_EMEM); + e->name = name; + e->prefix = prefix; + e->name_len = name_len; + e->prefix_len = prefix_len; + + LY_CHECK_RET(ly_set_add(&xmlctx->elements, e, 1, NULL)); + if (xmlctx->elements.count > LY_MAX_BLOCK_DEPTH) { + LOGERR(xmlctx->ctx, LY_EINVAL, "The maximum number of open elements has been exceeded."); + return LY_EINVAL; + } + + /* skip WS */ + ign_xmlws(xmlctx); + + /* parse and store all namespaces */ + prev_input = xmlctx->in->current; + prev_line = xmlctx->in->line; + is_ns = 1; + while ((xmlctx->in->current[0] != '\0') && !(ret = ly_getutf8(&xmlctx->in->current, &c, &parsed))) { + if (!is_xmlqnamestartchar(c)) { + break; + } + xmlctx->in->current -= parsed; + + /* parse attribute name */ + LY_CHECK_GOTO(ret = lyxml_parse_qname(xmlctx, &prefix, &prefix_len, &name, &name_len), cleanup); + + /* parse the value */ + LY_CHECK_GOTO(ret = lyxml_next_attr_content(xmlctx, (const char **)&value, &value_len, &ws_only, &dynamic), cleanup); + + /* store every namespace */ + if ((prefix && !ly_strncmp("xmlns", prefix, prefix_len)) || (!prefix && !ly_strncmp("xmlns", name, name_len))) { + ret = lyxml_ns_add(xmlctx, prefix ? name : NULL, prefix ? name_len : 0, + dynamic ? value : strndup(value, value_len)); + dynamic = 0; + LY_CHECK_GOTO(ret, cleanup); + } else { + /* not a namespace */ + is_ns = 0; + } + if (dynamic) { + free(value); + } + + /* skip WS */ + ign_xmlws(xmlctx); + + if (is_ns) { + /* we can actually skip all the namespaces as there is no reason to parse them again */ + prev_input = xmlctx->in->current; + prev_line = xmlctx->in->line; + } + } + +cleanup: + if (!ret) { + xmlctx->in->current = prev_input; + xmlctx->in->line = prev_line; + } + return ret; +} + +/** + * @brief Move parser to the attribute content and parse it. + * + * @param[in] xmlctx XML context to use. + * @param[out] value Parsed attribute value. + * @param[out] value_len Length of @p value. + * @param[out] ws_only Whether the value is empty/white-spaces only. + * @param[out] dynamic Whether the value was dynamically allocated. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_next_attr_content(struct lyxml_ctx *xmlctx, const char **value, size_t *value_len, ly_bool *ws_only, ly_bool *dynamic) +{ + char quot; + + /* skip WS */ + ign_xmlws(xmlctx); + + /* skip '=' */ + if (xmlctx->in->current[0] == '\0') { + LOGVAL(xmlctx->ctx, LY_VCODE_EOF); + return LY_EVALID; + } else if (xmlctx->in->current[0] != '=') { + LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current), + xmlctx->in->current, "'='"); + return LY_EVALID; + } + move_input(xmlctx, 1); + + /* skip WS */ + ign_xmlws(xmlctx); + + /* find quotes */ + if (xmlctx->in->current[0] == '\0') { + LOGVAL(xmlctx->ctx, LY_VCODE_EOF); + return LY_EVALID; + } else if ((xmlctx->in->current[0] != '\'') && (xmlctx->in->current[0] != '\"')) { + LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(xmlctx->in->current), + xmlctx->in->current, "either single or double quotation mark"); + return LY_EVALID; + } + + /* remember quote */ + quot = xmlctx->in->current[0]; + move_input(xmlctx, 1); + + /* parse attribute value */ + LY_CHECK_RET(lyxml_parse_value(xmlctx, quot, (char **)value, value_len, ws_only, dynamic)); + + /* move after ending quote (without checking for EOF) */ + ly_in_skip(xmlctx->in, 1); + + return LY_SUCCESS; +} + +/** + * @brief Move parser to the next attribute and parse it. + * + * @param[in] xmlctx XML context to use. + * @param[out] prefix Parsed attribute prefix. + * @param[out] prefix_len Length of @p prefix. + * @param[out] name Parsed attribute name. + * @param[out] name_len Length of @p name. + * @return LY_ERR value. + */ +static LY_ERR +lyxml_next_attribute(struct lyxml_ctx *xmlctx, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len) +{ + const char *in; + char *value; + uint32_t c; + size_t parsed, value_len; + ly_bool ws_only, dynamic; + + /* skip WS */ + ign_xmlws(xmlctx); + + /* parse only possible attributes */ + while ((xmlctx->in->current[0] != '>') && (xmlctx->in->current[0] != '/')) { + in = xmlctx->in->current; + if (in[0] == '\0') { + LOGVAL(xmlctx->ctx, LY_VCODE_EOF); + return LY_EVALID; + } else if ((ly_getutf8(&in, &c, &parsed) || !is_xmlqnamestartchar(c))) { + LOGVAL(xmlctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(in - parsed), in - parsed, + "element tag end ('>' or '/>') or an attribute"); + return LY_EVALID; + } + + /* parse attribute name */ + LY_CHECK_RET(lyxml_parse_qname(xmlctx, prefix, prefix_len, name, name_len)); + + if ((!*prefix || ly_strncmp("xmlns", *prefix, *prefix_len)) && (*prefix || ly_strncmp("xmlns", *name, *name_len))) { + /* standard attribute */ + break; + } + + /* namespace, skip it */ + LY_CHECK_RET(lyxml_next_attr_content(xmlctx, (const char **)&value, &value_len, &ws_only, &dynamic)); + if (dynamic) { + free(value); + } + + /* skip WS */ + ign_xmlws(xmlctx); + } + + return LY_SUCCESS; +} + +/** + * @brief Move parser to the next element and parse it. + * + * @param[in] xmlctx XML context to use. + * @param[out] prefix Parsed element prefix. + * @param[out] prefix_len Length of @p prefix. + * @param[out] name Parse element name. + * @param[out] name_len Length of @p name. + * @param[out] closing Flag if the element is closing (includes '/'). + * @return LY_ERR value. + */ +static LY_ERR +lyxml_next_element(struct lyxml_ctx *xmlctx, const char **prefix, size_t *prefix_len, const char **name, size_t *name_len, + ly_bool *closing) +{ + /* skip WS until EOF or after opening tag '<' */ + LY_CHECK_RET(lyxml_skip_until_end_or_after_otag(xmlctx)); + if (xmlctx->in->current[0] == '\0') { + /* set return values */ + *prefix = *name = NULL; + *prefix_len = *name_len = 0; + return LY_SUCCESS; + } + + if (xmlctx->in->current[0] == '/') { + move_input(xmlctx, 1); + *closing = 1; + } else { + *closing = 0; + } + + /* skip WS */ + ign_xmlws(xmlctx); + + /* parse element name */ + LY_CHECK_RET(lyxml_parse_qname(xmlctx, prefix, prefix_len, name, name_len)); + + return LY_SUCCESS; +} + +LY_ERR +lyxml_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyxml_ctx **xmlctx_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxml_ctx *xmlctx; + ly_bool closing; + + /* new context */ + xmlctx = calloc(1, sizeof *xmlctx); + LY_CHECK_ERR_RET(!xmlctx, LOGMEM(ctx), LY_EMEM); + xmlctx->ctx = ctx; + xmlctx->in = in; + + LOG_LOCSET(NULL, NULL, NULL, in); + + /* parse next element, if any */ + LY_CHECK_GOTO(ret = lyxml_next_element(xmlctx, &xmlctx->prefix, &xmlctx->prefix_len, &xmlctx->name, + &xmlctx->name_len, &closing), cleanup); + + if (xmlctx->in->current[0] == '\0') { + /* update status */ + xmlctx->status = LYXML_END; + } else if (closing) { + LOGVAL(ctx, LYVE_SYNTAX, "Stray closing element tag (\"%.*s\").", (int)xmlctx->name_len, xmlctx->name); + ret = LY_EVALID; + goto cleanup; + } else { + /* open an element, also parses all enclosed namespaces */ + LY_CHECK_GOTO(ret = lyxml_open_element(xmlctx, xmlctx->prefix, xmlctx->prefix_len, xmlctx->name, xmlctx->name_len), cleanup); + + /* update status */ + xmlctx->status = LYXML_ELEMENT; + } + +cleanup: + if (ret) { + lyxml_ctx_free(xmlctx); + } else { + *xmlctx_p = xmlctx; + } + return ret; +} + +LY_ERR +lyxml_ctx_next(struct lyxml_ctx *xmlctx) +{ + LY_ERR ret = LY_SUCCESS; + ly_bool closing; + struct lyxml_elem *e; + + /* if the value was not used, free it */ + if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) { + free((char *)xmlctx->value); + xmlctx->value = NULL; + xmlctx->dynamic = 0; + } + + switch (xmlctx->status) { + case LYXML_ELEM_CONTENT: + /* content | */ + + /* handle special case when empty content for "" was returned */ + if (xmlctx->in->current[0] == '/') { + assert(xmlctx->elements.count); + e = (struct lyxml_elem *)xmlctx->elements.objs[xmlctx->elements.count - 1]; + + /* close the element (parses closing tag) */ + ret = lyxml_close_element(xmlctx, e->prefix, e->prefix_len, e->name, e->name_len, 1); + LY_CHECK_GOTO(ret, cleanup); + + /* update status */ + xmlctx->status = LYXML_ELEM_CLOSE; + break; + } + /* fall through */ + case LYXML_ELEM_CLOSE: + /* | * */ + + /* parse next element, if any */ + ret = lyxml_next_element(xmlctx, &xmlctx->prefix, &xmlctx->prefix_len, &xmlctx->name, &xmlctx->name_len, &closing); + LY_CHECK_GOTO(ret, cleanup); + + if (xmlctx->in->current[0] == '\0') { + /* update status */ + xmlctx->status = LYXML_END; + } else if (closing) { + /* close an element (parses also closing tag) */ + ret = lyxml_close_element(xmlctx, xmlctx->prefix, xmlctx->prefix_len, xmlctx->name, xmlctx->name_len, 0); + LY_CHECK_GOTO(ret, cleanup); + + /* update status */ + xmlctx->status = LYXML_ELEM_CLOSE; + } else { + /* open an element, also parses all enclosed namespaces */ + ret = lyxml_open_element(xmlctx, xmlctx->prefix, xmlctx->prefix_len, xmlctx->name, xmlctx->name_len); + LY_CHECK_GOTO(ret, cleanup); + + /* update status */ + xmlctx->status = LYXML_ELEMENT; + } + break; + + case LYXML_ELEMENT: + /* content */ + case LYXML_ATTR_CONTENT: + /* attr='val'| attr='val'* > content */ + + /* parse attribute name, if any */ + ret = lyxml_next_attribute(xmlctx, &xmlctx->prefix, &xmlctx->prefix_len, &xmlctx->name, &xmlctx->name_len); + LY_CHECK_GOTO(ret, cleanup); + + if (xmlctx->in->current[0] == '>') { + /* no attributes but a closing tag */ + ly_in_skip(xmlctx->in, 1); + if (!xmlctx->in->current[0]) { + LOGVAL(xmlctx->ctx, LY_VCODE_EOF); + ret = LY_EVALID; + goto cleanup; + } + + /* parse element content */ + ret = lyxml_parse_value(xmlctx, '<', (char **)&xmlctx->value, &xmlctx->value_len, &xmlctx->ws_only, + &xmlctx->dynamic); + LY_CHECK_GOTO(ret, cleanup); + + if (!xmlctx->value_len) { + /* empty value should by alocated staticaly, but check for in any case */ + if (xmlctx->dynamic) { + free((char *) xmlctx->value); + } + /* use empty value, easier to work with */ + xmlctx->value = ""; + xmlctx->dynamic = 0; + } + + /* update status */ + xmlctx->status = LYXML_ELEM_CONTENT; + } else if (xmlctx->in->current[0] == '/') { + /* no content but we still return it */ + xmlctx->value = ""; + xmlctx->value_len = 0; + xmlctx->ws_only = 1; + xmlctx->dynamic = 0; + + /* update status */ + xmlctx->status = LYXML_ELEM_CONTENT; + } else { + /* update status */ + xmlctx->status = LYXML_ATTRIBUTE; + } + break; + + case LYXML_ATTRIBUTE: + /* attr|='val' */ + + /* skip formatting and parse value */ + ret = lyxml_next_attr_content(xmlctx, &xmlctx->value, &xmlctx->value_len, &xmlctx->ws_only, &xmlctx->dynamic); + LY_CHECK_GOTO(ret, cleanup); + + /* update status */ + xmlctx->status = LYXML_ATTR_CONTENT; + break; + + case LYXML_END: + /* |EOF */ + /* nothing to do */ + break; + } + +cleanup: + if (ret) { + /* invalidate context */ + xmlctx->status = LYXML_END; + } + return ret; +} + +LY_ERR +lyxml_ctx_peek(struct lyxml_ctx *xmlctx, enum LYXML_PARSER_STATUS *next) +{ + LY_ERR ret = LY_SUCCESS; + const char *prefix, *name, *prev_input; + size_t prefix_len, name_len; + ly_bool closing; + + prev_input = xmlctx->in->current; + + switch (xmlctx->status) { + case LYXML_ELEM_CONTENT: + if (xmlctx->in->current[0] == '/') { + *next = LYXML_ELEM_CLOSE; + break; + } + /* fall through */ + case LYXML_ELEM_CLOSE: + /* parse next element, if any */ + ret = lyxml_next_element(xmlctx, &prefix, &prefix_len, &name, &name_len, &closing); + LY_CHECK_GOTO(ret, cleanup); + + if (xmlctx->in->current[0] == '\0') { + *next = LYXML_END; + } else if (closing) { + *next = LYXML_ELEM_CLOSE; + } else { + *next = LYXML_ELEMENT; + } + break; + case LYXML_ELEMENT: + case LYXML_ATTR_CONTENT: + /* parse attribute name, if any */ + ret = lyxml_next_attribute(xmlctx, &prefix, &prefix_len, &name, &name_len); + LY_CHECK_GOTO(ret, cleanup); + + if ((xmlctx->in->current[0] == '>') || (xmlctx->in->current[0] == '/')) { + *next = LYXML_ELEM_CONTENT; + } else { + *next = LYXML_ATTRIBUTE; + } + break; + case LYXML_ATTRIBUTE: + *next = LYXML_ATTR_CONTENT; + break; + case LYXML_END: + *next = LYXML_END; + break; + } + +cleanup: + xmlctx->in->current = prev_input; + return ret; +} + +/** + * @brief Free all namespaces in XML context. + * + * @param[in] xmlctx XML context to use. + */ +static void +lyxml_ns_rm_all(struct lyxml_ctx *xmlctx) +{ + struct lyxml_ns *ns; + uint32_t i; + + for (i = 0; i < xmlctx->ns.count; ++i) { + ns = xmlctx->ns.objs[i]; + + free(ns->prefix); + free(ns->uri); + free(ns); + } + ly_set_erase(&xmlctx->ns, NULL); +} + +void +lyxml_ctx_free(struct lyxml_ctx *xmlctx) +{ + if (!xmlctx) { + return; + } + + LOG_LOCBACK(0, 0, 0, 1); + + if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) { + free((char *)xmlctx->value); + } + ly_set_erase(&xmlctx->elements, free); + lyxml_ns_rm_all(xmlctx); + free(xmlctx); +} + +/** + * @brief Duplicate an XML element. + * + * @param[in] elem Element to duplicate. + * @return Element duplicate. + * @return NULL on error. + */ +static struct lyxml_elem * +lyxml_elem_dup(const struct lyxml_elem *elem) +{ + struct lyxml_elem *dup; + + dup = malloc(sizeof *dup); + LY_CHECK_ERR_RET(!dup, LOGMEM(NULL), NULL); + + memcpy(dup, elem, sizeof *dup); + + return dup; +} + +/** + * @brief Duplicate an XML namespace. + * + * @param[in] ns Namespace to duplicate. + * @return Namespace duplicate. + * @return NULL on error. + */ +static struct lyxml_ns * +lyxml_ns_dup(const struct lyxml_ns *ns) +{ + struct lyxml_ns *dup; + + dup = malloc(sizeof *dup); + LY_CHECK_ERR_RET(!dup, LOGMEM(NULL), NULL); + + if (ns->prefix) { + dup->prefix = strdup(ns->prefix); + LY_CHECK_ERR_RET(!dup->prefix, LOGMEM(NULL); free(dup), NULL); + } else { + dup->prefix = NULL; + } + dup->uri = strdup(ns->uri); + LY_CHECK_ERR_RET(!dup->uri, LOGMEM(NULL); free(dup->prefix); free(dup), NULL); + dup->depth = ns->depth; + + return dup; +} + +LY_ERR +lyxml_ctx_backup(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup) +{ + uint32_t i; + + /* first make shallow copy */ + memcpy(backup, xmlctx, sizeof *backup); + + if ((xmlctx->status == LYXML_ELEM_CONTENT) && xmlctx->dynamic) { + /* it was backed up, do not free */ + xmlctx->dynamic = 0; + } + + /* backup in */ + backup->b_current = xmlctx->in->current; + backup->b_line = xmlctx->in->line; + + /* duplicate elements */ + backup->elements.objs = malloc(xmlctx->elements.size * sizeof(struct lyxml_elem)); + LY_CHECK_ERR_RET(!backup->elements.objs, LOGMEM(xmlctx->ctx), LY_EMEM); + for (i = 0; i < xmlctx->elements.count; ++i) { + backup->elements.objs[i] = lyxml_elem_dup(xmlctx->elements.objs[i]); + LY_CHECK_RET(!backup->elements.objs[i], LY_EMEM); + } + + /* duplicate ns */ + backup->ns.objs = malloc(xmlctx->ns.size * sizeof(struct lyxml_ns)); + LY_CHECK_ERR_RET(!backup->ns.objs, LOGMEM(xmlctx->ctx), LY_EMEM); + for (i = 0; i < xmlctx->ns.count; ++i) { + backup->ns.objs[i] = lyxml_ns_dup(xmlctx->ns.objs[i]); + LY_CHECK_RET(!backup->ns.objs[i], LY_EMEM); + } + + return LY_SUCCESS; +} + +void +lyxml_ctx_restore(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup) +{ + if (((xmlctx->status == LYXML_ELEM_CONTENT) || (xmlctx->status == LYXML_ATTR_CONTENT)) && xmlctx->dynamic) { + /* free dynamic value */ + free((char *)xmlctx->value); + } + + /* free elements */ + ly_set_erase(&xmlctx->elements, free); + + /* free ns */ + lyxml_ns_rm_all(xmlctx); + + /* restore in */ + xmlctx->in->current = backup->b_current; + xmlctx->in->line = backup->b_line; + backup->in = xmlctx->in; + + /* restore backup */ + memcpy(xmlctx, backup, sizeof *xmlctx); +} + +LY_ERR +lyxml_dump_text(struct ly_out *out, const char *text, ly_bool attribute) +{ + LY_ERR ret; + + if (!text) { + return 0; + } + + for (uint64_t u = 0; text[u]; u++) { + switch (text[u]) { + case '&': + ret = ly_print_(out, "&"); + break; + case '<': + ret = ly_print_(out, "<"); + break; + case '>': + /* not needed, just for readability */ + ret = ly_print_(out, ">"); + break; + case '"': + if (attribute) { + ret = ly_print_(out, """); + break; + } + /* fall through */ + default: + ret = ly_write_(out, &text[u], 1); + break; + } + LY_CHECK_RET(ret); + } + + return LY_SUCCESS; +} + +LY_ERR +lyxml_value_compare(const struct ly_ctx *ctx1, const char *value1, void *val_prefix_data1, + const struct ly_ctx *ctx2, const char *value2, void *val_prefix_data2) +{ + const char *value1_iter, *value2_iter; + const char *value1_next, *value2_next; + uint32_t value1_len, value2_len; + ly_bool is_prefix1, is_prefix2; + const struct lys_module *mod1, *mod2; + LY_ERR ret; + + if (!value1 && !value2) { + return LY_SUCCESS; + } + if ((value1 && !value2) || (!value1 && value2)) { + return LY_ENOT; + } + + if (!ctx2) { + ctx2 = ctx1; + } + + ret = LY_SUCCESS; + for (value1_iter = value1, value2_iter = value2; + value1_iter && value2_iter; + value1_iter = value1_next, value2_iter = value2_next) { + if ((ret = ly_value_prefix_next(value1_iter, NULL, &value1_len, &is_prefix1, &value1_next))) { + break; + } + if ((ret = ly_value_prefix_next(value2_iter, NULL, &value2_len, &is_prefix2, &value2_next))) { + break; + } + + if (is_prefix1 != is_prefix2) { + ret = LY_ENOT; + break; + } + + if (!is_prefix1) { + if (value1_len != value2_len) { + ret = LY_ENOT; + break; + } + if (strncmp(value1_iter, value2_iter, value1_len)) { + ret = LY_ENOT; + break; + } + continue; + } + + mod1 = mod2 = NULL; + if (val_prefix_data1) { + /* find module of the first prefix, if any */ + mod1 = ly_resolve_prefix(ctx1, value1_iter, value1_len, LY_VALUE_XML, val_prefix_data1); + } + if (val_prefix_data2) { + mod2 = ly_resolve_prefix(ctx2, value2_iter, value2_len, LY_VALUE_XML, val_prefix_data2); + } + if (!mod1 || !mod2) { + /* not a prefix or maps to different namespaces */ + ret = LY_ENOT; + break; + } + + if (mod1->ctx == mod2->ctx) { + /* same contexts */ + if ((mod1->name != mod2->name) || (mod1->revision != mod2->revision)) { + ret = LY_ENOT; + break; + } + } else { + /* different contexts */ + if (strcmp(mod1->name, mod2->name)) { + ret = LY_ENOT; + break; + } + + if (mod1->revision || mod2->revision) { + if (!mod1->revision || !mod2->revision) { + ret = LY_ENOT; + break; + } + if (strcmp(mod1->revision, mod2->revision)) { + ret = LY_ENOT; + break; + } + } + } + } + + if (value1_iter || value2_iter) { + ret = LY_ENOT; + } + + return ret; +} diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 0000000..fc86a81 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,209 @@ +/** + * @file xml.h + * @author Radek Krejci + * @author Michal Vasko + * @brief Generic XML parser routines. + * + * Copyright (c) 2015 - 2022 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_XML_H_ +#define LY_XML_H_ + +#include +#include + +#include "log.h" +#include "set.h" + +struct ly_ctx; +struct ly_in; +struct ly_out; + +/* Macro to test if character is whitespace */ +#define is_xmlws(c) (c == 0x20 || c == 0x9 || c == 0xa || c == 0xd) + +/* Macro to test if character is allowed to be a first character of an qualified identifier */ +#define is_xmlqnamestartchar(c) ((c >= 'a' && c <= 'z') || c == '_' || \ + (c >= 'A' && c <= 'Z') || /* c == ':' || */ \ + (c >= 0x370 && c <= 0x1fff && c != 0x37e ) || \ + (c >= 0xc0 && c <= 0x2ff && c != 0xd7 && c != 0xf7) || c == 0x200c || \ + c == 0x200d || (c >= 0x2070 && c <= 0x218f) || \ + (c >= 0x2c00 && c <= 0x2fef) || (c >= 0x3001 && c <= 0xd7ff) || \ + (c >= 0xf900 && c <= 0xfdcf) || (c >= 0xfdf0 && c <= 0xfffd) || \ + (c >= 0x10000 && c <= 0xeffff)) + +/* Macro to test if character is allowed to be used in an qualified identifier */ +#define is_xmlqnamechar(c) ((c >= 'a' && c <= 'z') || c == '_' || c == '-' || \ + (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || /* c == ':' || */ \ + c == '.' || c == 0xb7 || (c >= 0x370 && c <= 0x1fff && c != 0x37e ) ||\ + (c >= 0xc0 && c <= 0x2ff && c != 0xd7 && c != 0xf7) || c == 0x200c || \ + c == 0x200d || (c >= 0x300 && c <= 0x36f) || \ + (c >= 0x2070 && c <= 0x218f) || (c >= 0x203f && c <= 0x2040) || \ + (c >= 0x2c00 && c <= 0x2fef) || (c >= 0x3001 && c <= 0xd7ff) || \ + (c >= 0xf900 && c <= 0xfdcf) || (c >= 0xfdf0 && c <= 0xfffd) || \ + (c >= 0x10000 && c <= 0xeffff)) + +struct lyxml_ns { + char *prefix; /* prefix of the namespace, NULL for the default namespace */ + char *uri; /* namespace URI */ + uint32_t depth; /* depth level of the element to maintain the list of accessible namespace definitions */ +}; + +/* element tag identifier for matching opening and closing tags */ +struct lyxml_elem { + const char *prefix; /**< only pointer, not in dictionary */ + const char *name; /**< only pointer, not in dictionary */ + size_t prefix_len; + size_t name_len; +}; + +/** + * @brief Status of the parser providing information what is expected next (which function is supposed to be called). + */ +enum LYXML_PARSER_STATUS { + LYXML_ELEMENT, /* opening XML element parsed */ + LYXML_ELEM_CLOSE, /* closing XML element parsed */ + LYXML_ELEM_CONTENT, /* XML element context parsed */ + LYXML_ATTRIBUTE, /* XML attribute parsed */ + LYXML_ATTR_CONTENT, /* XML attribute content parsed */ + LYXML_END /* end of input data */ +}; + +struct lyxml_ctx { + const struct ly_ctx *ctx; + struct ly_in *in; /* input structure */ + + enum LYXML_PARSER_STATUS status; /* status providing information about the last parsed object, following attributes + are filled based on it */ + + union { + const char *prefix; /* LYXML_ELEMENT, LYXML_ATTRIBUTE - elem/attr prefix */ + const char *value; /* LYXML_ELEM_CONTENT, LYXML_ATTR_CONTENT - elem/attr value */ + }; + union { + size_t prefix_len; /* LYXML_ELEMENT, LYXML_ATTRIBUTE - elem/attr prefix length */ + size_t value_len; /* LYXML_ELEM_CONTENT, LYXML_ATTR_CONTENT - elem/attr value length */ + }; + union { + const char *name; /* LYXML_ELEMENT, LYXML_ATTRIBUTE - elem/attr name */ + ly_bool ws_only; /* LYXML_ELEM_CONTENT, LYXML_ATTR_CONTENT - whether elem/attr value is empty/white-space only */ + }; + union { + size_t name_len; /* LYXML_ELEMENT, LYXML_ATTRIBUTE - elem/attr name length */ + ly_bool dynamic; /* LYXML_ELEM_CONTENT, LYXML_ATTR_CONTENT - whether elem/attr value is dynamically allocated */ + }; + + struct ly_set elements; /* list of not-yet-closed elements */ + struct ly_set ns; /* handled with LY_SET_OPT_USEASLIST */ + + /* backup in members */ + const char *b_current; + uint64_t b_line; +}; + +/** + * @brief Create a new XML parser context and start parsing. + * + * @param[in] ctx libyang context. + * @param[in] in Input structure. + * @param[out] xmlctx New XML context with status ::LYXML_ELEMENT. + * @return LY_ERR value. + */ +LY_ERR lyxml_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyxml_ctx **xmlctx); + +/** + * @brief Move to the next XML artefact and update parser status. + * + * LYXML_ELEMENT (-> LYXML_ATTRIBUTE -> LYXML_ATTR_CONTENT)* -> LYXML_ELEM_CONTENT -> LYXML_ELEM_CLOSE ... + * -> LYXML_ELEMENT ... + * + * @param[in] xmlctx XML context to move. + * @return LY_ERR value. + */ +LY_ERR lyxml_ctx_next(struct lyxml_ctx *xmlctx); + +/** + * @brief Peek at the next XML parser status without changing it. + * + * @param[in] xmlctx XML context to use. + * @param[out] next Next XML parser status. + * @return LY_ERR value. + */ +LY_ERR lyxml_ctx_peek(struct lyxml_ctx *xmlctx, enum LYXML_PARSER_STATUS *next); + +/** + * @brief Remove all the namespaces defined in the element recently closed (removed from the xmlctx->elements). + * + * @param[in] xmlctx XML context to work with. + */ +void lyxml_ns_rm(struct lyxml_ctx *xmlctx); + +/** + * @brief Get a namespace record for the given prefix in the current context. + * + * @param[in] ns_set Set with namespaces from the XML context. + * @param[in] prefix Pointer to the namespace prefix. Can be NULL for default namespace. + * @param[in] prefix_len Length of the prefix string (since it might not be NULL-terminated). + * @return The namespace record or NULL if the record for the specified prefix not found. + */ +const struct lyxml_ns *lyxml_ns_get(const struct ly_set *ns_set, const char *prefix, size_t prefix_len); + +/** + * @brief Print the given @p text as XML string which replaces some of the characters which cannot appear in XML data. + * + * @param[in] out Output structure for printing. + * @param[in] text String to print. + * @param[in] attribute Flag for attribute's value where a double quotes must be replaced. + * @return LY_ERR values. + */ +LY_ERR lyxml_dump_text(struct ly_out *out, const char *text, ly_bool attribute); + +/** + * @brief Remove the allocated working memory of the context. + * + * @param[in] xmlctx XML context to clear. + */ +void lyxml_ctx_free(struct lyxml_ctx *xmlctx); + +/** + * @brief Create a backup of XML context. + * + * @param[in] xmlctx XML context to back up. + * @param[out] backup Backup XML context. + * @return LY_ERR value. + */ +LY_ERR lyxml_ctx_backup(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup); + +/** + * @brief Restore previous backup of XML context. + * + * @param[in,out] xmlctx XML context to restore. + * @param[in] backup Backup XML context to restore, is unusable afterwards. + */ +void lyxml_ctx_restore(struct lyxml_ctx *xmlctx, struct lyxml_ctx *backup); + +/** + * @brief Compare values and their prefix mappings. + * + * @param[in] ctx1 Libyang context for resolving prefixes in @p value1. + * @param[in] value1 First value. + * @param[in] val_prefix_data1 First value prefix data. + * @param[in] ctx2 Libyang context for resolving prefixes in @p value2. + * Can be set to NULL if @p ctx1 is equal to @p ctx2. + * @param[in] value2 Second value. + * @param[in] val_prefix_data2 Second value prefix data. + * @return LY_SUCCESS if values are equal. + * @return LY_ENOT if values are not equal. + * @return LY_ERR on error. + */ +LY_ERR lyxml_value_compare(const struct ly_ctx *ctx1, const char *value1, void *val_prefix_data1, + const struct ly_ctx *ctx2, const char *value2, void *val_prefix_data2); + +#endif /* LY_XML_H_ */ diff --git a/src/xpath.c b/src/xpath.c new file mode 100644 index 0000000..ab7921e --- /dev/null +++ b/src/xpath.c @@ -0,0 +1,9883 @@ +/** + * @file xpath.c + * @author Michal Vasko + * @brief YANG XPath evaluation functions + * + * Copyright (c) 2015 - 2022 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 /* asprintf, strdup */ + +#include "xpath.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "compat.h" +#include "context.h" +#include "dict.h" +#include "hash_table.h" +#include "out.h" +#include "parser_data.h" +#include "path.h" +#include "plugins_exts/metadata.h" +#include "plugins_types.h" +#include "printer_data.h" +#include "schema_compile_node.h" +#include "tree.h" +#include "tree_data.h" +#include "tree_data_internal.h" +#include "tree_edit.h" +#include "tree_schema_internal.h" +#include "xml.h" + +static LY_ERR set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, + enum lyxp_axis axis, uint32_t *index_p); +static LY_ERR reparse_or_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth); +static LY_ERR eval_expr_select(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_expr_type etype, + struct lyxp_set *set, uint32_t options); +static LY_ERR moveto_resolve_model(const char **qname, uint32_t *qname_len, const struct lyxp_set *set, + const struct lysc_node *ctx_scnode, const struct lys_module **moveto_mod); +static LY_ERR moveto_axis_node_next(const struct lyd_node **iter, enum lyxp_node_type *iter_type, + const struct lyd_node *node, enum lyxp_node_type node_type, enum lyxp_axis axis, struct lyxp_set *set); +static LY_ERR moveto_node(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, + enum lyxp_axis axis, uint32_t options); +static LY_ERR moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, + enum lyxp_axis axis, uint32_t options); +static LY_ERR moveto_op_comp(struct lyxp_set *set1, struct lyxp_set *set2, const char *op, ly_bool *result); + +/* Functions are divided into the following basic classes: + * + * (re)parse functions: + * Parse functions parse the expression into + * tokens (syntactic analysis). + * Reparse functions perform semantic analysis + * (do not save the result, just a check) of + * the expression and fill repeat indices. + * + * warn functions: + * Warn functions check specific reasonable conditions for schema XPath + * and print a warning if they are not satisfied. + * + * moveto functions: + * They and only they actually change the context (set). + * + * eval functions: + * They execute a parsed XPath expression on some data subtree. + */ + +/** + * @brief Print the type of an XPath \p set. + * + * @param[in] set Set to use. + * @return Set type string. + */ +static const char * +print_set_type(struct lyxp_set *set) +{ + switch (set->type) { + case LYXP_SET_NODE_SET: + return "node set"; + case LYXP_SET_SCNODE_SET: + return "schema node set"; + case LYXP_SET_BOOLEAN: + return "boolean"; + case LYXP_SET_NUMBER: + return "number"; + case LYXP_SET_STRING: + return "string"; + } + + return NULL; +} + +const char * +lyxp_token2str(enum lyxp_token tok) +{ + switch (tok) { + case LYXP_TOKEN_PAR1: + return "("; + case LYXP_TOKEN_PAR2: + return ")"; + case LYXP_TOKEN_BRACK1: + return "["; + case LYXP_TOKEN_BRACK2: + return "]"; + case LYXP_TOKEN_DOT: + return "."; + case LYXP_TOKEN_DDOT: + return ".."; + case LYXP_TOKEN_AT: + return "@"; + case LYXP_TOKEN_COMMA: + return ","; + case LYXP_TOKEN_NAMETEST: + return "NameTest"; + case LYXP_TOKEN_NODETYPE: + return "NodeType"; + case LYXP_TOKEN_VARREF: + return "VariableReference"; + case LYXP_TOKEN_FUNCNAME: + return "FunctionName"; + case LYXP_TOKEN_OPER_LOG: + return "Operator(Logic)"; + case LYXP_TOKEN_OPER_EQUAL: + return "Operator(Equal)"; + case LYXP_TOKEN_OPER_NEQUAL: + return "Operator(Non-equal)"; + case LYXP_TOKEN_OPER_COMP: + return "Operator(Comparison)"; + case LYXP_TOKEN_OPER_MATH: + return "Operator(Math)"; + case LYXP_TOKEN_OPER_UNI: + return "Operator(Union)"; + case LYXP_TOKEN_OPER_PATH: + return "Operator(Path)"; + case LYXP_TOKEN_OPER_RPATH: + return "Operator(Recursive Path)"; + case LYXP_TOKEN_LITERAL: + return "Literal"; + case LYXP_TOKEN_NUMBER: + return "Number"; + default: + LOGINT(NULL); + return ""; + } +} + +/** + * @brief Transform string into an axis. + * + * @param[in] str String to transform. + * @param[in] str_len Length of @p str. + * @return Transformed axis. + */ +static enum lyxp_axis +str2axis(const char *str, uint32_t str_len) +{ + switch (str_len) { + case 4: + assert(!strncmp("self", str, str_len)); + return LYXP_AXIS_SELF; + case 5: + assert(!strncmp("child", str, str_len)); + return LYXP_AXIS_CHILD; + case 6: + assert(!strncmp("parent", str, str_len)); + return LYXP_AXIS_PARENT; + case 8: + assert(!strncmp("ancestor", str, str_len)); + return LYXP_AXIS_ANCESTOR; + case 9: + if (str[0] == 'a') { + assert(!strncmp("attribute", str, str_len)); + return LYXP_AXIS_ATTRIBUTE; + } else if (str[0] == 'f') { + assert(!strncmp("following", str, str_len)); + return LYXP_AXIS_FOLLOWING; + } else { + assert(!strncmp("preceding", str, str_len)); + return LYXP_AXIS_PRECEDING; + } + break; + case 10: + assert(!strncmp("descendant", str, str_len)); + return LYXP_AXIS_DESCENDANT; + case 16: + assert(!strncmp("ancestor-or-self", str, str_len)); + return LYXP_AXIS_ANCESTOR_OR_SELF; + case 17: + if (str[0] == 'f') { + assert(!strncmp("following-sibling", str, str_len)); + return LYXP_AXIS_FOLLOWING_SIBLING; + } else { + assert(!strncmp("preceding-sibling", str, str_len)); + return LYXP_AXIS_PRECEDING_SIBLING; + } + break; + case 18: + assert(!strncmp("descendant-or-self", str, str_len)); + return LYXP_AXIS_DESCENDANT_OR_SELF; + } + + LOGINT(NULL); + return 0; +} + +/** + * @brief Print the whole expression \p exp to debug output. + * + * @param[in] exp Expression to use. + */ +static void +print_expr_struct_debug(const struct lyxp_expr *exp) +{ +#define MSG_BUFFER_SIZE 128 + char tmp[MSG_BUFFER_SIZE]; + uint32_t i, j; + + if (!exp || (ly_ll < LY_LLDBG)) { + return; + } + + LOGDBG(LY_LDGXPATH, "expression \"%s\":", exp->expr); + for (i = 0; i < exp->used; ++i) { + sprintf(tmp, "\ttoken %s, in expression \"%.*s\"", lyxp_token2str(exp->tokens[i]), exp->tok_len[i], + &exp->expr[exp->tok_pos[i]]); + if (exp->repeat && exp->repeat[i]) { + sprintf(tmp + strlen(tmp), " (repeat %d", exp->repeat[i][0]); + for (j = 1; exp->repeat[i][j]; ++j) { + sprintf(tmp + strlen(tmp), ", %d", exp->repeat[i][j]); + } + strcat(tmp, ")"); + } + LOGDBG(LY_LDGXPATH, tmp); + } +#undef MSG_BUFFER_SIZE +} + +#ifndef NDEBUG + +/** + * @brief Print XPath set content to debug output. + * + * @param[in] set Set to print. + */ +static void +print_set_debug(struct lyxp_set *set) +{ + uint32_t i; + char *str; + struct lyxp_set_node *item; + struct lyxp_set_scnode *sitem; + + if (ly_ll < LY_LLDBG) { + return; + } + + switch (set->type) { + case LYXP_SET_NODE_SET: + LOGDBG(LY_LDGXPATH, "set NODE SET:"); + for (i = 0; i < set->used; ++i) { + item = &set->val.nodes[i]; + + switch (item->type) { + case LYXP_NODE_NONE: + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): NONE", i + 1, item->pos); + break; + case LYXP_NODE_ROOT: + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ROOT", i + 1, item->pos); + break; + case LYXP_NODE_ROOT_CONFIG: + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ROOT CONFIG", i + 1, item->pos); + break; + case LYXP_NODE_ELEM: + if ((item->node->schema->nodetype == LYS_LIST) && (lyd_child(item->node)->schema->nodetype == LYS_LEAF)) { + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s (1st child val: %s)", i + 1, item->pos, + item->node->schema->name, lyd_get_value(lyd_child(item->node))); + } else if (item->node->schema->nodetype == LYS_LEAFLIST) { + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s (val: %s)", i + 1, item->pos, + item->node->schema->name, lyd_get_value(item->node)); + } else { + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s", i + 1, item->pos, item->node->schema->name); + } + break; + case LYXP_NODE_TEXT: + if (item->node->schema->nodetype & LYS_ANYDATA) { + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): TEXT <%s>", i + 1, item->pos, + item->node->schema->nodetype == LYS_ANYXML ? "anyxml" : "anydata"); + } else { + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): TEXT %s", i + 1, item->pos, lyd_get_value(item->node)); + } + break; + case LYXP_NODE_META: + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): META %s = %s", i + 1, item->pos, set->val.meta[i].meta->name, + set->val.meta[i].meta->value); + break; + } + } + break; + + case LYXP_SET_SCNODE_SET: + LOGDBG(LY_LDGXPATH, "set SCNODE SET:"); + for (i = 0; i < set->used; ++i) { + sitem = &set->val.scnodes[i]; + + switch (sitem->type) { + case LYXP_NODE_ROOT: + LOGDBG(LY_LDGXPATH, "\t%d (%u): ROOT", i + 1, sitem->in_ctx); + break; + case LYXP_NODE_ROOT_CONFIG: + LOGDBG(LY_LDGXPATH, "\t%d (%u): ROOT CONFIG", i + 1, sitem->in_ctx); + break; + case LYXP_NODE_ELEM: + LOGDBG(LY_LDGXPATH, "\t%d (%u): ELEM %s", i + 1, sitem->in_ctx, sitem->scnode->name); + break; + default: + LOGINT(NULL); + break; + } + } + break; + + case LYXP_SET_BOOLEAN: + LOGDBG(LY_LDGXPATH, "set BOOLEAN"); + LOGDBG(LY_LDGXPATH, "\t%s", (set->val.bln ? "true" : "false")); + break; + + case LYXP_SET_STRING: + LOGDBG(LY_LDGXPATH, "set STRING"); + LOGDBG(LY_LDGXPATH, "\t%s", set->val.str); + break; + + case LYXP_SET_NUMBER: + LOGDBG(LY_LDGXPATH, "set NUMBER"); + + if (isnan(set->val.num)) { + str = strdup("NaN"); + } else if ((set->val.num == 0) || (set->val.num == -0.0f)) { + str = strdup("0"); + } else if (isinf(set->val.num) && !signbit(set->val.num)) { + str = strdup("Infinity"); + } else if (isinf(set->val.num) && signbit(set->val.num)) { + str = strdup("-Infinity"); + } else if ((long long)set->val.num == set->val.num) { + if (asprintf(&str, "%lld", (long long)set->val.num) == -1) { + str = NULL; + } + } else { + if (asprintf(&str, "%03.1Lf", set->val.num) == -1) { + str = NULL; + } + } + LY_CHECK_ERR_RET(!str, LOGMEM(NULL), ); + + LOGDBG(LY_LDGXPATH, "\t%s", str); + free(str); + } +} + +#endif + +/** + * @brief Realloc the string \p str. + * + * @param[in] ctx libyang context for logging. + * @param[in] needed How much free space is required. + * @param[in,out] str Pointer to the string to use. + * @param[in,out] used Used bytes in \p str. + * @param[in,out] size Allocated bytes in \p str. + * @return LY_ERR + */ +static LY_ERR +cast_string_realloc(const struct ly_ctx *ctx, uint64_t needed, char **str, uint32_t *used, uint32_t *size) +{ + if (*size - (unsigned)*used < needed) { + do { + if ((UINT32_MAX - *size) < LYXP_STRING_CAST_SIZE_STEP) { + LOGERR(ctx, LY_EINVAL, "XPath string length limit (%" PRIu32 ") reached.", UINT32_MAX); + return LY_EINVAL; + } + *size += LYXP_STRING_CAST_SIZE_STEP; + } while (*size - (unsigned)*used < needed); + *str = ly_realloc(*str, *size * sizeof(char)); + LY_CHECK_ERR_RET(!(*str), LOGMEM(ctx), LY_EMEM); + } + + return LY_SUCCESS; +} + +/** + * @brief Cast nodes recursively to one string @p str. + * + * @param[in] node Node to cast, NULL if root. + * @param[in] set XPath set. + * @param[in] indent Current indent. + * @param[in,out] str Resulting string. + * @param[in,out] used Used bytes in @p str. + * @param[in,out] size Allocated bytes in @p str. + * @return LY_ERR value. + */ +static LY_ERR +cast_string_recursive(const struct lyd_node *node, struct lyxp_set *set, uint32_t indent, char **str, uint32_t *used, + uint32_t *size) +{ + char *buf, *line, *ptr = NULL; + const char *value_str; + const struct lyd_node *child; + enum lyxp_node_type child_type; + struct lyd_node *tree; + struct lyd_node_any *any; + LY_ERR rc; + + if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && node && (node->schema->flags & LYS_CONFIG_R)) { + return LY_SUCCESS; + } + + if (!node) { + /* fake container */ + LY_CHECK_RET(cast_string_realloc(set->ctx, 1, str, used, size)); + strcpy(*str + (*used - 1), "\n"); + ++(*used); + + ++indent; + + /* print all the top-level nodes */ + child = NULL; + child_type = 0; + while (!moveto_axis_node_next(&child, &child_type, NULL, set->root_type, LYXP_AXIS_CHILD, set)) { + LY_CHECK_RET(cast_string_recursive(child, set, indent, str, used, size)); + } + + /* end fake container */ + LY_CHECK_RET(cast_string_realloc(set->ctx, 1, str, used, size)); + strcpy(*str + (*used - 1), "\n"); + ++(*used); + + --indent; + } else { + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + case LYS_RPC: + case LYS_NOTIF: + LY_CHECK_RET(cast_string_realloc(set->ctx, 1, str, used, size)); + strcpy(*str + (*used - 1), "\n"); + ++(*used); + + for (child = lyd_child(node); child; child = child->next) { + LY_CHECK_RET(cast_string_recursive(child, set, indent + 1, str, used, size)); + } + + break; + + case LYS_LEAF: + case LYS_LEAFLIST: + value_str = lyd_get_value(node); + + /* print indent */ + LY_CHECK_RET(cast_string_realloc(set->ctx, indent * 2 + strlen(value_str) + 1, str, used, size)); + memset(*str + (*used - 1), ' ', indent * 2); + *used += indent * 2; + + /* print value */ + if (*used == 1) { + sprintf(*str + (*used - 1), "%s", value_str); + *used += strlen(value_str); + } else { + sprintf(*str + (*used - 1), "%s\n", value_str); + *used += strlen(value_str) + 1; + } + + break; + + case LYS_ANYXML: + case LYS_ANYDATA: + any = (struct lyd_node_any *)node; + if (!(void *)any->value.tree) { + /* no content */ + buf = strdup(""); + LY_CHECK_ERR_RET(!buf, LOGMEM(set->ctx), LY_EMEM); + } else { + struct ly_out *out; + + if (any->value_type == LYD_ANYDATA_LYB) { + /* try to parse it into a data tree */ + if (lyd_parse_data_mem((struct ly_ctx *)set->ctx, any->value.mem, LYD_LYB, + LYD_PARSE_ONLY | LYD_PARSE_STRICT, 0, &tree) == LY_SUCCESS) { + /* successfully parsed */ + free(any->value.mem); + any->value.tree = tree; + any->value_type = LYD_ANYDATA_DATATREE; + } + /* error is covered by the following switch where LYD_ANYDATA_LYB causes failure */ + } + + switch (any->value_type) { + case LYD_ANYDATA_STRING: + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + buf = strdup(any->value.json); + LY_CHECK_ERR_RET(!buf, LOGMEM(set->ctx), LY_EMEM); + break; + case LYD_ANYDATA_DATATREE: + LY_CHECK_RET(ly_out_new_memory(&buf, 0, &out)); + rc = lyd_print_all(out, any->value.tree, LYD_XML, 0); + ly_out_free(out, NULL, 0); + LY_CHECK_RET(rc < 0, -rc); + break; + case LYD_ANYDATA_LYB: + LOGERR(set->ctx, LY_EINVAL, "Cannot convert LYB anydata into string."); + return LY_EINVAL; + } + } + + line = strtok_r(buf, "\n", &ptr); + do { + rc = cast_string_realloc(set->ctx, indent * 2 + strlen(line) + 1, str, used, size); + if (rc != LY_SUCCESS) { + free(buf); + return rc; + } + memset(*str + (*used - 1), ' ', indent * 2); + *used += indent * 2; + + strcpy(*str + (*used - 1), line); + *used += strlen(line); + + strcpy(*str + (*used - 1), "\n"); + *used += 1; + } while ((line = strtok_r(NULL, "\n", &ptr))); + + free(buf); + break; + + default: + LOGINT_RET(set->ctx); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Cast an element into a string. + * + * @param[in] node Node to cast, NULL if root. + * @param[in] set XPath set. + * @param[out] str Element cast to dynamically-allocated string. + * @return LY_ERR + */ +static LY_ERR +cast_string_elem(const struct lyd_node *node, struct lyxp_set *set, char **str) +{ + uint32_t used, size; + LY_ERR rc; + + *str = malloc(LYXP_STRING_CAST_SIZE_START * sizeof(char)); + LY_CHECK_ERR_RET(!*str, LOGMEM(set->ctx), LY_EMEM); + (*str)[0] = '\0'; + used = 1; + size = LYXP_STRING_CAST_SIZE_START; + + rc = cast_string_recursive(node, set, 0, str, &used, &size); + if (rc != LY_SUCCESS) { + free(*str); + return rc; + } + + if (size > used) { + *str = ly_realloc(*str, used * sizeof(char)); + LY_CHECK_ERR_RET(!*str, LOGMEM(set->ctx), LY_EMEM); + } + return LY_SUCCESS; +} + +/** + * @brief Cast a LYXP_SET_NODE_SET set into a string. + * Context position aware. + * + * @param[in] set Set to cast. + * @param[out] str Cast dynamically-allocated string. + * @return LY_ERR + */ +static LY_ERR +cast_node_set_to_string(struct lyxp_set *set, char **str) +{ + if (!set->used) { + *str = strdup(""); + if (!*str) { + LOGMEM_RET(set->ctx); + } + return LY_SUCCESS; + } + + switch (set->val.nodes[0].type) { + case LYXP_NODE_NONE: + /* invalid */ + LOGINT_RET(set->ctx); + case LYXP_NODE_ROOT: + case LYXP_NODE_ROOT_CONFIG: + case LYXP_NODE_ELEM: + case LYXP_NODE_TEXT: + return cast_string_elem(set->val.nodes[0].node, set, str); + case LYXP_NODE_META: + *str = strdup(lyd_get_meta_value(set->val.meta[0].meta)); + if (!*str) { + LOGMEM_RET(set->ctx); + } + return LY_SUCCESS; + } + + LOGINT_RET(set->ctx); +} + +/** + * @brief Cast a string into an XPath number. + * + * @param[in] str String to use. + * @return Cast number. + */ +static long double +cast_string_to_number(const char *str) +{ + long double num; + char *ptr; + + errno = 0; + num = strtold(str, &ptr); + if (errno || *ptr) { + num = NAN; + } + return num; +} + +/** + * @brief Callback for checking value equality. + * + * Implementation of ::lyht_value_equal_cb. + * + * @param[in] val1_p First value. + * @param[in] val2_p Second value. + * @param[in] mod Whether hash table is being modified. + * @param[in] cb_data Callback data. + * @return Boolean value whether values are equal or not. + */ +static ly_bool +set_values_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + struct lyxp_set_hash_node *val1, *val2; + + val1 = (struct lyxp_set_hash_node *)val1_p; + val2 = (struct lyxp_set_hash_node *)val2_p; + + if ((val1->node == val2->node) && (val1->type == val2->type)) { + return 1; + } + + return 0; +} + +/** + * @brief Insert node and its hash into set. + * + * @param[in] set et to insert to. + * @param[in] node Node with hash. + * @param[in] type Node type. + */ +static void +set_insert_node_hash(struct lyxp_set *set, struct lyd_node *node, enum lyxp_node_type type) +{ + LY_ERR r; + uint32_t i, hash; + struct lyxp_set_hash_node hnode; + + if (!set->ht && (set->used >= LYD_HT_MIN_ITEMS)) { + /* create hash table and add all the nodes */ + set->ht = lyht_new(1, sizeof(struct lyxp_set_hash_node), set_values_equal_cb, NULL, 1); + for (i = 0; i < set->used; ++i) { + hnode.node = set->val.nodes[i].node; + hnode.type = set->val.nodes[i].type; + + hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = dict_hash_multi(hash, NULL, 0); + + r = lyht_insert(set->ht, &hnode, hash, NULL); + assert(!r); + (void)r; + + if (hnode.node == node) { + /* it was just added, do not add it twice */ + node = NULL; + } + } + } + + if (set->ht && node) { + /* add the new node into hash table */ + hnode.node = node; + hnode.type = type; + + hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = dict_hash_multi(hash, NULL, 0); + + r = lyht_insert(set->ht, &hnode, hash, NULL); + assert(!r); + (void)r; + } +} + +/** + * @brief Remove node and its hash from set. + * + * @param[in] set Set to remove from. + * @param[in] node Node to remove. + * @param[in] type Node type. + */ +static void +set_remove_node_hash(struct lyxp_set *set, struct lyd_node *node, enum lyxp_node_type type) +{ + LY_ERR r; + struct lyxp_set_hash_node hnode; + uint32_t hash; + + if (set->ht) { + hnode.node = node; + hnode.type = type; + + hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = dict_hash_multi(hash, NULL, 0); + + r = lyht_remove(set->ht, &hnode, hash); + assert(!r); + (void)r; + + if (!set->ht->used) { + lyht_free(set->ht); + set->ht = NULL; + } + } +} + +/** + * @brief Check whether node is in set based on its hash. + * + * @param[in] set Set to search in. + * @param[in] node Node to search for. + * @param[in] type Node type. + * @param[in] skip_idx Index in @p set to skip. + * @return LY_ERR + */ +static LY_ERR +set_dup_node_hash_check(const struct lyxp_set *set, struct lyd_node *node, enum lyxp_node_type type, int skip_idx) +{ + struct lyxp_set_hash_node hnode, *match_p; + uint32_t hash; + + hnode.node = node; + hnode.type = type; + + hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = dict_hash_multi(hash, NULL, 0); + + if (!lyht_find(set->ht, &hnode, hash, (void **)&match_p)) { + if ((skip_idx > -1) && (set->val.nodes[skip_idx].node == match_p->node) && (set->val.nodes[skip_idx].type == match_p->type)) { + /* we found it on the index that should be skipped, find another */ + hnode = *match_p; + if (lyht_find_next(set->ht, &hnode, hash, (void **)&match_p)) { + /* none other found */ + return LY_SUCCESS; + } + } + + return LY_EEXIST; + } + + /* not found */ + return LY_SUCCESS; +} + +void +lyxp_set_free_content(struct lyxp_set *set) +{ + if (!set) { + return; + } + + if (set->type == LYXP_SET_NODE_SET) { + free(set->val.nodes); + lyht_free(set->ht); + } else if (set->type == LYXP_SET_SCNODE_SET) { + free(set->val.scnodes); + lyht_free(set->ht); + } else { + if (set->type == LYXP_SET_STRING) { + free(set->val.str); + } + set->type = LYXP_SET_NODE_SET; + } + + set->val.nodes = NULL; + set->used = 0; + set->size = 0; + set->ht = NULL; + set->ctx_pos = 0; + set->ctx_size = 0; +} + +/** + * @brief Free dynamically-allocated set. + * + * @param[in] set Set to free. + */ +static void +lyxp_set_free(struct lyxp_set *set) +{ + if (!set) { + return; + } + + lyxp_set_free_content(set); + free(set); +} + +/** + * @brief Initialize set context. + * + * @param[in] new Set to initialize. + * @param[in] set Arbitrary initialized set. + */ +static void +set_init(struct lyxp_set *new, const struct lyxp_set *set) +{ + memset(new, 0, sizeof *new); + if (set) { + new->non_child_axis = set->non_child_axis; + new->ctx = set->ctx; + new->cur_node = set->cur_node; + new->root_type = set->root_type; + new->context_op = set->context_op; + new->tree = set->tree; + new->cur_mod = set->cur_mod; + new->format = set->format; + new->prefix_data = set->prefix_data; + new->vars = set->vars; + } +} + +/** + * @brief Create a deep copy of a set. + * + * @param[in] set Set to copy. + * @return Copy of @p set. + */ +static struct lyxp_set * +set_copy(struct lyxp_set *set) +{ + struct lyxp_set *ret; + uint32_t i; + + if (!set) { + return NULL; + } + + ret = malloc(sizeof *ret); + LY_CHECK_ERR_RET(!ret, LOGMEM(set->ctx), NULL); + set_init(ret, set); + + if (set->type == LYXP_SET_SCNODE_SET) { + ret->type = set->type; + + for (i = 0; i < set->used; ++i) { + if ((set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) || + (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START)) { + uint32_t idx; + + LY_CHECK_ERR_RET(set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type, + set->val.scnodes[i].axis, &idx), lyxp_set_free(ret), NULL); + /* coverity seems to think scnodes can be NULL */ + if (!ret->val.scnodes) { + lyxp_set_free(ret); + return NULL; + } + ret->val.scnodes[idx].in_ctx = set->val.scnodes[i].in_ctx; + } + } + } else if (set->type == LYXP_SET_NODE_SET) { + ret->type = set->type; + if (set->used) { + ret->val.nodes = malloc(set->used * sizeof *ret->val.nodes); + LY_CHECK_ERR_RET(!ret->val.nodes, LOGMEM(set->ctx); free(ret), NULL); + memcpy(ret->val.nodes, set->val.nodes, set->used * sizeof *ret->val.nodes); + } else { + ret->val.nodes = NULL; + } + + ret->used = ret->size = set->used; + ret->ctx_pos = set->ctx_pos; + ret->ctx_size = set->ctx_size; + if (set->ht) { + ret->ht = lyht_dup(set->ht); + } + } else { + memcpy(ret, set, sizeof *ret); + if (set->type == LYXP_SET_STRING) { + ret->val.str = strdup(set->val.str); + LY_CHECK_ERR_RET(!ret->val.str, LOGMEM(set->ctx); free(ret), NULL); + } + } + + return ret; +} + +/** + * @brief Fill XPath set with a string. Any current data are disposed of. + * + * @param[in] set Set to fill. + * @param[in] string String to fill into \p set. + * @param[in] str_len Length of \p string. 0 is a valid value! + */ +static void +set_fill_string(struct lyxp_set *set, const char *string, uint32_t str_len) +{ + lyxp_set_free_content(set); + + set->type = LYXP_SET_STRING; + if ((str_len == 0) && (string[0] != '\0')) { + string = ""; + } + set->val.str = strndup(string, str_len); +} + +/** + * @brief Fill XPath set with a number. Any current data are disposed of. + * + * @param[in] set Set to fill. + * @param[in] number Number to fill into \p set. + */ +static void +set_fill_number(struct lyxp_set *set, long double number) +{ + lyxp_set_free_content(set); + + set->type = LYXP_SET_NUMBER; + set->val.num = number; +} + +/** + * @brief Fill XPath set with a boolean. Any current data are disposed of. + * + * @param[in] set Set to fill. + * @param[in] boolean Boolean to fill into \p set. + */ +static void +set_fill_boolean(struct lyxp_set *set, ly_bool boolean) +{ + lyxp_set_free_content(set); + + set->type = LYXP_SET_BOOLEAN; + set->val.bln = boolean; +} + +/** + * @brief Fill XPath set with the value from another set (deep assign). + * Any current data are disposed of. + * + * @param[in] trg Set to fill. + * @param[in] src Source set to copy into \p trg. + */ +static void +set_fill_set(struct lyxp_set *trg, const struct lyxp_set *src) +{ + if (!trg || !src) { + return; + } + + if (trg->type == LYXP_SET_NODE_SET) { + free(trg->val.nodes); + } else if (trg->type == LYXP_SET_STRING) { + free(trg->val.str); + } + set_init(trg, src); + + if (src->type == LYXP_SET_SCNODE_SET) { + trg->type = LYXP_SET_SCNODE_SET; + trg->used = src->used; + trg->size = src->used; + + if (trg->size) { + trg->val.scnodes = ly_realloc(trg->val.scnodes, trg->size * sizeof *trg->val.scnodes); + LY_CHECK_ERR_RET(!trg->val.scnodes, LOGMEM(src->ctx); memset(trg, 0, sizeof *trg), ); + memcpy(trg->val.scnodes, src->val.scnodes, src->used * sizeof *src->val.scnodes); + } else { + trg->val.scnodes = NULL; + } + } else if (src->type == LYXP_SET_BOOLEAN) { + set_fill_boolean(trg, src->val.bln); + } else if (src->type == LYXP_SET_NUMBER) { + set_fill_number(trg, src->val.num); + } else if (src->type == LYXP_SET_STRING) { + set_fill_string(trg, src->val.str, strlen(src->val.str)); + } else { + if (trg->type == LYXP_SET_NODE_SET) { + free(trg->val.nodes); + } else if (trg->type == LYXP_SET_STRING) { + free(trg->val.str); + } + + assert(src->type == LYXP_SET_NODE_SET); + + trg->type = LYXP_SET_NODE_SET; + trg->used = src->used; + trg->size = src->used; + trg->ctx_pos = src->ctx_pos; + trg->ctx_size = src->ctx_size; + + if (trg->size) { + trg->val.nodes = malloc(trg->size * sizeof *trg->val.nodes); + LY_CHECK_ERR_RET(!trg->val.nodes, LOGMEM(src->ctx); memset(trg, 0, sizeof *trg), ); + memcpy(trg->val.nodes, src->val.nodes, src->used * sizeof *src->val.nodes); + } else { + trg->val.nodes = NULL; + } + if (src->ht) { + trg->ht = lyht_dup(src->ht); + } else { + trg->ht = NULL; + } + } +} + +/** + * @brief Clear context of all schema nodes. + * + * @param[in] set Set to clear. + * @param[in] new_ctx New context state for all the nodes currently in the context. + */ +static void +set_scnode_clear_ctx(struct lyxp_set *set, int32_t new_ctx) +{ + uint32_t i; + + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + set->val.scnodes[i].in_ctx = new_ctx; + } else if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START) { + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_START_USED; + } + } +} + +/** + * @brief Remove a node from a set. Removing last node changes + * set into LYXP_SET_EMPTY. Context position aware. + * + * @param[in] set Set to use. + * @param[in] idx Index from @p set of the node to be removed. + */ +static void +set_remove_node(struct lyxp_set *set, uint32_t idx) +{ + assert(set && (set->type == LYXP_SET_NODE_SET)); + assert(idx < set->used); + + set_remove_node_hash(set, set->val.nodes[idx].node, set->val.nodes[idx].type); + + --set->used; + if (idx < set->used) { + memmove(&set->val.nodes[idx], &set->val.nodes[idx + 1], (set->used - idx) * sizeof *set->val.nodes); + } else if (!set->used) { + lyxp_set_free_content(set); + } +} + +/** + * @brief Remove a node from a set by setting its type to LYXP_NODE_NONE. + * + * @param[in] set Set to use. + * @param[in] idx Index from @p set of the node to be removed. + */ +static void +set_remove_node_none(struct lyxp_set *set, uint32_t idx) +{ + assert(set && (set->type == LYXP_SET_NODE_SET)); + assert(idx < set->used); + + if (set->val.nodes[idx].type == LYXP_NODE_ELEM) { + set_remove_node_hash(set, set->val.nodes[idx].node, set->val.nodes[idx].type); + } + set->val.nodes[idx].type = LYXP_NODE_NONE; +} + +/** + * @brief Remove all LYXP_NODE_NONE nodes from a set. Removing last node changes + * set into LYXP_SET_EMPTY. Context position aware. + * + * @param[in] set Set to consolidate. + */ +static void +set_remove_nodes_none(struct lyxp_set *set) +{ + uint32_t i, orig_used, end = 0; + int64_t start; + + assert(set); + + orig_used = set->used; + set->used = 0; + for (i = 0; i < orig_used; ) { + start = -1; + do { + if ((set->val.nodes[i].type != LYXP_NODE_NONE) && (start == -1)) { + start = i; + } else if ((start > -1) && (set->val.nodes[i].type == LYXP_NODE_NONE)) { + end = i; + ++i; + break; + } + + ++i; + if (i == orig_used) { + end = i; + } + } while (i < orig_used); + + if (start > -1) { + /* move the whole chunk of valid nodes together */ + if (set->used != (unsigned)start) { + memmove(&set->val.nodes[set->used], &set->val.nodes[start], (end - start) * sizeof *set->val.nodes); + } + set->used += end - start; + } + } +} + +/** + * @brief Check for duplicates in a node set. + * + * @param[in] set Set to check. + * @param[in] node Node to look for in @p set. + * @param[in] node_type Type of @p node. + * @param[in] skip_idx Index from @p set to skip. + * @return LY_ERR + */ +static LY_ERR +set_dup_node_check(const struct lyxp_set *set, const struct lyd_node *node, enum lyxp_node_type node_type, int skip_idx) +{ + uint32_t i; + + if (set->ht && node) { + return set_dup_node_hash_check(set, (struct lyd_node *)node, node_type, skip_idx); + } + + for (i = 0; i < set->used; ++i) { + if ((skip_idx > -1) && (i == (unsigned)skip_idx)) { + continue; + } + + if ((set->val.nodes[i].node == node) && (set->val.nodes[i].type == node_type)) { + return LY_EEXIST; + } + } + + return LY_SUCCESS; +} + +ly_bool +lyxp_set_scnode_contains(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, int skip_idx, + uint32_t *index_p) +{ + uint32_t i; + + for (i = 0; i < set->used; ++i) { + if ((skip_idx > -1) && (i == (unsigned)skip_idx)) { + continue; + } + + if ((set->val.scnodes[i].scnode == node) && (set->val.scnodes[i].type == node_type)) { + if (index_p) { + *index_p = i; + } + return 1; + } + } + + return 0; +} + +void +lyxp_set_scnode_merge(struct lyxp_set *set1, struct lyxp_set *set2) +{ + uint32_t orig_used, i, j; + + assert((set1->type == LYXP_SET_SCNODE_SET) && (set2->type == LYXP_SET_SCNODE_SET)); + + if (!set2->used) { + return; + } + + if (!set1->used) { + /* release hidden allocated data (lyxp_set.size) */ + lyxp_set_free_content(set1); + /* direct copying of the entire structure */ + memcpy(set1, set2, sizeof *set1); + return; + } + + if (set1->used + set2->used > set1->size) { + set1->size = set1->used + set2->used; + set1->val.scnodes = ly_realloc(set1->val.scnodes, set1->size * sizeof *set1->val.scnodes); + LY_CHECK_ERR_RET(!set1->val.scnodes, LOGMEM(set1->ctx), ); + } + + orig_used = set1->used; + + for (i = 0; i < set2->used; ++i) { + for (j = 0; j < orig_used; ++j) { + /* detect duplicities */ + if (set1->val.scnodes[j].scnode == set2->val.scnodes[i].scnode) { + break; + } + } + + if (j < orig_used) { + /* node is there, but update its status if needed */ + if (set1->val.scnodes[j].in_ctx == LYXP_SET_SCNODE_START_USED) { + set1->val.scnodes[j].in_ctx = set2->val.scnodes[i].in_ctx; + } else if ((set1->val.scnodes[j].in_ctx == LYXP_SET_SCNODE_ATOM_NODE) && + (set2->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) { + set1->val.scnodes[j].in_ctx = set2->val.scnodes[i].in_ctx; + } + } else { + memcpy(&set1->val.scnodes[set1->used], &set2->val.scnodes[i], sizeof *set2->val.scnodes); + ++set1->used; + } + } + + lyxp_set_free_content(set2); + set2->type = LYXP_SET_SCNODE_SET; +} + +/** + * @brief Insert a node into a set. Context position aware. + * + * @param[in] set Set to use. + * @param[in] node Node to insert to @p set. + * @param[in] pos Sort position of @p node. If left 0, it is filled just before sorting. + * @param[in] node_type Node type of @p node. + * @param[in] idx Index in @p set to insert into. + */ +static void +set_insert_node(struct lyxp_set *set, const struct lyd_node *node, uint32_t pos, enum lyxp_node_type node_type, uint32_t idx) +{ + assert(set && (set->type == LYXP_SET_NODE_SET)); + + if (!set->size) { + /* first item */ + if (idx) { + /* no real harm done, but it is a bug */ + LOGINT(set->ctx); + idx = 0; + } + set->val.nodes = malloc(LYXP_SET_SIZE_START * sizeof *set->val.nodes); + LY_CHECK_ERR_RET(!set->val.nodes, LOGMEM(set->ctx), ); + set->type = LYXP_SET_NODE_SET; + set->used = 0; + set->size = LYXP_SET_SIZE_START; + set->ctx_pos = 1; + set->ctx_size = 1; + set->ht = NULL; + } else { + /* not an empty set */ + if (set->used == set->size) { + + /* set is full */ + set->val.nodes = ly_realloc(set->val.nodes, (set->size * LYXP_SET_SIZE_MUL_STEP) * sizeof *set->val.nodes); + LY_CHECK_ERR_RET(!set->val.nodes, LOGMEM(set->ctx), ); + set->size *= LYXP_SET_SIZE_MUL_STEP; + } + + if (idx > set->used) { + LOGINT(set->ctx); + idx = set->used; + } + + /* make space for the new node */ + if (idx < set->used) { + memmove(&set->val.nodes[idx + 1], &set->val.nodes[idx], (set->used - idx) * sizeof *set->val.nodes); + } + } + + /* finally assign the value */ + set->val.nodes[idx].node = (struct lyd_node *)node; + set->val.nodes[idx].type = node_type; + set->val.nodes[idx].pos = pos; + ++set->used; + + /* add into hash table */ + set_insert_node_hash(set, (struct lyd_node *)node, node_type); +} + +/** + * @brief Insert schema node into set. + * + * @param[in] set Set to insert into. + * @param[in] node Node to insert. + * @param[in] node_type Node type of @p node. + * @param[in] axis Axis that @p node was reached on. + * @param[out] index_p Optional pointer to store index if the inserted @p node. + * @return LY_SUCCESS on success. + * @return LY_EMEM on memory allocation failure. + */ +static LY_ERR +set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, + enum lyxp_axis axis, uint32_t *index_p) +{ + uint32_t index; + + assert(set->type == LYXP_SET_SCNODE_SET); + + if (!set->size) { + /* first item */ + set->val.scnodes = malloc(LYXP_SET_SIZE_START * sizeof *set->val.scnodes); + LY_CHECK_ERR_RET(!set->val.scnodes, LOGMEM(set->ctx), LY_EMEM); + set->type = LYXP_SET_SCNODE_SET; + set->used = 0; + set->size = LYXP_SET_SIZE_START; + set->ctx_pos = 1; + set->ctx_size = 1; + set->ht = NULL; + } + + if (lyxp_set_scnode_contains(set, node, node_type, -1, &index)) { + /* BUG if axes differs, this new one is thrown away */ + set->val.scnodes[index].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + } else { + if (set->used == set->size) { + set->val.scnodes = ly_realloc(set->val.scnodes, (set->size * LYXP_SET_SIZE_MUL_STEP) * sizeof *set->val.scnodes); + LY_CHECK_ERR_RET(!set->val.scnodes, LOGMEM(set->ctx), LY_EMEM); + set->size *= LYXP_SET_SIZE_MUL_STEP; + } + + index = set->used; + set->val.scnodes[index].scnode = (struct lysc_node *)node; + set->val.scnodes[index].type = node_type; + set->val.scnodes[index].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + set->val.scnodes[index].axis = axis; + ++set->used; + } + + if (index_p) { + *index_p = index; + } + + return LY_SUCCESS; +} + +/** + * @brief Set all nodes with ctx 1 to a new unique context value. + * + * @param[in] set Set to modify. + * @return New context value. + */ +static int32_t +set_scnode_new_in_ctx(struct lyxp_set *set) +{ + uint32_t i; + int32_t ret_ctx; + + assert(set->type == LYXP_SET_SCNODE_SET); + + ret_ctx = LYXP_SET_SCNODE_ATOM_PRED_CTX; +retry: + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx >= ret_ctx) { + ret_ctx = set->val.scnodes[i].in_ctx + 1; + goto retry; + } + } + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + set->val.scnodes[i].in_ctx = ret_ctx; + } + } + + return ret_ctx; +} + +/** + * @brief Get unique @p node position in the data. + * + * @param[in] node Node to find. + * @param[in] node_type Node type of @p node. + * @param[in] root Root node. + * @param[in] root_type Type of the XPath @p root node. + * @param[in] prev Node that we think is before @p node in DFS from @p root. Can optionally + * be used to increase efficiency and start the DFS from this node. + * @param[in] prev_pos Node @p prev position. Optional, but must be set if @p prev is set. + * @return Node position. + */ +static uint32_t +get_node_pos(const struct lyd_node *node, enum lyxp_node_type node_type, const struct lyd_node *root, + enum lyxp_node_type root_type, const struct lyd_node **prev, uint32_t *prev_pos) +{ + const struct lyd_node *elem = NULL, *top_sibling; + uint32_t pos = 1; + ly_bool found = 0; + + assert(prev && prev_pos && !root->prev->next); + + if ((node_type == LYXP_NODE_ROOT) || (node_type == LYXP_NODE_ROOT_CONFIG)) { + return 0; + } + + if (*prev) { + /* start from the previous element instead from the root */ + pos = *prev_pos; + for (top_sibling = *prev; top_sibling->parent; top_sibling = lyd_parent(top_sibling)) {} + goto dfs_search; + } + + LY_LIST_FOR(root, top_sibling) { + LYD_TREE_DFS_BEGIN(top_sibling, elem) { +dfs_search: + LYD_TREE_DFS_continue = 0; + + if (*prev && !elem) { + /* resume previous DFS */ + elem = LYD_TREE_DFS_next = (struct lyd_node *)*prev; + LYD_TREE_DFS_continue = 0; + } + + if (!elem->schema || ((root_type == LYXP_NODE_ROOT_CONFIG) && (elem->schema->flags & LYS_CONFIG_R))) { + /* skip */ + LYD_TREE_DFS_continue = 1; + } else { + if (elem == node) { + found = 1; + break; + } + ++pos; + } + + LYD_TREE_DFS_END(top_sibling, elem); + } + + /* node found */ + if (found) { + break; + } + } + + if (!found) { + if (!(*prev)) { + /* we went from root and failed to find it, cannot be */ + LOGINT(LYD_CTX(node)); + return 0; + } else { + /* start the search again from the beginning */ + *prev = root; + + top_sibling = root; + pos = 1; + goto dfs_search; + } + } + + /* remember the last found node for next time */ + *prev = node; + *prev_pos = pos; + + return pos; +} + +/** + * @brief Assign (fill) missing node positions. + * + * @param[in] set Set to fill positions in. + * @param[in] root Context root node. + * @param[in] root_type Context root type. + * @return LY_ERR + */ +static LY_ERR +set_assign_pos(struct lyxp_set *set, const struct lyd_node *root, enum lyxp_node_type root_type) +{ + const struct lyd_node *prev = NULL, *tmp_node; + uint32_t i, tmp_pos = 0; + + for (i = 0; i < set->used; ++i) { + if (!set->val.nodes[i].pos) { + tmp_node = NULL; + switch (set->val.nodes[i].type) { + case LYXP_NODE_META: + tmp_node = set->val.meta[i].meta->parent; + if (!tmp_node) { + LOGINT_RET(root->schema->module->ctx); + } + /* fall through */ + case LYXP_NODE_ELEM: + case LYXP_NODE_TEXT: + if (!tmp_node) { + tmp_node = set->val.nodes[i].node; + } + set->val.nodes[i].pos = get_node_pos(tmp_node, set->val.nodes[i].type, root, root_type, &prev, &tmp_pos); + break; + default: + /* all roots have position 0 */ + break; + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Get unique @p meta position in the parent metadata. + * + * @param[in] meta Metadata to use. + * @return Metadata position. + */ +static uint32_t +get_meta_pos(struct lyd_meta *meta) +{ + uint32_t pos = 0; + struct lyd_meta *meta2; + + for (meta2 = meta->parent->meta; meta2 && (meta2 != meta); meta2 = meta2->next) { + ++pos; + } + + assert(meta2); + return pos; +} + +/** + * @brief Compare 2 nodes in respect to XPath document order. + * + * @param[in] item1 1st node. + * @param[in] item2 2nd node. + * @return If 1st > 2nd returns 1, 1st == 2nd returns 0, and 1st < 2nd returns -1. + */ +static int +set_sort_compare(struct lyxp_set_node *item1, struct lyxp_set_node *item2) +{ + uint32_t meta_pos1 = 0, meta_pos2 = 0; + + if (item1->pos < item2->pos) { + return -1; + } + + if (item1->pos > item2->pos) { + return 1; + } + + /* node positions are equal, the fun case */ + + /* 1st ELEM - == - 2nd TEXT, 1st TEXT - == - 2nd ELEM */ + /* special case since text nodes are actually saved as their parents */ + if ((item1->node == item2->node) && (item1->type != item2->type)) { + if (item1->type == LYXP_NODE_ELEM) { + assert(item2->type == LYXP_NODE_TEXT); + return -1; + } else { + assert((item1->type == LYXP_NODE_TEXT) && (item2->type == LYXP_NODE_ELEM)); + return 1; + } + } + + /* we need meta positions now */ + if (item1->type == LYXP_NODE_META) { + meta_pos1 = get_meta_pos((struct lyd_meta *)item1->node); + } + if (item2->type == LYXP_NODE_META) { + meta_pos2 = get_meta_pos((struct lyd_meta *)item2->node); + } + + /* 1st ROOT - 2nd ROOT, 1st ELEM - 2nd ELEM, 1st TEXT - 2nd TEXT, 1st META - =pos= - 2nd META */ + /* check for duplicates */ + if (item1->node == item2->node) { + assert((item1->type == item2->type) && ((item1->type != LYXP_NODE_META) || (meta_pos1 == meta_pos2))); + return 0; + } + + /* 1st ELEM - 2nd TEXT, 1st ELEM - any pos - 2nd META */ + /* elem is always first, 2nd node is after it */ + if (item1->type == LYXP_NODE_ELEM) { + assert(item2->type != LYXP_NODE_ELEM); + return -1; + } + + /* 1st TEXT - 2nd ELEM, 1st TEXT - any pos - 2nd META, 1st META - any pos - 2nd ELEM, 1st META - >pos> - 2nd META */ + /* 2nd is before 1st */ + if (((item1->type == LYXP_NODE_TEXT) && + ((item2->type == LYXP_NODE_ELEM) || (item2->type == LYXP_NODE_META))) || + ((item1->type == LYXP_NODE_META) && (item2->type == LYXP_NODE_ELEM)) || + (((item1->type == LYXP_NODE_META) && (item2->type == LYXP_NODE_META)) && + (meta_pos1 > meta_pos2))) { + return 1; + } + + /* 1st META - any pos - 2nd TEXT, 1st META type == LYXP_SET_NODE_SET); + + set_init(trg, src); + + /* insert node into target set */ + set_insert_node(trg, src->val.nodes[src_idx].node, src->val.nodes[src_idx].pos, src->val.nodes[src_idx].type, 0); + + /* cast target set appropriately */ + return lyxp_set_cast(trg, type); +} + +/** + * @brief Set content canonization for comparisons. + * + * @param[in,out] set Set to canonize. + * @param[in] xp_node Source XPath node/meta to use for canonization. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +static LY_ERR +set_comp_canonize(struct lyxp_set *set, const struct lyxp_set_node *xp_node) +{ + const struct lysc_type *type = NULL; + struct lyd_value val; + struct ly_err_item *err = NULL; + LY_ERR rc; + + /* is there anything to canonize even? */ + if (set->type == LYXP_SET_STRING) { + /* do we have a type to use for canonization? */ + if ((xp_node->type == LYXP_NODE_ELEM) && (xp_node->node->schema->nodetype & LYD_NODE_TERM)) { + type = ((struct lyd_node_term *)xp_node->node)->value.realtype; + } else if (xp_node->type == LYXP_NODE_META) { + type = ((struct lyd_meta *)xp_node->node)->value.realtype; + } + } + if (!type) { + /* no canonization needed/possible */ + return LY_SUCCESS; + } + + /* check for built-in types without required canonization */ + if ((type->basetype == LY_TYPE_STRING) && (type->plugin->store == lyplg_type_store_string)) { + /* string */ + return LY_SUCCESS; + } + if ((type->basetype == LY_TYPE_BOOL) && (type->plugin->store == lyplg_type_store_boolean)) { + /* boolean */ + return LY_SUCCESS; + } + if ((type->basetype == LY_TYPE_ENUM) && (type->plugin->store == lyplg_type_store_enum)) { + /* enumeration */ + return LY_SUCCESS; + } + + /* print canonized string, ignore errors, the value may not satisfy schema constraints */ + rc = type->plugin->store(set->ctx, type, set->val.str, strlen(set->val.str), 0, set->format, set->prefix_data, + LYD_HINT_DATA, xp_node->node->schema, &val, NULL, &err); + ly_err_free(err); + if (rc) { + /* invalid value, function store automaticaly dealloc value when fail */ + return LY_SUCCESS; + } + + /* use the canonized string value */ + free(set->val.str); + set->val.str = strdup(lyd_value_get_canonical(set->ctx, &val)); + type->plugin->free(set->ctx, &val); + LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), LY_EMEM); + + return LY_SUCCESS; +} + +/** + * @brief Bubble sort @p set into XPath document order. + * Context position aware. + * + * @param[in] set Set to sort. + * @return How many times the whole set was traversed - 1 (if set was sorted, returns 0). + */ +static int +set_sort(struct lyxp_set *set) +{ + uint32_t i, j; + int ret = 0, cmp; + ly_bool inverted, change; + const struct lyd_node *root; + struct lyxp_set_node item; + struct lyxp_set_hash_node hnode; + uint64_t hash; + + if ((set->type != LYXP_SET_NODE_SET) || (set->used < 2)) { + return 0; + } + + /* find first top-level node to be used as anchor for positions */ + for (root = set->tree; root->parent; root = lyd_parent(root)) {} + for ( ; root->prev->next; root = root->prev) {} + + /* fill positions */ + if (set_assign_pos(set, root, set->root_type)) { + return -1; + } + +#ifndef NDEBUG + LOGDBG(LY_LDGXPATH, "SORT BEGIN"); + print_set_debug(set); +#endif + + for (i = 0; i < set->used; ++i) { + inverted = 0; + change = 0; + + for (j = 1; j < set->used - i; ++j) { + /* compare node positions */ + if (inverted) { + cmp = set_sort_compare(&set->val.nodes[j], &set->val.nodes[j - 1]); + } else { + cmp = set_sort_compare(&set->val.nodes[j - 1], &set->val.nodes[j]); + } + + /* swap if needed */ + if ((inverted && (cmp < 0)) || (!inverted && (cmp > 0))) { + change = 1; + + item = set->val.nodes[j - 1]; + set->val.nodes[j - 1] = set->val.nodes[j]; + set->val.nodes[j] = item; + } else { + /* whether node_pos1 should be smaller than node_pos2 or the other way around */ + inverted = !inverted; + } + } + + ++ret; + + if (!change) { + break; + } + } + +#ifndef NDEBUG + LOGDBG(LY_LDGXPATH, "SORT END %d", ret); + print_set_debug(set); +#endif + + /* check node hashes */ + if (set->used >= LYD_HT_MIN_ITEMS) { + assert(set->ht); + for (i = 0; i < set->used; ++i) { + hnode.node = set->val.nodes[i].node; + hnode.type = set->val.nodes[i].type; + + hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = dict_hash_multi(hash, NULL, 0); + + assert(!lyht_find(set->ht, &hnode, hash, NULL)); + } + } + + return ret - 1; +} + +/** + * @brief Merge 2 sorted sets into one. + * + * @param[in,out] trg Set to merge into. Duplicates are removed. + * @param[in] src Set to be merged into @p trg. It is cast to #LYXP_SET_EMPTY on success. + * @return LY_ERR + */ +static LY_ERR +set_sorted_merge(struct lyxp_set *trg, struct lyxp_set *src) +{ + uint32_t i, j, k, count, dup_count; + int cmp; + const struct lyd_node *root; + + if ((trg->type != LYXP_SET_NODE_SET) || (src->type != LYXP_SET_NODE_SET)) { + return LY_EINVAL; + } + + if (!src->used) { + return LY_SUCCESS; + } else if (!trg->used) { + set_fill_set(trg, src); + lyxp_set_free_content(src); + return LY_SUCCESS; + } + + /* find first top-level node to be used as anchor for positions */ + for (root = trg->tree; root->parent; root = lyd_parent(root)) {} + for ( ; root->prev->next; root = root->prev) {} + + /* fill positions */ + if (set_assign_pos(trg, root, trg->root_type) || set_assign_pos(src, root, src->root_type)) { + return LY_EINT; + } + +#ifndef NDEBUG + LOGDBG(LY_LDGXPATH, "MERGE target"); + print_set_debug(trg); + LOGDBG(LY_LDGXPATH, "MERGE source"); + print_set_debug(src); +#endif + + /* make memory for the merge (duplicates are not detected yet, so space + * will likely be wasted on them, too bad) */ + if (trg->size - trg->used < src->used) { + trg->size = trg->used + src->used; + + trg->val.nodes = ly_realloc(trg->val.nodes, trg->size * sizeof *trg->val.nodes); + LY_CHECK_ERR_RET(!trg->val.nodes, LOGMEM(src->ctx), LY_EMEM); + } + + i = 0; + j = 0; + count = 0; + dup_count = 0; + do { + cmp = set_sort_compare(&src->val.nodes[i], &trg->val.nodes[j]); + if (!cmp) { + if (!count) { + /* duplicate, just skip it */ + ++i; + ++j; + } else { + /* we are copying something already, so let's copy the duplicate too, + * we are hoping that afterwards there are some more nodes to + * copy and this way we can copy them all together */ + ++count; + ++dup_count; + ++i; + ++j; + } + } else if (cmp < 0) { + /* inserting src node into trg, just remember it for now */ + ++count; + ++i; + + /* insert the hash now */ + set_insert_node_hash(trg, src->val.nodes[i - 1].node, src->val.nodes[i - 1].type); + } else if (count) { +copy_nodes: + /* time to actually copy the nodes, we have found the largest block of nodes */ + memmove(&trg->val.nodes[j + (count - dup_count)], + &trg->val.nodes[j], + (trg->used - j) * sizeof *trg->val.nodes); + memcpy(&trg->val.nodes[j - dup_count], &src->val.nodes[i - count], count * sizeof *src->val.nodes); + + trg->used += count - dup_count; + /* do not change i, except the copying above, we are basically doing exactly what is in the else branch below */ + j += count - dup_count; + + count = 0; + dup_count = 0; + } else { + ++j; + } + } while ((i < src->used) && (j < trg->used)); + + if ((i < src->used) || count) { + /* insert all the hashes first */ + for (k = i; k < src->used; ++k) { + set_insert_node_hash(trg, src->val.nodes[k].node, src->val.nodes[k].type); + } + + /* loop ended, but we need to copy something at trg end */ + count += src->used - i; + i = src->used; + goto copy_nodes; + } + + /* we are inserting hashes before the actual node insert, which causes + * situations when there were initially not enough items for a hash table, + * but even after some were inserted, hash table was not created (during + * insertion the number of items is not updated yet) */ + if (!trg->ht && (trg->used >= LYD_HT_MIN_ITEMS)) { + set_insert_node_hash(trg, NULL, 0); + } + +#ifndef NDEBUG + LOGDBG(LY_LDGXPATH, "MERGE result"); + print_set_debug(trg); +#endif + + lyxp_set_free_content(src); + return LY_SUCCESS; +} + +LY_ERR +lyxp_check_token(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t tok_idx, enum lyxp_token want_tok) +{ + if (exp->used == tok_idx) { + if (ctx) { + LOGVAL(ctx, LY_VCODE_XP_EOF); + } + return LY_EINCOMPLETE; + } + + if (want_tok && (exp->tokens[tok_idx] != want_tok)) { + if (ctx) { + LOGVAL(ctx, LY_VCODE_XP_INTOK2, lyxp_token2str(exp->tokens[tok_idx]), + &exp->expr[exp->tok_pos[tok_idx]], lyxp_token2str(want_tok)); + } + return LY_ENOT; + } + + return LY_SUCCESS; +} + +LY_ERR +lyxp_next_token(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_token want_tok) +{ + LY_CHECK_RET(lyxp_check_token(ctx, exp, *tok_idx, want_tok)); + + /* skip the token */ + ++(*tok_idx); + + return LY_SUCCESS; +} + +/* just like lyxp_check_token() but tests for 2 tokens */ +static LY_ERR +exp_check_token2(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t tok_idx, enum lyxp_token want_tok1, + enum lyxp_token want_tok2) +{ + if (exp->used == tok_idx) { + if (ctx) { + LOGVAL(ctx, LY_VCODE_XP_EOF); + } + return LY_EINCOMPLETE; + } + + if ((exp->tokens[tok_idx] != want_tok1) && (exp->tokens[tok_idx] != want_tok2)) { + if (ctx) { + LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[tok_idx]), + &exp->expr[exp->tok_pos[tok_idx]]); + } + return LY_ENOT; + } + + return LY_SUCCESS; +} + +LY_ERR +lyxp_next_token2(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_token want_tok1, + enum lyxp_token want_tok2) +{ + LY_CHECK_RET(exp_check_token2(ctx, exp, *tok_idx, want_tok1, want_tok2)); + + /* skip the token */ + ++(*tok_idx); + + return LY_SUCCESS; +} + +/** + * @brief Stack operation push on the repeat array. + * + * @param[in] exp Expression to use. + * @param[in] tok_idx Position in the expresion \p exp. + * @param[in] repeat_op_idx Index from \p exp of the operator token. This value is pushed. + */ +static void +exp_repeat_push(struct lyxp_expr *exp, uint32_t tok_idx, uint32_t repeat_op_idx) +{ + uint32_t i; + + if (exp->repeat[tok_idx]) { + for (i = 0; exp->repeat[tok_idx][i]; ++i) {} + exp->repeat[tok_idx] = realloc(exp->repeat[tok_idx], (i + 2) * sizeof *exp->repeat[tok_idx]); + LY_CHECK_ERR_RET(!exp->repeat[tok_idx], LOGMEM(NULL), ); + exp->repeat[tok_idx][i] = repeat_op_idx; + exp->repeat[tok_idx][i + 1] = 0; + } else { + exp->repeat[tok_idx] = calloc(2, sizeof *exp->repeat[tok_idx]); + LY_CHECK_ERR_RET(!exp->repeat[tok_idx], LOGMEM(NULL), ); + exp->repeat[tok_idx][0] = repeat_op_idx; + } +} + +/** + * @brief Reparse Predicate. Logs directly on error. + * + * [7] Predicate ::= '[' Expr ']' + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_predicate(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + LY_ERR rc; + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_BRACK1); + LY_CHECK_RET(rc); + ++(*tok_idx); + + rc = reparse_or_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_BRACK2); + LY_CHECK_RET(rc); + ++(*tok_idx); + + return LY_SUCCESS; +} + +/** + * @brief Reparse RelativeLocationPath. Logs directly on error. + * + * [4] RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..' + * [6] NodeTest ::= NameTest | NodeType '(' ')' + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression \p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR (LY_EINCOMPLETE on forward reference) + */ +static LY_ERR +reparse_relative_location_path(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + LY_ERR rc; + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NONE); + LY_CHECK_RET(rc); + + goto step; + do { + /* '/' or '//' */ + ++(*tok_idx); + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NONE); + LY_CHECK_RET(rc); +step: + /* Step */ + switch (exp->tokens[*tok_idx]) { + case LYXP_TOKEN_DOT: + ++(*tok_idx); + break; + + case LYXP_TOKEN_DDOT: + ++(*tok_idx); + break; + + case LYXP_TOKEN_AXISNAME: + ++(*tok_idx); + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_DCOLON); + LY_CHECK_RET(rc); + + /* fall through */ + case LYXP_TOKEN_AT: + ++(*tok_idx); + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NONE); + LY_CHECK_RET(rc); + if ((exp->tokens[*tok_idx] != LYXP_TOKEN_NAMETEST) && (exp->tokens[*tok_idx] != LYXP_TOKEN_NODETYPE)) { + LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EVALID; + } + if (exp->tokens[*tok_idx] == LYXP_TOKEN_NODETYPE) { + goto reparse_nodetype; + } + /* fall through */ + case LYXP_TOKEN_NAMETEST: + ++(*tok_idx); + goto reparse_predicate; + + case LYXP_TOKEN_NODETYPE: +reparse_nodetype: + ++(*tok_idx); + + /* '(' */ + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_PAR1); + LY_CHECK_RET(rc); + ++(*tok_idx); + + /* ')' */ + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_PAR2); + LY_CHECK_RET(rc); + ++(*tok_idx); + +reparse_predicate: + /* Predicate* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) { + rc = reparse_predicate(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + break; + default: + LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EVALID; + } + } while (!exp_check_token2(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_PATH, LYXP_TOKEN_OPER_RPATH)); + + return LY_SUCCESS; +} + +/** + * @brief Reparse AbsoluteLocationPath. Logs directly on error. + * + * [3] AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression \p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_absolute_location_path(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + LY_ERR rc; + + LY_CHECK_RET(exp_check_token2(ctx, exp, *tok_idx, LYXP_TOKEN_OPER_PATH, LYXP_TOKEN_OPER_RPATH)); + + /* '/' RelativeLocationPath? */ + if (exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_PATH) { + /* '/' */ + ++(*tok_idx); + + if (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_NONE)) { + return LY_SUCCESS; + } + switch (exp->tokens[*tok_idx]) { + case LYXP_TOKEN_DOT: + case LYXP_TOKEN_DDOT: + case LYXP_TOKEN_AXISNAME: + case LYXP_TOKEN_AT: + case LYXP_TOKEN_NAMETEST: + case LYXP_TOKEN_NODETYPE: + rc = reparse_relative_location_path(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + /* fall through */ + default: + break; + } + + } else { + /* '//' RelativeLocationPath */ + ++(*tok_idx); + + rc = reparse_relative_location_path(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Reparse FunctionCall. Logs directly on error. + * + * [9] FunctionCall ::= FunctionName '(' ( Expr ( ',' Expr )* )? ')' + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_function_call(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + int8_t min_arg_count = -1; + uint32_t arg_count, max_arg_count = 0, func_tok_idx; + LY_ERR rc; + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_FUNCNAME); + LY_CHECK_RET(rc); + func_tok_idx = *tok_idx; + switch (exp->tok_len[*tok_idx]) { + case 3: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "not", 3)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "sum", 3)) { + min_arg_count = 1; + max_arg_count = 1; + } + break; + case 4: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "lang", 4)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "last", 4)) { + min_arg_count = 0; + max_arg_count = 0; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "name", 4)) { + min_arg_count = 0; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "true", 4)) { + min_arg_count = 0; + max_arg_count = 0; + } + break; + case 5: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "count", 5)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "false", 5)) { + min_arg_count = 0; + max_arg_count = 0; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "floor", 5)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "round", 5)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "deref", 5)) { + min_arg_count = 1; + max_arg_count = 1; + } + break; + case 6: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "concat", 6)) { + min_arg_count = 2; + max_arg_count = UINT32_MAX; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "number", 6)) { + min_arg_count = 0; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "string", 6)) { + min_arg_count = 0; + max_arg_count = 1; + } + break; + case 7: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "boolean", 7)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "ceiling", 7)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "current", 7)) { + min_arg_count = 0; + max_arg_count = 0; + } + break; + case 8: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "contains", 8)) { + min_arg_count = 2; + max_arg_count = 2; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "position", 8)) { + min_arg_count = 0; + max_arg_count = 0; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "re-match", 8)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + case 9: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "substring", 9)) { + min_arg_count = 2; + max_arg_count = 3; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "translate", 9)) { + min_arg_count = 3; + max_arg_count = 3; + } + break; + case 10: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "local-name", 10)) { + min_arg_count = 0; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "enum-value", 10)) { + min_arg_count = 1; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "bit-is-set", 10)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + case 11: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "starts-with", 11)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + case 12: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "derived-from", 12)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + case 13: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "namespace-uri", 13)) { + min_arg_count = 0; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "string-length", 13)) { + min_arg_count = 0; + max_arg_count = 1; + } + break; + case 15: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "normalize-space", 15)) { + min_arg_count = 0; + max_arg_count = 1; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "substring-after", 15)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + case 16: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "substring-before", 16)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + case 20: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "derived-from-or-self", 20)) { + min_arg_count = 2; + max_arg_count = 2; + } + break; + } + if (min_arg_count == -1) { + LOGVAL(ctx, LY_VCODE_XP_INFUNC, exp->tok_len[*tok_idx], &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EINVAL; + } + ++(*tok_idx); + + /* '(' */ + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_PAR1); + LY_CHECK_RET(rc); + ++(*tok_idx); + + /* ( Expr ( ',' Expr )* )? */ + arg_count = 0; + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NONE); + LY_CHECK_RET(rc); + if (exp->tokens[*tok_idx] != LYXP_TOKEN_PAR2) { + ++arg_count; + rc = reparse_or_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_COMMA)) { + ++(*tok_idx); + + ++arg_count; + rc = reparse_or_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + + /* ')' */ + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_PAR2); + LY_CHECK_RET(rc); + ++(*tok_idx); + + if ((arg_count < (uint32_t)min_arg_count) || (arg_count > max_arg_count)) { + LOGVAL(ctx, LY_VCODE_XP_INARGCOUNT, arg_count, exp->tok_len[func_tok_idx], &exp->expr[exp->tok_pos[func_tok_idx]]); + return LY_EVALID; + } + + return LY_SUCCESS; +} + +/** + * @brief Reparse PathExpr. Logs directly on error. + * + * [10] PathExpr ::= LocationPath | PrimaryExpr Predicate* + * | PrimaryExpr Predicate* '/' RelativeLocationPath + * | PrimaryExpr Predicate* '//' RelativeLocationPath + * [2] LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + * [8] PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_path_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + LY_ERR rc; + + if (lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_NONE)) { + return LY_EVALID; + } + + switch (exp->tokens[*tok_idx]) { + case LYXP_TOKEN_PAR1: + /* '(' Expr ')' Predicate* */ + ++(*tok_idx); + + rc = reparse_or_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + + rc = lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_PAR2); + LY_CHECK_RET(rc); + ++(*tok_idx); + goto predicate; + case LYXP_TOKEN_DOT: + case LYXP_TOKEN_DDOT: + case LYXP_TOKEN_AXISNAME: + case LYXP_TOKEN_AT: + case LYXP_TOKEN_NAMETEST: + case LYXP_TOKEN_NODETYPE: + /* RelativeLocationPath */ + rc = reparse_relative_location_path(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + break; + case LYXP_TOKEN_VARREF: + /* VariableReference */ + ++(*tok_idx); + goto predicate; + case LYXP_TOKEN_FUNCNAME: + /* FunctionCall */ + rc = reparse_function_call(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + goto predicate; + case LYXP_TOKEN_OPER_PATH: + case LYXP_TOKEN_OPER_RPATH: + /* AbsoluteLocationPath */ + rc = reparse_absolute_location_path(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + break; + case LYXP_TOKEN_LITERAL: + /* Literal */ + ++(*tok_idx); + goto predicate; + case LYXP_TOKEN_NUMBER: + /* Number */ + ++(*tok_idx); + goto predicate; + default: + LOGVAL(ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EVALID; + } + + return LY_SUCCESS; + +predicate: + /* Predicate* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) { + rc = reparse_predicate(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + + /* ('/' or '//') RelativeLocationPath */ + if (!exp_check_token2(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_PATH, LYXP_TOKEN_OPER_RPATH)) { + + /* '/' or '//' */ + ++(*tok_idx); + + rc = reparse_relative_location_path(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Reparse UnaryExpr. Logs directly on error. + * + * [17] UnaryExpr ::= UnionExpr | '-' UnaryExpr + * [18] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_unary_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + uint32_t prev_exp; + LY_ERR rc; + + /* ('-')* */ + prev_exp = *tok_idx; + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_MATH) && + (exp->expr[exp->tok_pos[*tok_idx]] == '-')) { + exp_repeat_push(exp, prev_exp, LYXP_EXPR_UNARY); + ++(*tok_idx); + } + + /* PathExpr */ + prev_exp = *tok_idx; + rc = reparse_path_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + + /* ('|' PathExpr)* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_UNI)) { + exp_repeat_push(exp, prev_exp, LYXP_EXPR_UNION); + ++(*tok_idx); + + rc = reparse_path_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Reparse AdditiveExpr. Logs directly on error. + * + * [15] AdditiveExpr ::= MultiplicativeExpr + * | AdditiveExpr '+' MultiplicativeExpr + * | AdditiveExpr '-' MultiplicativeExpr + * [16] MultiplicativeExpr ::= UnaryExpr + * | MultiplicativeExpr '*' UnaryExpr + * | MultiplicativeExpr 'div' UnaryExpr + * | MultiplicativeExpr 'mod' UnaryExpr + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_additive_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + uint32_t prev_add_exp, prev_mul_exp; + LY_ERR rc; + + prev_add_exp = *tok_idx; + goto reparse_multiplicative_expr; + + /* ('+' / '-' MultiplicativeExpr)* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_MATH) && + ((exp->expr[exp->tok_pos[*tok_idx]] == '+') || (exp->expr[exp->tok_pos[*tok_idx]] == '-'))) { + exp_repeat_push(exp, prev_add_exp, LYXP_EXPR_ADDITIVE); + ++(*tok_idx); + +reparse_multiplicative_expr: + /* UnaryExpr */ + prev_mul_exp = *tok_idx; + rc = reparse_unary_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + + /* ('*' / 'div' / 'mod' UnaryExpr)* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_MATH) && + ((exp->expr[exp->tok_pos[*tok_idx]] == '*') || (exp->tok_len[*tok_idx] == 3))) { + exp_repeat_push(exp, prev_mul_exp, LYXP_EXPR_MULTIPLICATIVE); + ++(*tok_idx); + + rc = reparse_unary_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Reparse EqualityExpr. Logs directly on error. + * + * [13] EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr + * | EqualityExpr '!=' RelationalExpr + * [14] RelationalExpr ::= AdditiveExpr + * | RelationalExpr '<' AdditiveExpr + * | RelationalExpr '>' AdditiveExpr + * | RelationalExpr '<=' AdditiveExpr + * | RelationalExpr '>=' AdditiveExpr + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_equality_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + uint32_t prev_eq_exp, prev_rel_exp; + LY_ERR rc; + + prev_eq_exp = *tok_idx; + goto reparse_additive_expr; + + /* ('=' / '!=' RelationalExpr)* */ + while (!exp_check_token2(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_EQUAL, LYXP_TOKEN_OPER_NEQUAL)) { + exp_repeat_push(exp, prev_eq_exp, LYXP_EXPR_EQUALITY); + ++(*tok_idx); + +reparse_additive_expr: + /* AdditiveExpr */ + prev_rel_exp = *tok_idx; + rc = reparse_additive_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + + /* ('<' / '>' / '<=' / '>=' AdditiveExpr)* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_COMP)) { + exp_repeat_push(exp, prev_rel_exp, LYXP_EXPR_RELATIONAL); + ++(*tok_idx); + + rc = reparse_additive_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Reparse OrExpr. Logs directly on error. + * + * [11] OrExpr ::= AndExpr | OrExpr 'or' AndExpr + * [12] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + * + * @param[in] ctx Context for logging. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] depth Current number of nested expressions. + * @return LY_ERR + */ +static LY_ERR +reparse_or_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth) +{ + uint32_t prev_or_exp, prev_and_exp; + LY_ERR rc; + + ++depth; + LY_CHECK_ERR_RET(depth > LYXP_MAX_BLOCK_DEPTH, LOGVAL(ctx, LY_VCODE_XP_DEPTH), LY_EINVAL); + + prev_or_exp = *tok_idx; + goto reparse_equality_expr; + + /* ('or' AndExpr)* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_LOG) && (exp->tok_len[*tok_idx] == 2)) { + exp_repeat_push(exp, prev_or_exp, LYXP_EXPR_OR); + ++(*tok_idx); + +reparse_equality_expr: + /* EqualityExpr */ + prev_and_exp = *tok_idx; + rc = reparse_equality_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + + /* ('and' EqualityExpr)* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_LOG) && (exp->tok_len[*tok_idx] == 3)) { + exp_repeat_push(exp, prev_and_exp, LYXP_EXPR_AND); + ++(*tok_idx); + + rc = reparse_equality_expr(ctx, exp, tok_idx, depth); + LY_CHECK_RET(rc); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Parse NCName. + * + * @param[in] ncname Name to parse. + * @return Length of @p ncname valid bytes. + */ +static ssize_t +parse_ncname(const char *ncname) +{ + uint32_t uc; + size_t size; + ssize_t len = 0; + + LY_CHECK_RET(ly_getutf8(&ncname, &uc, &size), 0); + if (!is_xmlqnamestartchar(uc) || (uc == ':')) { + return len; + } + + do { + len += size; + if (!*ncname) { + break; + } + LY_CHECK_RET(ly_getutf8(&ncname, &uc, &size), -len); + } while (is_xmlqnamechar(uc) && (uc != ':')); + + return len; +} + +/** + * @brief Add @p token into the expression @p exp. + * + * @param[in] ctx Context for logging. + * @param[in] exp Expression to use. + * @param[in] token Token to add. + * @param[in] tok_pos Token position in the XPath expression. + * @param[in] tok_len Token length in the XPath expression. + * @return LY_ERR + */ +static LY_ERR +exp_add_token(const struct ly_ctx *ctx, struct lyxp_expr *exp, enum lyxp_token token, uint32_t tok_pos, uint32_t tok_len) +{ + uint32_t prev; + + if (exp->used == exp->size) { + prev = exp->size; + exp->size += LYXP_EXPR_SIZE_STEP; + if (prev > exp->size) { + LOGINT(ctx); + return LY_EINT; + } + + exp->tokens = ly_realloc(exp->tokens, exp->size * sizeof *exp->tokens); + LY_CHECK_ERR_RET(!exp->tokens, LOGMEM(ctx), LY_EMEM); + exp->tok_pos = ly_realloc(exp->tok_pos, exp->size * sizeof *exp->tok_pos); + LY_CHECK_ERR_RET(!exp->tok_pos, LOGMEM(ctx), LY_EMEM); + exp->tok_len = ly_realloc(exp->tok_len, exp->size * sizeof *exp->tok_len); + LY_CHECK_ERR_RET(!exp->tok_len, LOGMEM(ctx), LY_EMEM); + } + + exp->tokens[exp->used] = token; + exp->tok_pos[exp->used] = tok_pos; + exp->tok_len[exp->used] = tok_len; + ++exp->used; + return LY_SUCCESS; +} + +void +lyxp_expr_free(const struct ly_ctx *ctx, struct lyxp_expr *expr) +{ + uint32_t i; + + if (!expr) { + return; + } + + lydict_remove(ctx, expr->expr); + free(expr->tokens); + free(expr->tok_pos); + free(expr->tok_len); + if (expr->repeat) { + for (i = 0; i < expr->used; ++i) { + free(expr->repeat[i]); + } + } + free(expr->repeat); + free(expr); +} + +/** + * @brief Parse Axis name. + * + * @param[in] str String to parse. + * @param[in] str_len Length of @p str. + * @return LY_SUCCESS if an axis. + * @return LY_ENOT otherwise. + */ +static LY_ERR +expr_parse_axis(const char *str, size_t str_len) +{ + switch (str_len) { + case 4: + if (!strncmp("self", str, str_len)) { + return LY_SUCCESS; + } + break; + case 5: + if (!strncmp("child", str, str_len)) { + return LY_SUCCESS; + } + break; + case 6: + if (!strncmp("parent", str, str_len)) { + return LY_SUCCESS; + } + break; + case 8: + if (!strncmp("ancestor", str, str_len)) { + return LY_SUCCESS; + } + break; + case 9: + if (!strncmp("attribute", str, str_len)) { + return LY_SUCCESS; + } else if (!strncmp("following", str, str_len)) { + return LY_SUCCESS; + } else if (!strncmp("namespace", str, str_len)) { + LOGERR(NULL, LY_EINVAL, "Axis \"namespace\" not supported."); + return LY_ENOT; + } else if (!strncmp("preceding", str, str_len)) { + return LY_SUCCESS; + } + break; + case 10: + if (!strncmp("descendant", str, str_len)) { + return LY_SUCCESS; + } + break; + case 16: + if (!strncmp("ancestor-or-self", str, str_len)) { + return LY_SUCCESS; + } + break; + case 17: + if (!strncmp("following-sibling", str, str_len)) { + return LY_SUCCESS; + } else if (!strncmp("preceding-sibling", str, str_len)) { + return LY_SUCCESS; + } + break; + case 18: + if (!strncmp("descendant-or-self", str, str_len)) { + return LY_SUCCESS; + } + break; + } + + return LY_ENOT; +} + +LY_ERR +lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, ly_bool reparse, struct lyxp_expr **expr_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *expr; + size_t parsed = 0, tok_len; + enum lyxp_token tok_type; + ly_bool prev_func_check = 0, prev_ntype_check = 0, has_axis; + uint32_t tok_idx = 0; + ssize_t ncname_len; + + assert(expr_p); + + if (!expr_str[0]) { + LOGVAL(ctx, LY_VCODE_XP_EOF); + return LY_EVALID; + } + + if (!expr_len) { + expr_len = strlen(expr_str); + } + if (expr_len > UINT32_MAX) { + LOGVAL(ctx, LYVE_XPATH, "XPath expression cannot be longer than %" PRIu32 " characters.", UINT32_MAX); + return LY_EVALID; + } + + /* init lyxp_expr structure */ + expr = calloc(1, sizeof *expr); + LY_CHECK_ERR_GOTO(!expr, LOGMEM(ctx); ret = LY_EMEM, error); + LY_CHECK_GOTO(ret = lydict_insert(ctx, expr_str, expr_len, &expr->expr), error); + expr->used = 0; + expr->size = LYXP_EXPR_SIZE_START; + expr->tokens = malloc(expr->size * sizeof *expr->tokens); + LY_CHECK_ERR_GOTO(!expr->tokens, LOGMEM(ctx); ret = LY_EMEM, error); + + expr->tok_pos = malloc(expr->size * sizeof *expr->tok_pos); + LY_CHECK_ERR_GOTO(!expr->tok_pos, LOGMEM(ctx); ret = LY_EMEM, error); + + expr->tok_len = malloc(expr->size * sizeof *expr->tok_len); + LY_CHECK_ERR_GOTO(!expr->tok_len, LOGMEM(ctx); ret = LY_EMEM, error); + + /* make expr 0-terminated */ + expr_str = expr->expr; + + while (is_xmlws(expr_str[parsed])) { + ++parsed; + } + + do { + if (expr_str[parsed] == '(') { + + /* '(' */ + tok_len = 1; + tok_type = LYXP_TOKEN_PAR1; + + if (prev_ntype_check && expr->used && (expr->tokens[expr->used - 1] == LYXP_TOKEN_NAMETEST) && + (((expr->tok_len[expr->used - 1] == 4) && + (!strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "node", 4) || + !strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "text", 4))) || + ((expr->tok_len[expr->used - 1] == 7) && + !strncmp(&expr_str[expr->tok_pos[expr->used - 1]], "comment", 7)))) { + /* it is NodeType after all */ + expr->tokens[expr->used - 1] = LYXP_TOKEN_NODETYPE; + + prev_ntype_check = 0; + prev_func_check = 0; + } else if (prev_func_check && expr->used && (expr->tokens[expr->used - 1] == LYXP_TOKEN_NAMETEST)) { + /* it is FunctionName after all */ + expr->tokens[expr->used - 1] = LYXP_TOKEN_FUNCNAME; + + prev_ntype_check = 0; + prev_func_check = 0; + } + + } else if (expr_str[parsed] == ')') { + + /* ')' */ + tok_len = 1; + tok_type = LYXP_TOKEN_PAR2; + + } else if (expr_str[parsed] == '[') { + + /* '[' */ + tok_len = 1; + tok_type = LYXP_TOKEN_BRACK1; + + } else if (expr_str[parsed] == ']') { + + /* ']' */ + tok_len = 1; + tok_type = LYXP_TOKEN_BRACK2; + + } else if (!strncmp(&expr_str[parsed], "..", 2)) { + + /* '..' */ + tok_len = 2; + tok_type = LYXP_TOKEN_DDOT; + + } else if ((expr_str[parsed] == '.') && (!isdigit(expr_str[parsed + 1]))) { + + /* '.' */ + tok_len = 1; + tok_type = LYXP_TOKEN_DOT; + + } else if (expr_str[parsed] == '@') { + + /* '@' */ + tok_len = 1; + tok_type = LYXP_TOKEN_AT; + + } else if (expr_str[parsed] == ',') { + + /* ',' */ + tok_len = 1; + tok_type = LYXP_TOKEN_COMMA; + + } else if (expr_str[parsed] == '\'') { + + /* Literal with ' */ + for (tok_len = 1; (expr_str[parsed + tok_len] != '\0') && (expr_str[parsed + tok_len] != '\''); ++tok_len) {} + LY_CHECK_ERR_GOTO(expr_str[parsed + tok_len] == '\0', + LOGVAL(ctx, LY_VCODE_XP_EOE, expr_str[parsed], &expr_str[parsed]); ret = LY_EVALID, + error); + ++tok_len; + tok_type = LYXP_TOKEN_LITERAL; + + } else if (expr_str[parsed] == '\"') { + + /* Literal with " */ + for (tok_len = 1; (expr_str[parsed + tok_len] != '\0') && (expr_str[parsed + tok_len] != '\"'); ++tok_len) {} + LY_CHECK_ERR_GOTO(expr_str[parsed + tok_len] == '\0', + LOGVAL(ctx, LY_VCODE_XP_EOE, expr_str[parsed], &expr_str[parsed]); ret = LY_EVALID, + error); + ++tok_len; + tok_type = LYXP_TOKEN_LITERAL; + + } else if ((expr_str[parsed] == '.') || (isdigit(expr_str[parsed]))) { + + /* Number */ + for (tok_len = 0; isdigit(expr_str[parsed + tok_len]); ++tok_len) {} + if (expr_str[parsed + tok_len] == '.') { + ++tok_len; + for ( ; isdigit(expr_str[parsed + tok_len]); ++tok_len) {} + } + tok_type = LYXP_TOKEN_NUMBER; + + } else if (expr_str[parsed] == '$') { + + /* VariableReference */ + parsed++; + ncname_len = parse_ncname(&expr_str[parsed]); + LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], + parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + tok_len = ncname_len; + LY_CHECK_ERR_GOTO(expr_str[parsed + tok_len] == ':', + LOGVAL(ctx, LYVE_XPATH, "Variable with prefix is not supported."); ret = LY_EVALID, + error); + tok_type = LYXP_TOKEN_VARREF; + + } else if (expr_str[parsed] == '/') { + + /* Operator '/', '//' */ + if (!strncmp(&expr_str[parsed], "//", 2)) { + tok_len = 2; + tok_type = LYXP_TOKEN_OPER_RPATH; + } else { + tok_len = 1; + tok_type = LYXP_TOKEN_OPER_PATH; + } + + } else if (!strncmp(&expr_str[parsed], "!=", 2)) { + + /* Operator '!=' */ + tok_len = 2; + tok_type = LYXP_TOKEN_OPER_NEQUAL; + + } else if (!strncmp(&expr_str[parsed], "<=", 2) || !strncmp(&expr_str[parsed], ">=", 2)) { + + /* Operator '<=', '>=' */ + tok_len = 2; + tok_type = LYXP_TOKEN_OPER_COMP; + + } else if (expr_str[parsed] == '|') { + + /* Operator '|' */ + tok_len = 1; + tok_type = LYXP_TOKEN_OPER_UNI; + + } else if ((expr_str[parsed] == '+') || (expr_str[parsed] == '-')) { + + /* Operator '+', '-' */ + tok_len = 1; + tok_type = LYXP_TOKEN_OPER_MATH; + + } else if (expr_str[parsed] == '=') { + + /* Operator '=' */ + tok_len = 1; + tok_type = LYXP_TOKEN_OPER_EQUAL; + + } else if ((expr_str[parsed] == '<') || (expr_str[parsed] == '>')) { + + /* Operator '<', '>' */ + tok_len = 1; + tok_type = LYXP_TOKEN_OPER_COMP; + + } else if (expr->used && (expr->tokens[expr->used - 1] != LYXP_TOKEN_AT) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_PAR1) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_BRACK1) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_COMMA) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_LOG) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_EQUAL) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_NEQUAL) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_COMP) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_MATH) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_UNI) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_PATH) && + (expr->tokens[expr->used - 1] != LYXP_TOKEN_OPER_RPATH)) { + + /* Operator '*', 'or', 'and', 'mod', or 'div' */ + if (expr_str[parsed] == '*') { + tok_len = 1; + tok_type = LYXP_TOKEN_OPER_MATH; + + } else if (!strncmp(&expr_str[parsed], "or", 2)) { + tok_len = 2; + tok_type = LYXP_TOKEN_OPER_LOG; + + } else if (!strncmp(&expr_str[parsed], "and", 3)) { + tok_len = 3; + tok_type = LYXP_TOKEN_OPER_LOG; + + } else if (!strncmp(&expr_str[parsed], "mod", 3) || !strncmp(&expr_str[parsed], "div", 3)) { + tok_len = 3; + tok_type = LYXP_TOKEN_OPER_MATH; + + } else if (prev_ntype_check || prev_func_check) { + LOGVAL(ctx, LYVE_XPATH, "Invalid character 0x%x ('%c'), perhaps \"%.*s\" is supposed to be a function call.", + expr_str[parsed], expr_str[parsed], expr->tok_len[expr->used - 1], &expr->expr[expr->tok_pos[expr->used - 1]]); + ret = LY_EVALID; + goto error; + } else { + LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], parsed + 1, expr_str); + ret = LY_EVALID; + goto error; + } + } else { + + /* (AxisName '::')? ((NCName ':')? '*' | QName) or NodeType/FunctionName */ + if (expr_str[parsed] == '*') { + ncname_len = 1; + } else { + ncname_len = parse_ncname(&expr_str[parsed]); + LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], + parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + } + tok_len = ncname_len; + + has_axis = 0; + if (!strncmp(&expr_str[parsed + tok_len], "::", 2)) { + /* axis */ + LY_CHECK_ERR_GOTO(expr_parse_axis(&expr_str[parsed], ncname_len), + LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], parsed + 1, expr_str); ret = LY_EVALID, error); + tok_type = LYXP_TOKEN_AXISNAME; + + LY_CHECK_GOTO(ret = exp_add_token(ctx, expr, tok_type, parsed, tok_len), error); + parsed += tok_len; + + /* '::' */ + tok_len = 2; + tok_type = LYXP_TOKEN_DCOLON; + + LY_CHECK_GOTO(ret = exp_add_token(ctx, expr, tok_type, parsed, tok_len), error); + parsed += tok_len; + + if (expr_str[parsed] == '*') { + ncname_len = 1; + } else { + ncname_len = parse_ncname(&expr_str[parsed]); + LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], + parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + } + tok_len = ncname_len; + + has_axis = 1; + } + + if (expr_str[parsed + tok_len] == ':') { + ++tok_len; + if (expr_str[parsed + tok_len] == '*') { + ++tok_len; + } else { + ncname_len = parse_ncname(&expr_str[parsed + tok_len]); + LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], + parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + tok_len += ncname_len; + } + /* remove old flags to prevent ambiguities */ + prev_ntype_check = 0; + prev_func_check = 0; + tok_type = LYXP_TOKEN_NAMETEST; + } else { + /* if not '*', there is no prefix so it can still be NodeType/FunctionName, we can't finally decide now */ + prev_ntype_check = (expr_str[parsed] == '*') ? 0 : 1; + prev_func_check = (prev_ntype_check && !has_axis) ? 1 : 0; + tok_type = LYXP_TOKEN_NAMETEST; + } + } + + /* store the token, move on to the next one */ + LY_CHECK_GOTO(ret = exp_add_token(ctx, expr, tok_type, parsed, tok_len), error); + parsed += tok_len; + while (is_xmlws(expr_str[parsed])) { + ++parsed; + } + + } while (expr_str[parsed]); + + if (reparse) { + /* prealloc repeat */ + expr->repeat = calloc(expr->size, sizeof *expr->repeat); + LY_CHECK_ERR_GOTO(!expr->repeat, LOGMEM(ctx); ret = LY_EMEM, error); + + /* fill repeat */ + LY_CHECK_ERR_GOTO(reparse_or_expr(ctx, expr, &tok_idx, 0), ret = LY_EVALID, error); + if (expr->used > tok_idx) { + LOGVAL(ctx, LYVE_XPATH, "Unparsed characters \"%s\" left at the end of an XPath expression.", + &expr->expr[expr->tok_pos[tok_idx]]); + ret = LY_EVALID; + goto error; + } + } + + print_expr_struct_debug(expr); + *expr_p = expr; + return LY_SUCCESS; + +error: + lyxp_expr_free(ctx, expr); + return ret; +} + +LY_ERR +lyxp_expr_dup(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t start_idx, uint32_t end_idx, + struct lyxp_expr **dup_p) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr *dup = NULL; + uint32_t used = 0, i, j, expr_len; + const char *expr_start; + + assert((!start_idx && !end_idx) || ((start_idx < exp->used) && (end_idx < exp->used) && (start_idx <= end_idx))); + + if (!exp) { + goto cleanup; + } + + if (!start_idx && !end_idx) { + end_idx = exp->used - 1; + } + + expr_start = exp->expr + exp->tok_pos[start_idx]; + expr_len = (exp->tok_pos[end_idx] + exp->tok_len[end_idx]) - exp->tok_pos[start_idx]; + + dup = calloc(1, sizeof *dup); + LY_CHECK_ERR_GOTO(!dup, LOGMEM(ctx); ret = LY_EMEM, cleanup); + + if (exp->used) { + used = (end_idx - start_idx) + 1; + + dup->tokens = malloc(used * sizeof *dup->tokens); + LY_CHECK_ERR_GOTO(!dup->tokens, LOGMEM(ctx); ret = LY_EMEM, cleanup); + memcpy(dup->tokens, exp->tokens + start_idx, used * sizeof *dup->tokens); + + dup->tok_pos = malloc(used * sizeof *dup->tok_pos); + LY_CHECK_ERR_GOTO(!dup->tok_pos, LOGMEM(ctx); ret = LY_EMEM, cleanup); + memcpy(dup->tok_pos, exp->tok_pos + start_idx, used * sizeof *dup->tok_pos); + + if (start_idx) { + /* fix the indices in the expression */ + for (i = 0; i < used; ++i) { + dup->tok_pos[i] -= expr_start - exp->expr; + } + } + + dup->tok_len = malloc(used * sizeof *dup->tok_len); + LY_CHECK_ERR_GOTO(!dup->tok_len, LOGMEM(ctx); ret = LY_EMEM, cleanup); + memcpy(dup->tok_len, exp->tok_len + start_idx, used * sizeof *dup->tok_len); + + if (exp->repeat) { + dup->repeat = malloc(used * sizeof *dup->repeat); + LY_CHECK_ERR_GOTO(!dup->repeat, LOGMEM(ctx); ret = LY_EMEM, cleanup); + for (i = start_idx; i <= end_idx; ++i) { + if (!exp->repeat[i]) { + dup->repeat[i - start_idx] = NULL; + } else { + for (j = 0; exp->repeat[i][j]; ++j) {} + /* the ending 0 as well */ + ++j; + + dup->repeat[i - start_idx] = malloc(j * sizeof **dup->repeat); + LY_CHECK_ERR_GOTO(!dup->repeat[i - start_idx], LOGMEM(ctx); ret = LY_EMEM, cleanup); + memcpy(dup->repeat[i - start_idx], exp->repeat[i], j * sizeof **dup->repeat); + } + } + } + } + + dup->used = used; + dup->size = used; + + /* copy only subexpression */ + LY_CHECK_GOTO(ret = lydict_insert(ctx, expr_start, expr_len, &dup->expr), cleanup); + +cleanup: + if (ret) { + lyxp_expr_free(ctx, dup); + } else { + *dup_p = dup; + } + return ret; +} + +/** + * @brief Get the last-added schema node that is currently in the context. + * + * @param[in] set Set to search in. + * @return Last-added schema context node, NULL if no node is in context. + */ +static struct lysc_node * +warn_get_scnode_in_ctx(struct lyxp_set *set) +{ + uint32_t i; + + if (!set || (set->type != LYXP_SET_SCNODE_SET)) { + return NULL; + } + + i = set->used; + do { + --i; + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + /* if there are more, simply return the first found (last added) */ + return set->val.scnodes[i].scnode; + } + } while (i); + + return NULL; +} + +/** + * @brief Test whether a type is numeric - integer type or decimal64. + * + * @param[in] type Type to test. + * @return Boolean value whether @p type is numeric type or not. + */ +static ly_bool +warn_is_numeric_type(struct lysc_type *type) +{ + struct lysc_type_union *uni; + ly_bool ret; + LY_ARRAY_COUNT_TYPE u; + + switch (type->basetype) { + case LY_TYPE_DEC64: + case LY_TYPE_INT8: + case LY_TYPE_UINT8: + case LY_TYPE_INT16: + case LY_TYPE_UINT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT32: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + return 1; + case LY_TYPE_UNION: + uni = (struct lysc_type_union *)type; + LY_ARRAY_FOR(uni->types, u) { + ret = warn_is_numeric_type(uni->types[u]); + if (ret) { + /* found a suitable type */ + return ret; + } + } + /* did not find any suitable type */ + return 0; + case LY_TYPE_LEAFREF: + return warn_is_numeric_type(((struct lysc_type_leafref *)type)->realtype); + default: + return 0; + } +} + +/** + * @brief Test whether a type is string-like - no integers, decimal64 or binary. + * + * @param[in] type Type to test. + * @return Boolean value whether @p type's basetype is string type or not. + */ +static ly_bool +warn_is_string_type(struct lysc_type *type) +{ + struct lysc_type_union *uni; + ly_bool ret; + LY_ARRAY_COUNT_TYPE u; + + switch (type->basetype) { + case LY_TYPE_BITS: + case LY_TYPE_ENUM: + case LY_TYPE_IDENT: + case LY_TYPE_INST: + case LY_TYPE_STRING: + return 1; + case LY_TYPE_UNION: + uni = (struct lysc_type_union *)type; + LY_ARRAY_FOR(uni->types, u) { + ret = warn_is_string_type(uni->types[u]); + if (ret) { + /* found a suitable type */ + return ret; + } + } + /* did not find any suitable type */ + return 0; + case LY_TYPE_LEAFREF: + return warn_is_string_type(((struct lysc_type_leafref *)type)->realtype); + default: + return 0; + } +} + +/** + * @brief Test whether a type is one specific type. + * + * @param[in] type Type to test. + * @param[in] base Expected type. + * @return Boolean value whether the given @p type is of the specific basetype @p base. + */ +static ly_bool +warn_is_specific_type(struct lysc_type *type, LY_DATA_TYPE base) +{ + struct lysc_type_union *uni; + ly_bool ret; + LY_ARRAY_COUNT_TYPE u; + + if (type->basetype == base) { + return 1; + } else if (type->basetype == LY_TYPE_UNION) { + uni = (struct lysc_type_union *)type; + LY_ARRAY_FOR(uni->types, u) { + ret = warn_is_specific_type(uni->types[u], base); + if (ret) { + /* found a suitable type */ + return ret; + } + } + /* did not find any suitable type */ + return 0; + } else if (type->basetype == LY_TYPE_LEAFREF) { + return warn_is_specific_type(((struct lysc_type_leafref *)type)->realtype, base); + } + + return 0; +} + +/** + * @brief Get next type of a (union) type. + * + * @param[in] type Base type. + * @param[in] prev_type Previously returned type. + * @return Next type or NULL. + */ +static struct lysc_type * +warn_is_equal_type_next_type(struct lysc_type *type, struct lysc_type *prev_type) +{ + struct lysc_type_union *uni; + ly_bool found = 0; + LY_ARRAY_COUNT_TYPE u; + + if (type->basetype == LY_TYPE_UNION) { + uni = (struct lysc_type_union *)type; + if (!prev_type) { + return uni->types[0]; + } + LY_ARRAY_FOR(uni->types, u) { + if (found) { + return uni->types[u]; + } + if (prev_type == uni->types[u]) { + found = 1; + } + } + return NULL; + } else { + if (prev_type) { + assert(type == prev_type); + return NULL; + } else { + return type; + } + } +} + +/** + * @brief Test whether 2 types have a common type. + * + * @param[in] type1 First type. + * @param[in] type2 Second type. + * @return 1 if they do, 0 otherwise. + */ +static int +warn_is_equal_type(struct lysc_type *type1, struct lysc_type *type2) +{ + struct lysc_type *t1, *rt1, *t2, *rt2; + + t1 = NULL; + while ((t1 = warn_is_equal_type_next_type(type1, t1))) { + if (t1->basetype == LY_TYPE_LEAFREF) { + rt1 = ((struct lysc_type_leafref *)t1)->realtype; + } else { + rt1 = t1; + } + + t2 = NULL; + while ((t2 = warn_is_equal_type_next_type(type2, t2))) { + if (t2->basetype == LY_TYPE_LEAFREF) { + rt2 = ((struct lysc_type_leafref *)t2)->realtype; + } else { + rt2 = t2; + } + + if (rt2->basetype == rt1->basetype) { + /* match found */ + return 1; + } + } + } + + return 0; +} + +/** + * @brief Print warning with information about the XPath subexpression that caused previous warning. + * + * @param[in] ctx Context for logging. + * @param[in] tok_pos Index of the subexpression in the whole expression. + * @param[in] subexpr Subexpression start. + * @param[in] subexpr_len Length of @p subexpr to print. + * @param[in] cur_scnode Expression context node. + */ +static void +warn_subexpr_log(const struct ly_ctx *ctx, uint32_t tok_pos, const char *subexpr, int subexpr_len, + const struct lysc_node *cur_scnode) +{ + char *path; + + path = lysc_path(cur_scnode, LYSC_PATH_LOG, NULL, 0); + LOGWRN(ctx, "Previous warning generated by XPath subexpression[%" PRIu32 "] \"%.*s\" with context node \"%s\".", + tok_pos, subexpr_len, subexpr, path); + free(path); +} + +/** + * @brief Check both operands of comparison operators. + * + * @param[in] ctx Context for errors. + * @param[in] set1 First operand set. + * @param[in] set2 Second operand set. + * @param[in] numbers_only Whether accept only numbers or other types are fine too (for '=' and '!='). + * @param[in] expr Start of the expression to print with the warning. + * @param[in] tok_pos Token position. + */ +static void +warn_operands(struct ly_ctx *ctx, struct lyxp_set *set1, struct lyxp_set *set2, ly_bool numbers_only, const char *expr, + uint32_t tok_pos) +{ + struct lysc_node_leaf *node1, *node2; + ly_bool leaves = 1, warning = 0; + + node1 = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(set1); + node2 = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(set2); + + if (!node1 && !node2) { + /* no node-sets involved, nothing to do */ + return; + } + + if (node1) { + if (!(node1->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(ctx, "Node type %s \"%s\" used as operand.", lys_nodetype2str(node1->nodetype), node1->name); + warning = 1; + leaves = 0; + } else if (numbers_only && !warn_is_numeric_type(node1->type)) { + LOGWRN(ctx, "Node \"%s\" is not of a numeric type, but used where it was expected.", node1->name); + warning = 1; + } + } + + if (node2) { + if (!(node2->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(ctx, "Node type %s \"%s\" used as operand.", lys_nodetype2str(node2->nodetype), node2->name); + warning = 1; + leaves = 0; + } else if (numbers_only && !warn_is_numeric_type(node2->type)) { + LOGWRN(ctx, "Node \"%s\" is not of a numeric type, but used where it was expected.", node2->name); + warning = 1; + } + } + + if (node1 && node2 && leaves && !numbers_only) { + if ((warn_is_numeric_type(node1->type) && !warn_is_numeric_type(node2->type)) || + (!warn_is_numeric_type(node1->type) && warn_is_numeric_type(node2->type)) || + (!warn_is_numeric_type(node1->type) && !warn_is_numeric_type(node2->type) && + !warn_is_equal_type(node1->type, node2->type))) { + LOGWRN(ctx, "Incompatible types of operands \"%s\" and \"%s\" for comparison.", node1->name, node2->name); + warning = 1; + } + } + + if (warning) { + warn_subexpr_log(ctx, tok_pos, expr + tok_pos, 20, set1->cur_scnode); + } +} + +/** + * @brief Check that a value is valid for a leaf. If not applicable, does nothing. + * + * @param[in] exp Parsed XPath expression. + * @param[in] set Set with the leaf/leaf-list. + * @param[in] val_exp Index of the value (literal/number) in @p exp. + * @param[in] equal_exp Index of the start of the equality expression in @p exp. + * @param[in] last_equal_exp Index of the end of the equality expression in @p exp. + */ +static void +warn_equality_value(const struct lyxp_expr *exp, struct lyxp_set *set, uint32_t val_exp, uint32_t equal_exp, + uint32_t last_equal_exp) +{ + struct lysc_node *scnode; + struct lysc_type *type; + char *value; + struct lyd_value storage; + LY_ERR rc; + struct ly_err_item *err = NULL; + + if ((scnode = warn_get_scnode_in_ctx(set)) && (scnode->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && + ((exp->tokens[val_exp] == LYXP_TOKEN_LITERAL) || (exp->tokens[val_exp] == LYXP_TOKEN_NUMBER))) { + /* check that the node can have the specified value */ + if (exp->tokens[val_exp] == LYXP_TOKEN_LITERAL) { + value = strndup(exp->expr + exp->tok_pos[val_exp] + 1, exp->tok_len[val_exp] - 2); + } else { + value = strndup(exp->expr + exp->tok_pos[val_exp], exp->tok_len[val_exp]); + } + if (!value) { + LOGMEM(set->ctx); + return; + } + + if ((((struct lysc_node_leaf *)scnode)->type->basetype == LY_TYPE_IDENT) && !strchr(value, ':')) { + LOGWRN(set->ctx, "Identityref \"%s\" comparison with identity \"%s\" without prefix, consider adding" + " a prefix or best using \"derived-from(-or-self)()\" functions.", scnode->name, value); + warn_subexpr_log(set->ctx, exp->tok_pos[equal_exp], exp->expr + exp->tok_pos[equal_exp], + (exp->tok_pos[last_equal_exp] - exp->tok_pos[equal_exp]) + exp->tok_len[last_equal_exp], + set->cur_scnode); + } + + type = ((struct lysc_node_leaf *)scnode)->type; + if (type->basetype != LY_TYPE_IDENT) { + rc = type->plugin->store(set->ctx, type, value, strlen(value), 0, set->format, set->prefix_data, + LYD_HINT_DATA, scnode, &storage, NULL, &err); + if (rc == LY_EINCOMPLETE) { + rc = LY_SUCCESS; + } + + if (err) { + LOGWRN(set->ctx, "Invalid value \"%s\" which does not fit the type (%s).", value, err->msg); + ly_err_free(err); + } else if (rc != LY_SUCCESS) { + LOGWRN(set->ctx, "Invalid value \"%s\" which does not fit the type.", value); + } + if (rc != LY_SUCCESS) { + warn_subexpr_log(set->ctx, exp->tok_pos[equal_exp], exp->expr + exp->tok_pos[equal_exp], + (exp->tok_pos[last_equal_exp] - exp->tok_pos[equal_exp]) + exp->tok_len[last_equal_exp], + set->cur_scnode); + } else { + type->plugin->free(set->ctx, &storage); + } + } + free(value); + } +} + +/* + * XPath functions + */ + +/** + * @brief Execute the YANG 1.1 bit-is-set(node-set, string) function. Returns LYXP_SET_BOOLEAN + * depending on whether the first node bit value from the second argument is set. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_bit_is_set(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lyd_node_term *leaf; + struct lysc_node_leaf *sleaf; + struct lyd_value_bits *bits; + LY_ERR rc = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_BITS)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"bits\".", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "bit-is-set(node-set, string)"); + return LY_EVALID; + } + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + set_fill_boolean(set, 0); + if (args[0]->used) { + leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node; + if ((leaf->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (leaf->value.realtype->basetype == LY_TYPE_BITS)) { + LYD_VALUE_GET(&leaf->value, bits); + LY_ARRAY_FOR(bits->items, u) { + if (!strcmp(bits->items[u]->name, args[1]->val.str)) { + set_fill_boolean(set, 1); + break; + } + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath boolean(object) function. Returns LYXP_SET_BOOLEAN + * with the argument converted to boolean. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_boolean(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_BOOLEAN); + LY_CHECK_RET(rc); + set_fill_set(set, args[0]); + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath ceiling(number) function. Returns LYXP_SET_NUMBER + * with the first argument rounded up to the nearest integer. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_ceiling(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_DEC64)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"decimal64\".", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + if ((long long)args[0]->val.num != args[0]->val.num) { + set_fill_number(set, ((long long)args[0]->val.num) + 1); + } else { + set_fill_number(set, args[0]->val.num); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath concat(string, string, string*) function. + * Returns LYXP_SET_STRING with the concatenation of all the arguments. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_concat(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + uint32_t i; + char *str = NULL; + size_t used = 1; + LY_ERR rc = LY_SUCCESS; + struct lysc_node_leaf *sleaf; + + if (options & LYXP_SCNODE_ALL) { + for (i = 0; i < arg_count; ++i) { + if ((args[i]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[i]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #%u of %s is a %s node \"%s\".", + i + 1, __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #%u of %s is node \"%s\", not of string-type.", i + 1, __func__, sleaf->name); + } + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + for (i = 0; i < arg_count; ++i) { + rc = lyxp_set_cast(args[i], LYXP_SET_STRING); + if (rc != LY_SUCCESS) { + free(str); + return rc; + } + + str = ly_realloc(str, (used + strlen(args[i]->val.str)) * sizeof(char)); + LY_CHECK_ERR_RET(!str, LOGMEM(set->ctx), LY_EMEM); + strcpy(str + used - 1, args[i]->val.str); + used += strlen(args[i]->val.str); + } + + /* free, kind of */ + lyxp_set_free_content(set); + set->type = LYXP_SET_STRING; + set->val.str = str; + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath contains(string, string) function. + * Returns LYXP_SET_BOOLEAN whether the second argument can + * be found in the first or not. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_contains(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + if (strstr(args[0]->val.str, args[1]->val.str)) { + set_fill_boolean(set, 1); + } else { + set_fill_boolean(set, 0); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath count(node-set) function. Returns LYXP_SET_NUMBER + * with the size of the node-set from the argument. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_count(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return rc; + } + + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "count(node-set)"); + return LY_EVALID; + } + + set_fill_number(set, args[0]->used); + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath current() function. Returns LYXP_SET_NODE_SET + * with the context with the intial node. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_current(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + if (arg_count || args) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGCOUNT, arg_count, LY_PRI_LENSTR("current()")); + return LY_EVALID; + } + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + + if (set->cur_scnode) { + LY_CHECK_RET(set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL)); + } else { + /* root node */ + LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL)); + } + } else { + lyxp_set_free_content(set); + + if (set->cur_node) { + /* position is filled later */ + set_insert_node(set, set->cur_node, 0, LYXP_NODE_ELEM, 0); + } else { + /* root node */ + set_insert_node(set, NULL, 0, set->root_type, 0); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the YANG 1.1 deref(node-set) function. Returns LYXP_SET_NODE_SET with either + * leafref or instance-identifier target node(s). + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lyd_node_term *leaf; + struct lysc_node_leaf *sleaf = NULL; + struct lysc_type_leafref *lref; + const struct lysc_node *target; + struct ly_path *p; + struct lyd_node *node; + char *errmsg = NULL; + uint8_t oper; + LY_ERR r; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & LYD_NODE_TERM)) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_LEAFREF) && + !warn_is_specific_type(sleaf->type, LY_TYPE_INST)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"leafref\" nor \"instance-identifier\".", + __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + if (sleaf && (sleaf->nodetype & LYD_NODE_TERM) && (sleaf->type->basetype == LY_TYPE_LEAFREF)) { + lref = (struct lysc_type_leafref *)sleaf->type; + oper = (sleaf->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT; + + /* it was already evaluated on schema, it must succeed */ + r = ly_path_compile_leafref(set->ctx, &sleaf->node, NULL, lref->path, oper, LY_PATH_TARGET_MANY, + LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, &p); + if (!r) { + /* get the target node */ + target = p[LY_ARRAY_COUNT(p) - 1].node; + ly_path_free(set->ctx, p); + + LY_CHECK_RET(set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL)); + } /* else the target was found before but is disabled so it was removed */ + } + + return LY_SUCCESS; + } + + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "deref(node-set)"); + return LY_EVALID; + } + + lyxp_set_free_content(set); + if (args[0]->used) { + leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node; + sleaf = (struct lysc_node_leaf *)leaf->schema; + if (sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + if (sleaf->type->basetype == LY_TYPE_LEAFREF) { + /* find leafref target */ + if (lyplg_type_resolve_leafref((struct lysc_type_leafref *)sleaf->type, &leaf->node, &leaf->value, set->tree, + &node, &errmsg)) { + LOGERR(set->ctx, LY_EVALID, "%s", errmsg); + free(errmsg); + return LY_EVALID; + } + } else { + assert(sleaf->type->basetype == LY_TYPE_INST); + if (ly_path_eval(leaf->value.target, set->tree, &node)) { + LOGERR(set->ctx, LY_EVALID, "Invalid instance-identifier \"%s\" value - required instance not found.", + lyd_get_value(&leaf->node)); + return LY_EVALID; + } + } + + /* insert it */ + set_insert_node(set, node, 0, LYXP_NODE_ELEM, 0); + } + } + + return LY_SUCCESS; +} + +static LY_ERR +xpath_derived_(struct lyxp_set **args, struct lyxp_set *set, uint32_t options, ly_bool self_match, const char *func) +{ + uint32_t i, id_len; + LY_ARRAY_COUNT_TYPE u; + struct lyd_node_term *leaf; + struct lysc_node_leaf *sleaf; + struct lyd_meta *meta; + struct lyd_value *val; + const struct lys_module *mod; + const char *id_name; + struct lysc_ident *id; + LY_ERR rc = LY_SUCCESS; + ly_bool found; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", func); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", func, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_IDENT)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"identityref\".", func, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", func, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", func, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "derived-from(-or-self)(node-set, string)"); + return LY_EVALID; + } + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + /* parse the identity */ + id_name = args[1]->val.str; + id_len = strlen(id_name); + rc = moveto_resolve_model(&id_name, &id_len, set, set->cur_node ? set->cur_node->schema : NULL, &mod); + LY_CHECK_RET(rc); + if (!mod) { + LOGVAL(set->ctx, LYVE_XPATH, "Identity \"%.*s\" without a prefix.", (int)id_len, id_name); + return LY_EVALID; + } + + /* find the identity */ + found = 0; + LY_ARRAY_FOR(mod->identities, u) { + if (!ly_strncmp(mod->identities[u].name, id_name, id_len)) { + /* we have match */ + found = 1; + break; + } + } + if (!found) { + LOGVAL(set->ctx, LYVE_XPATH, "Identity \"%.*s\" not found in module \"%s\".", (int)id_len, id_name, mod->name); + return LY_EVALID; + } + id = &mod->identities[u]; + + set_fill_boolean(set, 0); + found = 0; + for (i = 0; i < args[0]->used; ++i) { + if ((args[0]->val.nodes[i].type != LYXP_NODE_ELEM) && (args[0]->val.nodes[i].type != LYXP_NODE_META)) { + continue; + } + + if (args[0]->val.nodes[i].type == LYXP_NODE_ELEM) { + leaf = (struct lyd_node_term *)args[0]->val.nodes[i].node; + sleaf = (struct lysc_node_leaf *)leaf->schema; + val = &leaf->value; + if (!(sleaf->nodetype & LYD_NODE_TERM) || (leaf->value.realtype->basetype != LY_TYPE_IDENT)) { + /* uninteresting */ + continue; + } + } else { + meta = args[0]->val.meta[i].meta; + val = &meta->value; + if (val->realtype->basetype != LY_TYPE_IDENT) { + /* uninteresting */ + continue; + } + } + + /* check the identity itself */ + if (self_match && (id == val->ident)) { + set_fill_boolean(set, 1); + found = 1; + } + if (!found && !lyplg_type_identity_isderived(id, val->ident)) { + set_fill_boolean(set, 1); + found = 1; + } + + if (found) { + break; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the YANG 1.1 derived-from(node-set, string) function. Returns LYXP_SET_BOOLEAN depending + * on whether the first argument nodes contain a node of an identity derived from the second + * argument identity. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_derived_from(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + return xpath_derived_(args, set, options, 0, __func__); +} + +/** + * @brief Execute the YANG 1.1 derived-from-or-self(node-set, string) function. Returns LYXP_SET_BOOLEAN depending + * on whether the first argument nodes contain a node of an identity that either is or is derived from + * the second argument identity. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_derived_from_or_self(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + return xpath_derived_(args, set, options, 1, __func__); +} + +/** + * @brief Execute the YANG 1.1 enum-value(node-set) function. Returns LYXP_SET_NUMBER + * with the integer value of the first node's enum value, otherwise NaN. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_enum_value(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lyd_node_term *leaf; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_ENUM)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"enumeration\".", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "enum-value(node-set)"); + return LY_EVALID; + } + + set_fill_number(set, NAN); + if (args[0]->used) { + leaf = (struct lyd_node_term *)args[0]->val.nodes[0].node; + sleaf = (struct lysc_node_leaf *)leaf->schema; + if ((sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST)) && (sleaf->type->basetype == LY_TYPE_ENUM)) { + set_fill_number(set, leaf->value.enum_item->value); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath false() function. Returns LYXP_SET_BOOLEAN + * with false value. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_false(struct lyxp_set **UNUSED(args), uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + set_fill_boolean(set, 0); + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath floor(number) function. Returns LYXP_SET_NUMBER + * with the first argument floored (truncated). + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_floor(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t UNUSED(options)) +{ + LY_ERR rc; + + rc = lyxp_set_cast(args[0], LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + if (isfinite(args[0]->val.num)) { + set_fill_number(set, (long long)args[0]->val.num); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath lang(string) function. Returns LYXP_SET_BOOLEAN + * whether the language of the text matches the one from the argument. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_lang(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + const struct lyd_node *node; + struct lysc_node_leaf *sleaf; + struct lyd_meta *meta = NULL; + const char *val; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "lang(string)"); + return LY_EVALID; + } else if (!set->used) { + set_fill_boolean(set, 0); + return LY_SUCCESS; + } + + switch (set->val.nodes[0].type) { + case LYXP_NODE_ELEM: + case LYXP_NODE_TEXT: + node = set->val.nodes[0].node; + break; + case LYXP_NODE_META: + node = set->val.meta[0].meta->parent; + break; + default: + /* nothing to do with roots */ + set_fill_boolean(set, 0); + return LY_SUCCESS; + } + + /* find lang metadata */ + for ( ; node; node = lyd_parent(node)) { + for (meta = node->meta; meta; meta = meta->next) { + /* annotations */ + if (meta->name && !strcmp(meta->name, "lang") && !strcmp(meta->annotation->module->name, "xml")) { + break; + } + } + + if (meta) { + break; + } + } + + /* compare languages */ + if (!meta) { + set_fill_boolean(set, 0); + } else { + uint64_t i; + + val = lyd_get_meta_value(meta); + for (i = 0; args[0]->val.str[i]; ++i) { + if (tolower(args[0]->val.str[i]) != tolower(val[i])) { + set_fill_boolean(set, 0); + break; + } + } + if (!args[0]->val.str[i]) { + if (!val[i] || (val[i] == '-')) { + set_fill_boolean(set, 1); + } else { + set_fill_boolean(set, 0); + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath last() function. Returns LYXP_SET_NUMBER + * with the context size. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_last(struct lyxp_set **UNUSED(args), uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "last()"); + return LY_EVALID; + } else if (!set->used) { + set_fill_number(set, 0); + return LY_SUCCESS; + } + + set_fill_number(set, set->ctx_size); + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath local-name(node-set?) function. Returns LYXP_SET_STRING + * with the node name without namespace from the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_local_name(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + struct lyxp_set_node *item; + + /* suppress unused variable warning */ + (void)options; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + if (arg_count) { + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), + "local-name(node-set?)"); + return LY_EVALID; + } else if (!args[0]->used) { + set_fill_string(set, "", 0); + return LY_SUCCESS; + } + + /* we need the set sorted, it affects the result */ + assert(!set_sort(args[0])); + + item = &args[0]->val.nodes[0]; + } else { + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "local-name(node-set?)"); + return LY_EVALID; + } else if (!set->used) { + set_fill_string(set, "", 0); + return LY_SUCCESS; + } + + /* we need the set sorted, it affects the result */ + assert(!set_sort(set)); + + item = &set->val.nodes[0]; + } + + switch (item->type) { + case LYXP_NODE_NONE: + LOGINT_RET(set->ctx); + case LYXP_NODE_ROOT: + case LYXP_NODE_ROOT_CONFIG: + case LYXP_NODE_TEXT: + set_fill_string(set, "", 0); + break; + case LYXP_NODE_ELEM: + set_fill_string(set, item->node->schema->name, strlen(item->node->schema->name)); + break; + case LYXP_NODE_META: + set_fill_string(set, ((struct lyd_meta *)item->node)->name, strlen(((struct lyd_meta *)item->node)->name)); + break; + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath name(node-set?) function. Returns LYXP_SET_STRING + * with the node name fully qualified (with namespace) from the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_name(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + struct lyxp_set_node *item; + struct lys_module *mod = NULL; + char *str; + const char *name = NULL; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + if (arg_count) { + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "name(node-set?)"); + return LY_EVALID; + } else if (!args[0]->used) { + set_fill_string(set, "", 0); + return LY_SUCCESS; + } + + /* we need the set sorted, it affects the result */ + assert(!set_sort(args[0])); + + item = &args[0]->val.nodes[0]; + } else { + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "name(node-set?)"); + return LY_EVALID; + } else if (!set->used) { + set_fill_string(set, "", 0); + return LY_SUCCESS; + } + + /* we need the set sorted, it affects the result */ + assert(!set_sort(set)); + + item = &set->val.nodes[0]; + } + + switch (item->type) { + case LYXP_NODE_NONE: + LOGINT_RET(set->ctx); + case LYXP_NODE_ROOT: + case LYXP_NODE_ROOT_CONFIG: + case LYXP_NODE_TEXT: + /* keep NULL */ + break; + case LYXP_NODE_ELEM: + mod = item->node->schema->module; + name = item->node->schema->name; + break; + case LYXP_NODE_META: + mod = ((struct lyd_meta *)item->node)->annotation->module; + name = ((struct lyd_meta *)item->node)->name; + break; + } + + if (mod && name) { + int rc = asprintf(&str, "%s:%s", ly_get_prefix(mod, set->format, set->prefix_data), name); + + LY_CHECK_ERR_RET(rc == -1, LOGMEM(set->ctx), LY_EMEM); + set_fill_string(set, str, strlen(str)); + free(str); + } else { + set_fill_string(set, "", 0); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath namespace-uri(node-set?) function. Returns LYXP_SET_STRING + * with the namespace of the node from the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINVAL for wrong arguments on schema) + */ +static LY_ERR +xpath_namespace_uri(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + struct lyxp_set_node *item; + struct lys_module *mod; + + /* suppress unused variable warning */ + (void)options; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return LY_SUCCESS; + } + + if (arg_count) { + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), + "namespace-uri(node-set?)"); + return LY_EVALID; + } else if (!args[0]->used) { + set_fill_string(set, "", 0); + return LY_SUCCESS; + } + + /* we need the set sorted, it affects the result */ + assert(!set_sort(args[0])); + + item = &args[0]->val.nodes[0]; + } else { + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "namespace-uri(node-set?)"); + return LY_EVALID; + } else if (!set->used) { + set_fill_string(set, "", 0); + return LY_SUCCESS; + } + + /* we need the set sorted, it affects the result */ + assert(!set_sort(set)); + + item = &set->val.nodes[0]; + } + + switch (item->type) { + case LYXP_NODE_NONE: + LOGINT_RET(set->ctx); + case LYXP_NODE_ROOT: + case LYXP_NODE_ROOT_CONFIG: + case LYXP_NODE_TEXT: + set_fill_string(set, "", 0); + break; + case LYXP_NODE_ELEM: + case LYXP_NODE_META: + if (item->type == LYXP_NODE_ELEM) { + mod = item->node->schema->module; + } else { /* LYXP_NODE_META */ + /* annotations */ + mod = ((struct lyd_meta *)item->node)->annotation->module; + } + + set_fill_string(set, mod->ns, strlen(mod->ns)); + break; + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath normalize-space(string?) function. Returns LYXP_SET_STRING + * with normalized value (no leading, trailing, double white spaces) of the node + * from the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_normalize_space(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + uint32_t i, new_used; + char *new; + ly_bool have_spaces = 0, space_before = 0; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (arg_count && (args[0]->type == LYXP_SET_SCNODE_SET) && + (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + if (arg_count) { + set_fill_set(set, args[0]); + } + rc = lyxp_set_cast(set, LYXP_SET_STRING); + LY_CHECK_RET(rc); + + /* is there any normalization necessary? */ + for (i = 0; set->val.str[i]; ++i) { + if (is_xmlws(set->val.str[i])) { + if ((i == 0) || space_before || (!set->val.str[i + 1])) { + have_spaces = 1; + break; + } + space_before = 1; + } else { + space_before = 0; + } + } + + /* yep, there is */ + if (have_spaces) { + /* it's enough, at least one character will go, makes space for ending '\0' */ + new = malloc(strlen(set->val.str) * sizeof(char)); + LY_CHECK_ERR_RET(!new, LOGMEM(set->ctx), LY_EMEM); + new_used = 0; + + space_before = 0; + for (i = 0; set->val.str[i]; ++i) { + if (is_xmlws(set->val.str[i])) { + if ((i == 0) || space_before) { + space_before = 1; + continue; + } else { + space_before = 1; + } + } else { + space_before = 0; + } + + new[new_used] = (space_before ? ' ' : set->val.str[i]); + ++new_used; + } + + /* at worst there is one trailing space now */ + if (new_used && is_xmlws(new[new_used - 1])) { + --new_used; + } + + new = ly_realloc(new, (new_used + 1) * sizeof(char)); + LY_CHECK_ERR_RET(!new, LOGMEM(set->ctx), LY_EMEM); + new[new_used] = '\0'; + + free(set->val.str); + set->val.str = new; + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath not(boolean) function. Returns LYXP_SET_BOOLEAN + * with the argument converted to boolean and logically inverted. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_not(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + lyxp_set_cast(args[0], LYXP_SET_BOOLEAN); + if (args[0]->val.bln) { + set_fill_boolean(set, 0); + } else { + set_fill_boolean(set, 1); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath number(object?) function. Returns LYXP_SET_NUMBER + * with the number representation of either the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_number(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return LY_SUCCESS; + } + + if (arg_count) { + rc = lyxp_set_cast(args[0], LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + set_fill_set(set, args[0]); + } else { + rc = lyxp_set_cast(set, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath position() function. Returns LYXP_SET_NUMBER + * with the context position. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_position(struct lyxp_set **UNUSED(args), uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "position()"); + return LY_EVALID; + } else if (!set->used) { + set_fill_number(set, 0); + return LY_SUCCESS; + } + + set_fill_number(set, set->ctx_pos); + + /* UNUSED in 'Release' build type */ + (void)options; + return LY_SUCCESS; +} + +/** + * @brief Execute the YANG 1.1 re-match(string, string) function. Returns LYXP_SET_BOOLEAN + * depending on whether the second argument regex matches the first argument string. For details refer to + * YANG 1.1 RFC section 10.2.1. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_re_match(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lysc_pattern **patterns = NULL, **pattern; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + struct ly_err_item *err; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + LY_ARRAY_NEW_RET(set->ctx, patterns, pattern, LY_EMEM); + *pattern = calloc(1, sizeof **pattern); + LOG_LOCSET(NULL, set->cur_node, NULL, NULL); + rc = lys_compile_type_pattern_check(set->ctx, args[1]->val.str, &(*pattern)->code); + if (set->cur_node) { + LOG_LOCBACK(0, 1, 0, 0); + } + if (rc != LY_SUCCESS) { + LY_ARRAY_FREE(patterns); + return rc; + } + + rc = lyplg_type_validate_patterns(patterns, args[0]->val.str, strlen(args[0]->val.str), &err); + pcre2_code_free((*pattern)->code); + free(*pattern); + LY_ARRAY_FREE(patterns); + if (rc && (rc != LY_EVALID)) { + ly_err_print(set->ctx, err); + ly_err_free(err); + return rc; + } + + if (rc == LY_EVALID) { + ly_err_free(err); + set_fill_boolean(set, 0); + } else { + set_fill_boolean(set, 1); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath round(number) function. Returns LYXP_SET_NUMBER + * with the rounded first argument. For details refer to + * http://www.w3.org/TR/1999/REC-xpath-19991116/#function-round. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_round(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_DEC64)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"decimal64\".", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + + /* cover only the cases where floor can't be used */ + if ((args[0]->val.num == -0.0f) || ((args[0]->val.num < 0) && (args[0]->val.num >= -0.5))) { + set_fill_number(set, -0.0f); + } else { + args[0]->val.num += 0.5; + rc = xpath_floor(args, 1, args[0], options); + LY_CHECK_RET(rc); + set_fill_number(set, args[0]->val.num); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath starts-with(string, string) function. + * Returns LYXP_SET_BOOLEAN whether the second argument is + * the prefix of the first or not. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_starts_with(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + if (strncmp(args[0]->val.str, args[1]->val.str, strlen(args[1]->val.str))) { + set_fill_boolean(set, 0); + } else { + set_fill_boolean(set, 1); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath string(object?) function. Returns LYXP_SET_STRING + * with the string representation of either the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_string(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return LY_SUCCESS; + } + + if (arg_count) { + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + set_fill_set(set, args[0]); + } else { + rc = lyxp_set_cast(set, LYXP_SET_STRING); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath string-length(string?) function. Returns LYXP_SET_NUMBER + * with the length of the string in either the argument or the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_string_length(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (arg_count && (args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + if (!arg_count && (set->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(set))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #0 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #0 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + if (arg_count) { + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + set_fill_number(set, strlen(args[0]->val.str)); + } else { + rc = lyxp_set_cast(set, LYXP_SET_STRING); + LY_CHECK_RET(rc); + set_fill_number(set, strlen(set->val.str)); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath substring(string, number, number?) function. + * Returns LYXP_SET_STRING substring of the first argument starting + * on the second argument index ending on the third argument index, + * indexed from 1. For exact definition refer to + * http://www.w3.org/TR/1999/REC-xpath-19991116/#function-substring. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_substring(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) +{ + int64_t start; + int32_t len; + uint32_t str_start, str_len, pos; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_numeric_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of numeric type.", __func__, sleaf->name); + } + } + + if ((arg_count == 3) && (args[2]->type == LYXP_SET_SCNODE_SET) && + (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[2]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #3 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_numeric_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #3 of %s is node \"%s\", not of numeric type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + /* start */ + if (xpath_round(&args[1], 1, args[1], options)) { + return -1; + } + if (isfinite(args[1]->val.num)) { + start = args[1]->val.num - 1; + } else if (isinf(args[1]->val.num) && signbit(args[1]->val.num)) { + start = INT32_MIN; + } else { + start = INT32_MAX; + } + + /* len */ + if (arg_count == 3) { + rc = xpath_round(&args[2], 1, args[2], options); + LY_CHECK_RET(rc); + if (isnan(args[2]->val.num) || signbit(args[2]->val.num)) { + len = 0; + } else if (isfinite(args[2]->val.num)) { + len = args[2]->val.num; + } else { + len = INT32_MAX; + } + } else { + len = INT32_MAX; + } + + /* find matching character positions */ + str_start = 0; + str_len = 0; + for (pos = 0; args[0]->val.str[pos]; ++pos) { + if (pos < start) { + ++str_start; + } else if (pos < start + len) { + ++str_len; + } else { + break; + } + } + + set_fill_string(set, args[0]->val.str + str_start, str_len); + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath substring-after(string, string) function. + * Returns LYXP_SET_STRING with the string succeeding the occurance + * of the second argument in the first or an empty string. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_substring_after(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + char *ptr; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + ptr = strstr(args[0]->val.str, args[1]->val.str); + if (ptr) { + set_fill_string(set, ptr + strlen(args[1]->val.str), strlen(ptr + strlen(args[1]->val.str))); + } else { + set_fill_string(set, "", 0); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath substring-before(string, string) function. + * Returns LYXP_SET_STRING with the string preceding the occurance + * of the second argument in the first or an empty string. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_substring_before(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + char *ptr; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + ptr = strstr(args[0]->val.str, args[1]->val.str); + if (ptr) { + set_fill_string(set, args[0]->val.str, ptr - args[0]->val.str); + } else { + set_fill_string(set, "", 0); + } + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath sum(node-set) function. Returns LYXP_SET_NUMBER + * with the sum of all the nodes in the context. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_sum(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + long double num; + char *str; + uint32_t i; + struct lyxp_set set_item; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type == LYXP_SET_SCNODE_SET) { + for (i = 0; i < args[0]->used; ++i) { + if (args[0]->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + sleaf = (struct lysc_node_leaf *)args[0]->val.scnodes[i].scnode; + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, + lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_numeric_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of numeric type.", __func__, sleaf->name); + } + } + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + set_fill_number(set, 0); + + if (args[0]->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INARGTYPE, 1, print_set_type(args[0]), "sum(node-set)"); + return LY_EVALID; + } else if (!args[0]->used) { + return LY_SUCCESS; + } + + set_init(&set_item, set); + + set_item.type = LYXP_SET_NODE_SET; + set_item.val.nodes = calloc(1, sizeof *set_item.val.nodes); + LY_CHECK_ERR_RET(!set_item.val.nodes, LOGMEM(set->ctx), LY_EMEM); + + set_item.used = 1; + set_item.size = 1; + + for (i = 0; i < args[0]->used; ++i) { + set_item.val.nodes[0] = args[0]->val.nodes[i]; + + rc = cast_node_set_to_string(&set_item, &str); + LY_CHECK_RET(rc); + num = cast_string_to_number(str); + free(str); + set->val.num += num; + } + + free(set_item.val.nodes); + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath translate(string, string, string) function. + * Returns LYXP_SET_STRING with the first argument with the characters + * from the second argument replaced by those on the corresponding + * positions in the third argument. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_translate(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + uint32_t i, j, new_used; + char *new; + ly_bool have_removed; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if ((args[0]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[1]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[1]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #2 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #2 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + + if ((args[2]->type == LYXP_SET_SCNODE_SET) && (sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[2]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #3 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), sleaf->name); + } else if (!warn_is_string_type(sleaf->type)) { + LOGWRN(set->ctx, "Argument #3 of %s is node \"%s\", not of string-type.", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } + + rc = lyxp_set_cast(args[0], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[1], LYXP_SET_STRING); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(args[2], LYXP_SET_STRING); + LY_CHECK_RET(rc); + + new = malloc((strlen(args[0]->val.str) + 1) * sizeof(char)); + LY_CHECK_ERR_RET(!new, LOGMEM(set->ctx), LY_EMEM); + new_used = 0; + + have_removed = 0; + for (i = 0; args[0]->val.str[i]; ++i) { + ly_bool found = 0; + + for (j = 0; args[1]->val.str[j]; ++j) { + if (args[0]->val.str[i] == args[1]->val.str[j]) { + /* removing this char */ + if (j >= strlen(args[2]->val.str)) { + have_removed = 1; + found = 1; + break; + } + /* replacing this char */ + new[new_used] = args[2]->val.str[j]; + ++new_used; + found = 1; + break; + } + } + + /* copying this char */ + if (!found) { + new[new_used] = args[0]->val.str[i]; + ++new_used; + } + } + + if (have_removed) { + new = ly_realloc(new, (new_used + 1) * sizeof(char)); + LY_CHECK_ERR_RET(!new, LOGMEM(set->ctx), LY_EMEM); + } + new[new_used] = '\0'; + + lyxp_set_free_content(set); + set->type = LYXP_SET_STRING; + set->val.str = new; + + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath true() function. Returns LYXP_SET_BOOLEAN + * with true value. + * + * @param[in] args Array of arguments. + * @param[in] arg_count Count of elements in @p args. + * @param[in,out] set Context and result set at the same time. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_true(struct lyxp_set **UNUSED(args), uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) +{ + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + return LY_SUCCESS; + } + + set_fill_boolean(set, 1); + return LY_SUCCESS; +} + +/** + * @brief Execute the XPath node() processing instruction (node type). Returns LYXP_SET_NODE_SET + * with only nodes from the context. + * + * @param[in,out] set Context and result set at the same time. + * @param[in] axis Axis to search on. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_pi_node(struct lyxp_set *set, enum lyxp_axis axis, uint32_t options) +{ + if (options & LYXP_SCNODE_ALL) { + return moveto_scnode(set, NULL, NULL, axis, options); + } + + if (set->type != LYXP_SET_NODE_SET) { + lyxp_set_free_content(set); + return LY_SUCCESS; + } + + /* just like moving to a node with no restrictions */ + return moveto_node(set, NULL, NULL, axis, options); +} + +/** + * @brief Execute the XPath text() processing instruction (node type). Returns LYXP_SET_NODE_SET + * with the text content of the nodes in the context. + * + * @param[in,out] set Context and result set at the same time. + * @param[in] axis Axis to search on. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +xpath_pi_text(struct lyxp_set *set, enum lyxp_axis axis, uint32_t options) +{ + uint32_t i; + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INCTX, print_set_type(set), "text()"); + return LY_EVALID; + } + + if (axis != LYXP_AXIS_CHILD) { + /* even following and preceding axescan return text nodes, but whatever */ + lyxp_set_free_content(set); + return LY_SUCCESS; + } + + for (i = 0; i < set->used; ++i) { + switch (set->val.nodes[i].type) { + case LYXP_NODE_NONE: + LOGINT_RET(set->ctx); + case LYXP_NODE_ELEM: + if (set->val.nodes[i].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + set->val.nodes[i].type = LYXP_NODE_TEXT; + break; + } + /* fall through */ + case LYXP_NODE_ROOT: + case LYXP_NODE_ROOT_CONFIG: + case LYXP_NODE_TEXT: + case LYXP_NODE_META: + set_remove_node_none(set, i); + break; + } + } + set_remove_nodes_none(set); + + return LY_SUCCESS; +} + +/** + * @brief Skip prefix and return corresponding model if there is a prefix. Logs directly. + * + * XPath @p set is expected to be a (sc)node set! + * + * @param[in,out] qname Qualified node name. If includes prefix, it is skipped. + * @param[in,out] qname_len Length of @p qname, is updated accordingly. + * @param[in] set Set with general XPath context. + * @param[in] ctx_scnode Context node to inherit module for unprefixed node for ::LY_PREF_JSON. + * @param[out] moveto_mod Expected module of a matching node. + * @return LY_ERR + */ +static LY_ERR +moveto_resolve_model(const char **qname, uint32_t *qname_len, const struct lyxp_set *set, + const struct lysc_node *ctx_scnode, const struct lys_module **moveto_mod) +{ + const struct lys_module *mod = NULL; + const char *ptr; + size_t pref_len; + + assert((set->type == LYXP_SET_NODE_SET) || (set->type == LYXP_SET_SCNODE_SET)); + + if ((ptr = ly_strnchr(*qname, ':', *qname_len))) { + /* specific module */ + pref_len = ptr - *qname; + mod = ly_resolve_prefix(set->ctx, *qname, pref_len, set->format, set->prefix_data); + + /* check for errors and non-implemented modules, as they are not valid */ + if (!mod || !mod->implemented) { + LOGVAL(set->ctx, LY_VCODE_XP_INMOD, pref_len, *qname); + return LY_EVALID; + } + + *qname += pref_len + 1; + *qname_len -= pref_len + 1; + } else if (((*qname)[0] == '*') && (*qname_len == 1)) { + /* all modules - special case */ + mod = NULL; + } else { + switch (set->format) { + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + /* current module */ + mod = set->cur_mod; + break; + case LY_VALUE_CANON: + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* inherit parent (context node) module */ + if (ctx_scnode) { + mod = ctx_scnode->module; + } else { + mod = NULL; + } + break; + case LY_VALUE_XML: + /* all nodes need to be prefixed */ + LOGVAL(set->ctx, LYVE_DATA, "Non-prefixed node \"%.*s\" in XML xpath found.", *qname_len, *qname); + return LY_EVALID; + } + } + + *moveto_mod = mod; + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to the root. Handles absolute path. + * Result is LYXP_SET_NODE_SET. + * + * @param[in,out] set Set to use. + * @param[in] options Xpath options. + * @return LY_ERR value. + */ +static LY_ERR +moveto_root(struct lyxp_set *set, uint32_t options) +{ + assert(!(options & LYXP_SKIP_EXPR)); + + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL)); + } else { + set->type = LYXP_SET_NODE_SET; + set->used = 0; + set_insert_node(set, NULL, 0, set->root_type, 0); + set->non_child_axis = 0; + } + + return LY_SUCCESS; +} + +/** + * @brief Check @p node as a part of NameTest processing. + * + * @param[in] node Node to check. + * @param[in] node_type Node type of @p node. + * @param[in] set Set to read general context from. + * @param[in] node_name Node name in the dictionary to move to, NULL for any node. + * @param[in] moveto_mod Expected module of the node, NULL for no prefix. + * @param[in] options XPath options. + * @return LY_ERR (LY_ENOT if node does not match, LY_EINCOMPLETE on unresolved when, + * LY_EINVAL if neither node nor any children match) + */ +static LY_ERR +moveto_node_check(const struct lyd_node *node, enum lyxp_node_type node_type, const struct lyxp_set *set, + const char *node_name, const struct lys_module *moveto_mod, uint32_t options) +{ + if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) { + assert(node_type == set->root_type); + + if (node_name || moveto_mod) { + /* root will not match a specific node */ + return LY_ENOT; + } + return LY_SUCCESS; + } else if (node_type != LYXP_NODE_ELEM) { + /* other types will not match */ + return LY_ENOT; + } + + if (!node->schema) { + /* opaque node never matches */ + return LY_ENOT; + } + + /* module check */ + if (moveto_mod) { + if ((set->ctx == LYD_CTX(node)) && (node->schema->module != moveto_mod)) { + return LY_ENOT; + } else if ((set->ctx != LYD_CTX(node)) && strcmp(node->schema->module->name, moveto_mod->name)) { + return LY_ENOT; + } + } + + /* context check */ + if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (node->schema->flags & LYS_CONFIG_R)) { + return LY_EINVAL; + } else if (set->context_op && (node->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && + (node->schema != set->context_op)) { + return LY_EINVAL; + } + + /* name check */ + if (node_name) { + if ((set->ctx == LYD_CTX(node)) && (node->schema->name != node_name)) { + return LY_ENOT; + } else if ((set->ctx != LYD_CTX(node)) && strcmp(node->schema->name, node_name)) { + return LY_ENOT; + } + } + + /* when check, accept the context node because it should only be the path ".", we have checked the when is valid before */ + if (!(options & LYXP_IGNORE_WHEN) && lysc_has_when(node->schema) && !(node->flags & LYD_WHEN_TRUE) && + (node != set->cur_node)) { + return LY_EINCOMPLETE; + } + + /* match */ + return LY_SUCCESS; +} + +/** + * @brief Get the next node in a forward DFS. + * + * @param[in] iter Last returned node. + * @param[in] stop Node to stop the search on and not return. + * @return Next node, NULL if there are no more. + */ +static const struct lyd_node * +moveto_axis_node_next_dfs_forward(const struct lyd_node *iter, const struct lyd_node *stop) +{ + const struct lyd_node *next = NULL; + + /* 1) child */ + next = lyd_child(iter); + if (!next) { + if (iter == stop) { + /* reached stop, no more descendants */ + return NULL; + } + /* 2) child next sibling */ + next = iter->next; + } + while (!next) { + iter = lyd_parent(iter); + if ((!stop && !iter) || (stop && (lyd_parent(iter) == lyd_parent(stop)))) { + return NULL; + } + next = iter->next; + } + + return next; +} + +/** + * @brief Get the next node in a backward DFS. + * + * @param[in] iter Last returned node. + * @param[in] stop Node to stop the search on and not return. + * @return Next node, NULL if there are no more. + */ +static const struct lyd_node * +moveto_axis_node_next_dfs_backward(const struct lyd_node *iter, const struct lyd_node *stop) +{ + const struct lyd_node *next = NULL; + + /* 1) previous sibling innermost last child */ + next = iter->prev->next ? iter->prev : NULL; + while (next && lyd_child(next)) { + next = lyd_child(next); + next = next->prev; + } + + if (!next) { + /* 2) parent */ + iter = lyd_parent(iter); + if ((!stop && !iter) || (stop && (lyd_parent(iter) == lyd_parent(stop)))) { + return NULL; + } + next = iter; + } + + return next; +} + +/** + * @brief Get the first node on an axis for a context node. + * + * @param[in,out] iter NULL, updated to the next node. + * @param[in,out] iter_type Node type 0 of @p iter, updated to the node type of the next node. + * @param[in] node Context node. + * @param[in] node_type Type of @p node. + * @param[in] axis Axis to use. + * @param[in] set XPath set with the general context. + * @return LY_SUCCESS on success. + * @return LY_ENOTFOUND if no next node found. + */ +static LY_ERR +moveto_axis_node_next_first(const struct lyd_node **iter, enum lyxp_node_type *iter_type, const struct lyd_node *node, + enum lyxp_node_type node_type, enum lyxp_axis axis, struct lyxp_set *set) +{ + const struct lyd_node *next = NULL; + enum lyxp_node_type next_type = 0; + + assert(!*iter); + assert(!*iter_type); + + switch (axis) { + case LYXP_AXIS_ANCESTOR_OR_SELF: + case LYXP_AXIS_DESCENDANT_OR_SELF: + case LYXP_AXIS_SELF: + /* return the context node */ + next = node; + next_type = node_type; + break; + + case LYXP_AXIS_ANCESTOR: + case LYXP_AXIS_PARENT: + if (node_type == LYXP_NODE_ELEM) { + next = lyd_parent(node); + next_type = next ? LYXP_NODE_ELEM : set->root_type; + } else if (node_type == LYXP_NODE_TEXT) { + next = node; + next_type = LYXP_NODE_ELEM; + } else if (node_type == LYXP_NODE_META) { + next = ((struct lyd_meta *)node)->parent; + next_type = LYXP_NODE_ELEM; + } /* else root does not have a parent */ + break; + + case LYXP_AXIS_CHILD: + if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) { + assert(!node); + + /* search in all the trees */ + next = set->tree; + next_type = next ? LYXP_NODE_ELEM : 0; + } else { + /* search in children */ + next = lyd_child(node); + next_type = next ? LYXP_NODE_ELEM : 0; + } + break; + + case LYXP_AXIS_DESCENDANT: + if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) { + /* top-level nodes */ + next = set->tree; + next_type = LYXP_NODE_ELEM; + } else if (node_type == LYXP_NODE_ELEM) { + /* start from the context node */ + next = moveto_axis_node_next_dfs_forward(node, node); + next_type = next ? LYXP_NODE_ELEM : 0; + } /* else no children */ + break; + + case LYXP_AXIS_FOLLOWING: + case LYXP_AXIS_FOLLOWING_SIBLING: + if (node_type == LYXP_NODE_ELEM) { + /* first next sibling */ + next = node->next; + next_type = next ? LYXP_NODE_ELEM : 0; + } /* else no sibling */ + break; + + case LYXP_AXIS_PRECEDING: + if ((node_type == LYXP_NODE_ELEM) && node->prev->next) { + /* skip ancestors */ + next = moveto_axis_node_next_dfs_backward(node, NULL); + assert(next); + next_type = LYXP_NODE_ELEM; + } /* else no sibling */ + break; + + case LYXP_AXIS_PRECEDING_SIBLING: + if (node_type == LYXP_NODE_ELEM) { + /* first previous sibling */ + next = node->prev->next ? node->prev : NULL; + next_type = next ? LYXP_NODE_ELEM : 0; + } /* else no sibling */ + break; + + case LYXP_AXIS_ATTRIBUTE: + /* handled specially */ + assert(0); + LOGINT(set->ctx); + break; + } + + *iter = next; + *iter_type = next_type; + return next_type ? LY_SUCCESS : LY_ENOTFOUND; +} + +/** + * @brief Iterate over all nodes on an axis for a context node. + * + * @param[in,out] iter Last returned node, start with NULL, updated to the next node. + * @param[in,out] iter_type Node type of @p iter, start with 0, updated to the node type of the next node. + * @param[in] node Context node. + * @param[in] node_type Type of @p node. + * @param[in] axis Axis to use. + * @param[in] set XPath set with the general context. + * @return LY_SUCCESS on success. + * @return LY_ENOTFOUND if no next node found. + */ +static LY_ERR +moveto_axis_node_next(const struct lyd_node **iter, enum lyxp_node_type *iter_type, const struct lyd_node *node, + enum lyxp_node_type node_type, enum lyxp_axis axis, struct lyxp_set *set) +{ + const struct lyd_node *next = NULL; + enum lyxp_node_type next_type = 0; + + if (!*iter_type) { + /* first returned node */ + return moveto_axis_node_next_first(iter, iter_type, node, node_type, axis, set); + } + + switch (axis) { + case LYXP_AXIS_ANCESTOR_OR_SELF: + if ((*iter == node) && (*iter_type == node_type)) { + /* fake first ancestor, we returned self before */ + *iter = NULL; + *iter_type = 0; + return moveto_axis_node_next_first(iter, iter_type, node, node_type, LYXP_AXIS_ANCESTOR, set); + } /* else continue ancestor */ + + /* fallthrough */ + case LYXP_AXIS_ANCESTOR: + if (*iter_type == LYXP_NODE_ELEM) { + /* iter parent */ + next = lyd_parent(*iter); + next_type = next ? LYXP_NODE_ELEM : set->root_type; + } /* else root, no ancestors */ + break; + + case LYXP_AXIS_CHILD: + assert(*iter_type == LYXP_NODE_ELEM); + + /* next sibling (child) */ + next = (*iter)->next; + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_DESCENDANT_OR_SELF: + if ((*iter == node) && (*iter_type == node_type)) { + /* fake first descendant, we returned self before */ + *iter = NULL; + *iter_type = 0; + return moveto_axis_node_next_first(iter, iter_type, node, node_type, LYXP_AXIS_DESCENDANT, set); + } /* else continue descendant */ + + /* fallthrough */ + case LYXP_AXIS_DESCENDANT: + assert(*iter_type == LYXP_NODE_ELEM); + next = moveto_axis_node_next_dfs_forward(*iter, node); + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_FOLLOWING: + assert(*iter_type == LYXP_NODE_ELEM); + next = moveto_axis_node_next_dfs_forward(*iter, NULL); + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_FOLLOWING_SIBLING: + assert(*iter_type == LYXP_NODE_ELEM); + + /* next sibling */ + next = (*iter)->next; + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_PARENT: + case LYXP_AXIS_SELF: + /* parent/self was returned before */ + break; + + case LYXP_AXIS_PRECEDING: + assert(*iter_type == LYXP_NODE_ELEM); + next = moveto_axis_node_next_dfs_backward(*iter, NULL); + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_PRECEDING_SIBLING: + assert(*iter_type == LYXP_NODE_ELEM); + + /* previous sibling */ + next = (*iter)->prev->next ? (*iter)->prev : NULL; + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_ATTRIBUTE: + /* handled specially */ + assert(0); + LOGINT(set->ctx); + break; + } + + *iter = next; + *iter_type = next_type; + return next_type ? LY_SUCCESS : LY_ENOTFOUND; +} + +/** + * @brief Move context @p set to a node. Result is LYXP_SET_NODE_SET. Context position aware. + * + * @param[in,out] set Set to use. + * @param[in] moveto_mod Matching node module, NULL for no prefix. + * @param[in] ncname Matching node name in the dictionary, NULL for any. + * @param[in] axis Axis to search on. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +moveto_node(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, enum lyxp_axis axis, + uint32_t options) +{ + LY_ERR r, rc = LY_SUCCESS; + const struct lyd_node *iter; + enum lyxp_node_type iter_type; + struct lyxp_set result; + uint32_t i; + + if (options & LYXP_SKIP_EXPR) { + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + return LY_EVALID; + } + + /* init result set */ + set_init(&result, set); + + for (i = 0; i < set->used; ++i) { + /* iterate over all the nodes on the axis of the node */ + iter = NULL; + iter_type = 0; + while (!moveto_axis_node_next(&iter, &iter_type, set->val.nodes[i].node, set->val.nodes[i].type, axis, set)) { + r = moveto_node_check(iter, iter_type, set, ncname, moveto_mod, options); + if (r == LY_EINCOMPLETE) { + rc = r; + goto cleanup; + } else if (r) { + continue; + } + + /* check for duplicates if they are possible */ + switch (axis) { + case LYXP_AXIS_ANCESTOR: + case LYXP_AXIS_ANCESTOR_OR_SELF: + case LYXP_AXIS_DESCENDANT: + case LYXP_AXIS_DESCENDANT_OR_SELF: + case LYXP_AXIS_FOLLOWING: + case LYXP_AXIS_FOLLOWING_SIBLING: + case LYXP_AXIS_PARENT: + case LYXP_AXIS_PRECEDING: + case LYXP_AXIS_PRECEDING_SIBLING: + result.non_child_axis = 1; + if (set_dup_node_check(&result, iter, iter_type, -1)) { + continue; + } + break; + case LYXP_AXIS_CHILD: + case LYXP_AXIS_SELF: + break; + case LYXP_AXIS_ATTRIBUTE: + /* handled specially */ + assert(0); + LOGINT(set->ctx); + break; + } + + /* matching node */ + set_insert_node(&result, iter, 0, iter_type, result.used); + } + } + + /* move result to the set */ + lyxp_set_free_content(set); + *set = result; + result.type = LYXP_SET_NUMBER; + + /* sort the final set if the document order could have been broken */ + if (set->non_child_axis) { + set_sort(set); + } else { + assert(!set_sort(set)); + } + +cleanup: + lyxp_set_free_content(&result); + return rc; +} + +/** + * @brief Move context @p set to child nodes using hashes. Result is LYXP_SET_NODE_SET. Context position aware. + * + * @param[in,out] set Set to use. + * @param[in] scnode Matching node schema. + * @param[in] predicates If @p scnode is ::LYS_LIST or ::LYS_LEAFLIST, the predicates specifying a single instance. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +moveto_node_hash_child(struct lyxp_set *set, const struct lysc_node *scnode, const struct ly_path_predicate *predicates, + uint32_t options) +{ + LY_ERR ret = LY_SUCCESS, r; + uint32_t i; + const struct lyd_node *siblings; + struct lyxp_set result; + struct lyd_node *sub, *inst = NULL; + + assert(scnode && (!(scnode->nodetype & (LYS_LIST | LYS_LEAFLIST)) || predicates)); + + /* init result set */ + set_init(&result, set); + + if (options & LYXP_SKIP_EXPR) { + goto cleanup; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + ret = LY_EVALID; + goto cleanup; + } + + /* context check for all the nodes since we have the schema node */ + if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (scnode->flags & LYS_CONFIG_R)) { + lyxp_set_free_content(set); + goto cleanup; + } else if (set->context_op && (scnode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && + (scnode != set->context_op)) { + lyxp_set_free_content(set); + goto cleanup; + } + + /* create specific data instance if needed */ + if (scnode->nodetype == LYS_LIST) { + LY_CHECK_GOTO(ret = lyd_create_list(scnode, predicates, &inst), cleanup); + } else if (scnode->nodetype == LYS_LEAFLIST) { + LY_CHECK_GOTO(ret = lyd_create_term2(scnode, &predicates[0].value, &inst), cleanup); + } + + for (i = 0; i < set->used; ++i) { + siblings = NULL; + + if ((set->val.nodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.nodes[i].type == LYXP_NODE_ROOT)) { + assert(!set->val.nodes[i].node); + + /* search in all the trees */ + siblings = set->tree; + } else if (set->val.nodes[i].type == LYXP_NODE_ELEM) { + /* search in children */ + siblings = lyd_child(set->val.nodes[i].node); + } + + /* find the node using hashes */ + if (inst) { + r = lyd_find_sibling_first(siblings, inst, &sub); + } else { + r = lyd_find_sibling_val(siblings, scnode, NULL, 0, &sub); + } + LY_CHECK_ERR_GOTO(r && (r != LY_ENOTFOUND), ret = r, cleanup); + + /* when check */ + if (!(options & LYXP_IGNORE_WHEN) && sub && lysc_has_when(sub->schema) && !(sub->flags & LYD_WHEN_TRUE)) { + ret = LY_EINCOMPLETE; + goto cleanup; + } + + if (sub) { + /* pos filled later */ + set_insert_node(&result, sub, 0, LYXP_NODE_ELEM, result.used); + } + } + + /* move result to the set */ + lyxp_set_free_content(set); + *set = result; + result.type = LYXP_SET_NUMBER; + assert(!set_sort(set)); + +cleanup: + lyxp_set_free_content(&result); + lyd_free_tree(inst); + return ret; +} + +/** + * @brief Check @p node as a part of schema NameTest processing. + * + * @param[in] node Schema node to check. + * @param[in] ctx_scnode Context node. + * @param[in] set Set to read general context from. + * @param[in] node_name Node name in the dictionary to move to, NULL for any nodes. + * @param[in] moveto_mod Expected module of the node, NULL for no prefix. + * @return LY_ERR (LY_ENOT if node does not match, LY_EINVAL if neither node nor any children match) + */ +static LY_ERR +moveto_scnode_check(const struct lysc_node *node, const struct lysc_node *ctx_scnode, const struct lyxp_set *set, + const char *node_name, const struct lys_module *moveto_mod) +{ + if (!moveto_mod && node_name) { + switch (set->format) { + case LY_VALUE_SCHEMA: + case LY_VALUE_SCHEMA_RESOLVED: + /* use current module */ + moveto_mod = set->cur_mod; + break; + case LY_VALUE_JSON: + case LY_VALUE_LYB: + case LY_VALUE_STR_NS: + /* inherit module of the context node, if any */ + if (ctx_scnode) { + moveto_mod = ctx_scnode->module; + } + break; + case LY_VALUE_CANON: + case LY_VALUE_XML: + /* not defined */ + LOGINT(set->ctx); + return LY_EINVAL; + } + } + + if (!node) { + /* root will not match a specific node */ + if (node_name || moveto_mod) { + return LY_ENOT; + } + return LY_SUCCESS; + } + + /* module check */ + if (moveto_mod && (node->module != moveto_mod)) { + return LY_ENOT; + } + + /* context check */ + if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (node->flags & LYS_CONFIG_R)) { + return LY_EINVAL; + } else if (set->context_op && (node->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && (node != set->context_op)) { + return LY_EINVAL; + } + + /* name check */ + if (node_name && (node->name != node_name)) { + return LY_ENOT; + } + + /* match */ + return LY_SUCCESS; +} + +/** + * @brief Get the next node in a forward schema node DFS. + * + * @param[in] iter Last returned node. + * @param[in] stop Node to stop the search on and not return. + * @param[in] getnext_opts Options for ::lys_getnext(). + * @return Next node, NULL if there are no more. + */ +static const struct lysc_node * +moveto_axis_scnode_next_dfs_forward(const struct lysc_node *iter, const struct lysc_node *stop, uint32_t getnext_opts) +{ + const struct lysc_node *next = NULL; + + next = lysc_node_child(iter); + if (!next) { + /* no children, try siblings */ + if ((iter == stop) || !lysc_data_parent(iter)) { + /* we are done, no next element to process */ + return NULL; + } + + next = lys_getnext(iter, lysc_data_parent(iter), NULL, getnext_opts); + } + while (!next && iter) { + /* parent is already processed, go to its sibling */ + iter = iter->parent; + if ((iter == stop) || !lysc_data_parent(iter)) { + /* we are done, no next element to process */ + return NULL; + } + next = lys_getnext(iter, lysc_data_parent(iter), NULL, getnext_opts); + } + + return next; +} + +/** + * @brief Consider schema node based on its in_ctx enum value. + * + * @param[in,out] in_ctx In_ctx enum of the schema node, may be updated. + * @param[in] axis Axis to use. + * @return LY_SUCCESS on success. + * @return LY_ENOT if the node should not be returned. + */ +static LY_ERR +moveto_axis_scnode_next_in_ctx(int32_t *in_ctx, enum lyxp_axis axis) +{ + switch (axis) { + case LYXP_AXIS_SELF: + if ((*in_ctx == LYXP_SET_SCNODE_START) || (*in_ctx == LYXP_SET_SCNODE_ATOM_CTX)) { + /* additionally put the start node into context */ + *in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + return LY_SUCCESS; + } + break; + case LYXP_AXIS_PARENT: + case LYXP_AXIS_ANCESTOR_OR_SELF: + case LYXP_AXIS_ANCESTOR: + case LYXP_AXIS_DESCENDANT_OR_SELF: + case LYXP_AXIS_DESCENDANT: + case LYXP_AXIS_FOLLOWING: + case LYXP_AXIS_FOLLOWING_SIBLING: + case LYXP_AXIS_PRECEDING: + case LYXP_AXIS_PRECEDING_SIBLING: + case LYXP_AXIS_CHILD: + if (*in_ctx == LYXP_SET_SCNODE_START) { + /* remember that context node was used */ + *in_ctx = LYXP_SET_SCNODE_START_USED; + return LY_SUCCESS; + } else if (*in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + /* traversed */ + *in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + return LY_SUCCESS; + } + break; + case LYXP_AXIS_ATTRIBUTE: + /* unreachable */ + assert(0); + LOGINT(NULL); + break; + } + + return LY_ENOT; +} + +/** + * @brief Get previous sibling for a schema node. + * + * @param[in] scnode Schema node. + * @param[in] getnext_opts Options for ::lys_getnext(). + * @return Previous sibling, NULL if none. + */ +static const struct lysc_node * +moveto_axis_scnode_preceding_sibling(const struct lysc_node *scnode, uint32_t getnext_opts) +{ + const struct lysc_node *next = NULL, *prev = NULL; + + while ((next = lys_getnext(next, lysc_data_parent(scnode), scnode->module->compiled, getnext_opts))) { + if (next == scnode) { + break; + } + + prev = next; + } + + return prev; +} + +/** + * @brief Get the first schema node on an axis for a context node. + * + * @param[in,out] iter Last returned node, start with NULL, updated to the next node. + * @param[in,out] iter_type Node type of @p iter, start with 0, updated to the node type of the next node. + * @param[in,out] iter_mod Internal module iterator, do not change. + * @param[in,out] iter_mod_idx Internal module index iterator, do not change. + * @param[in] scnode Context node. + * @param[in] node_type Type of @p scnode. + * @param[in] in_ctx In_ctx enum of @p scnode. + * @param[in] axis Axis to use. + * @param[in] set XPath set with the general context. + * @param[in] getnext_opts Options for ::lys_getnext(). + * @return LY_SUCCESS on success. + * @return LY_ENOTFOUND if no next node found. + */ +static LY_ERR +moveto_axis_scnode_next_first(const struct lysc_node **iter, enum lyxp_node_type *iter_type, const struct lys_module **iter_mod, + uint32_t *iter_mod_idx, const struct lysc_node *scnode, enum lyxp_node_type node_type, enum lyxp_axis axis, + struct lyxp_set *set, uint32_t getnext_opts) +{ + const struct lysc_node *next = NULL; + enum lyxp_node_type next_type = 0; + + assert(!*iter); + assert(!*iter_type); + + *iter_mod = NULL; + *iter_mod_idx = 0; + + switch (axis) { + case LYXP_AXIS_ANCESTOR_OR_SELF: + case LYXP_AXIS_DESCENDANT_OR_SELF: + case LYXP_AXIS_SELF: + if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT) || (node_type == LYXP_NODE_ELEM)) { + /* just return the node */ + next = scnode; + next_type = node_type; + } + break; + + case LYXP_AXIS_ANCESTOR: + case LYXP_AXIS_PARENT: + if (node_type == LYXP_NODE_ELEM) { + next = lysc_data_parent(scnode); + next_type = next ? LYXP_NODE_ELEM : set->root_type; + } /* else no parent */ + break; + + case LYXP_AXIS_DESCENDANT: + case LYXP_AXIS_CHILD: + if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) { + /* it can actually be in any module, it's all , and even if it's moveto_mod (if set), + * it can be in a top-level augment */ + while ((*iter_mod = ly_ctx_get_module_iter(set->ctx, iter_mod_idx))) { + /* module may not be implemented or not compiled yet */ + if (!(*iter_mod)->compiled) { + continue; + } + + /* get next node */ + if ((next = lys_getnext(NULL, NULL, (*iter_mod)->compiled, getnext_opts))) { + next_type = LYXP_NODE_ELEM; + break; + } + } + } else if (node_type == LYXP_NODE_ELEM) { + /* get next node */ + next = lys_getnext(NULL, scnode, NULL, getnext_opts); + next_type = next ? LYXP_NODE_ELEM : 0; + } + break; + + case LYXP_AXIS_FOLLOWING: + case LYXP_AXIS_FOLLOWING_SIBLING: + if (node_type == LYXP_NODE_ELEM) { + /* first next sibling */ + next = lys_getnext(scnode, lysc_data_parent(scnode), scnode->module->compiled, getnext_opts); + next_type = next ? LYXP_NODE_ELEM : 0; + } /* else no sibling */ + break; + + case LYXP_AXIS_PRECEDING: + case LYXP_AXIS_PRECEDING_SIBLING: + if (node_type == LYXP_NODE_ELEM) { + /* first parent sibling */ + next = lys_getnext(NULL, lysc_data_parent(scnode), scnode->module->compiled, getnext_opts); + if (next == scnode) { + /* no preceding sibling */ + next = NULL; + } + next_type = next ? LYXP_NODE_ELEM : 0; + } /* else no sibling */ + break; + + case LYXP_AXIS_ATTRIBUTE: + /* unreachable */ + assert(0); + LOGINT(set->ctx); + break; + } + + *iter = next; + *iter_type = next_type; + return next_type ? LY_SUCCESS : LY_ENOTFOUND; +} + +/** + * @brief Iterate over all schema nodes on an axis for a context node. + * + * @param[in,out] iter Last returned node, start with NULL, updated to the next node. + * @param[in,out] iter_type Node type of @p iter, start with 0, updated to the node type of the next node. + * @param[in,out] iter_mod Internal module iterator, do not change. + * @param[in,out] iter_mod_idx Internal module index iterator, do not change. + * @param[in] scnode Context node. + * @param[in] node_type Type of @p scnode. + * @param[in] axis Axis to use. + * @param[in] set XPath set with the general context. + * @param[in] getnext_opts Options for ::lys_getnext(). + * @return LY_SUCCESS on success. + * @return LY_ENOTFOUND if no next node found. + */ +static LY_ERR +moveto_axis_scnode_next(const struct lysc_node **iter, enum lyxp_node_type *iter_type, const struct lys_module **iter_mod, + uint32_t *iter_mod_idx, const struct lysc_node *scnode, enum lyxp_node_type node_type, enum lyxp_axis axis, + struct lyxp_set *set, uint32_t getnext_opts) +{ + const struct lysc_node *next = NULL, *dfs_stop; + enum lyxp_node_type next_type = 0; + + if (!*iter_type) { + /* first returned node */ + return moveto_axis_scnode_next_first(iter, iter_type, iter_mod, iter_mod_idx, scnode, node_type, axis, set, + getnext_opts); + } + + switch (axis) { + case LYXP_AXIS_PARENT: + case LYXP_AXIS_SELF: + /* parent/self was returned before */ + break; + + case LYXP_AXIS_ANCESTOR_OR_SELF: + if ((*iter == scnode) && (*iter_type == node_type)) { + /* fake first ancestor, we returned self before */ + *iter = NULL; + *iter_type = 0; + return moveto_axis_scnode_next_first(iter, iter_type, iter_mod, iter_mod_idx, scnode, node_type, + LYXP_AXIS_ANCESTOR, set, getnext_opts); + } /* else continue ancestor */ + + /* fallthrough */ + case LYXP_AXIS_ANCESTOR: + if (*iter_type == LYXP_NODE_ELEM) { + next = lysc_data_parent(*iter); + next_type = next ? LYXP_NODE_ELEM : set->root_type; + } /* else no ancestor */ + break; + + case LYXP_AXIS_DESCENDANT_OR_SELF: + if ((*iter == scnode) && (*iter_type == node_type)) { + /* fake first descendant, we returned self before */ + *iter = NULL; + *iter_type = 0; + return moveto_axis_scnode_next_first(iter, iter_type, iter_mod, iter_mod_idx, scnode, node_type, + LYXP_AXIS_DESCENDANT, set, getnext_opts); + } /* else DFS until context node */ + dfs_stop = scnode; + + /* fallthrough */ + case LYXP_AXIS_DESCENDANT: + if (axis == LYXP_AXIS_DESCENDANT) { + /* DFS until the context node */ + dfs_stop = scnode; + } + + /* fallthrough */ + case LYXP_AXIS_PRECEDING: + if (axis == LYXP_AXIS_PRECEDING) { + /* DFS until the previous sibling */ + dfs_stop = moveto_axis_scnode_preceding_sibling(scnode, getnext_opts); + assert(dfs_stop); + + if (*iter == dfs_stop) { + /* we are done */ + break; + } + } + + /* fallthrough */ + case LYXP_AXIS_FOLLOWING: + if (axis == LYXP_AXIS_FOLLOWING) { + /* DFS through the whole module */ + dfs_stop = NULL; + } + + /* nested nodes */ + assert(*iter); + next = moveto_axis_scnode_next_dfs_forward(*iter, dfs_stop, getnext_opts); + if (next) { + next_type = LYXP_NODE_ELEM; + break; + } /* else get next top-level node just like a child */ + + /* fallthrough */ + case LYXP_AXIS_CHILD: + case LYXP_AXIS_FOLLOWING_SIBLING: + if (!*iter_mod) { + /* nodes from a single module */ + if ((next = lys_getnext(*iter, lysc_data_parent(*iter), (*iter)->module->compiled, getnext_opts))) { + next_type = LYXP_NODE_ELEM; + break; + } + + assert(scnode); + if ((axis != LYXP_AXIS_CHILD) && !lysc_data_parent(scnode)) { + /* iterating over top-level nodes, find next */ + while (lysc_data_parent(*iter)) { + *iter = lysc_data_parent(*iter); + } + if ((next = lys_getnext(*iter, NULL, (*iter)->module->compiled, getnext_opts))) { + next_type = LYXP_NODE_ELEM; + break; + } + } + } + + while (*iter_mod) { + /* module top-level nodes */ + if ((next = lys_getnext(*iter, NULL, (*iter_mod)->compiled, getnext_opts))) { + next_type = LYXP_NODE_ELEM; + break; + } + + /* get next module */ + while ((*iter_mod = ly_ctx_get_module_iter(set->ctx, iter_mod_idx))) { + /* module may not be implemented or not compiled yet */ + if ((*iter_mod)->compiled) { + break; + } + } + + /* new module, start over */ + *iter = NULL; + } + break; + + case LYXP_AXIS_PRECEDING_SIBLING: + assert(*iter); + + /* next parent sibling until scnode */ + next = lys_getnext(*iter, lysc_data_parent(*iter), (*iter)->module->compiled, getnext_opts); + if (next == scnode) { + /* no previous sibling */ + next = NULL; + } + next_type = next ? LYXP_NODE_ELEM : 0; + break; + + case LYXP_AXIS_ATTRIBUTE: + /* unreachable */ + assert(0); + LOGINT(set->ctx); + break; + } + + *iter = next; + *iter_type = next_type; + return next_type ? LY_SUCCESS : LY_ENOTFOUND; +} + +/** + * @brief Move context @p set to a schema node. Result is LYXP_SET_SCNODE_SET (or LYXP_SET_EMPTY). + * + * @param[in,out] set Set to use. + * @param[in] moveto_mod Matching node module, NULL for no prefix. + * @param[in] ncname Matching node name in the dictionary, NULL for any. + * @param[in] axis Axis to search on. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, enum lyxp_axis axis, + uint32_t options) +{ + ly_bool temp_ctx = 0; + uint32_t getnext_opts, orig_used, i, mod_idx, idx; + const struct lys_module *mod; + const struct lysc_node *iter; + enum lyxp_node_type iter_type; + + if (options & LYXP_SKIP_EXPR) { + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_SCNODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + return LY_EVALID; + } + + /* getnext opts */ + getnext_opts = 0; + if (options & LYXP_SCNODE_OUTPUT) { + getnext_opts |= LYS_GETNEXT_OUTPUT; + } + + orig_used = set->used; + for (i = 0; i < orig_used; ++i) { + /* update in_ctx first */ + if (moveto_axis_scnode_next_in_ctx(&set->val.scnodes[i].in_ctx, axis)) { + /* not usable, skip */ + continue; + } + + iter = NULL; + iter_type = 0; + while (!moveto_axis_scnode_next(&iter, &iter_type, &mod, &mod_idx, set->val.scnodes[i].scnode, + set->val.scnodes[i].type, axis, set, getnext_opts)) { + if (moveto_scnode_check(iter, NULL, set, ncname, moveto_mod)) { + continue; + } + + /* insert */ + LY_CHECK_RET(set_scnode_insert_node(set, iter, iter_type, axis, &idx)); + + /* we need to prevent these nodes from being considered in this moveto */ + if ((idx < orig_used) && (idx > i)) { + set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX; + temp_ctx = 1; + } + } + + if (moveto_mod && ncname && ((axis == LYXP_AXIS_DESCENDANT) || (axis == LYXP_AXIS_CHILD)) && + (set->val.scnodes[i].type == LYXP_NODE_ELEM) && !ly_nested_ext_schema(NULL, set->val.scnodes[i].scnode, + moveto_mod->name, strlen(moveto_mod->name), LY_VALUE_JSON, NULL, ncname, strlen(ncname), &iter, NULL)) { + /* there is a matching node from an extension, use it */ + LY_CHECK_RET(set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx)); + if ((idx < orig_used) && (idx > i)) { + set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX; + temp_ctx = 1; + } + } + } + + /* correct temporary in_ctx values */ + if (temp_ctx) { + for (i = 0; i < orig_used; ++i) { + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_NEW_CTX) { + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to a child node and all its descendants. Result is LYXP_SET_NODE_SET. + * Context position aware. + * + * @param[in] set Set to use. + * @param[in] moveto_mod Matching node module, NULL for no prefix. + * @param[in] ncname Matching node name in the dictionary, NULL for any. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +moveto_node_alldesc_child(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options) +{ + uint32_t i; + const struct lyd_node *next, *elem, *start; + struct lyxp_set ret_set; + LY_ERR rc; + + if (options & LYXP_SKIP_EXPR) { + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + return LY_EVALID; + } + + /* replace the original nodes (and throws away all text and meta nodes, root is replaced by a child) */ + rc = xpath_pi_node(set, LYXP_AXIS_CHILD, options); + LY_CHECK_RET(rc); + + /* this loop traverses all the nodes in the set and adds/keeps only those that match qname */ + set_init(&ret_set, set); + for (i = 0; i < set->used; ++i) { + + /* TREE DFS */ + start = set->val.nodes[i].node; + for (elem = next = start; elem; elem = next) { + rc = moveto_node_check(elem, LYXP_NODE_ELEM, set, ncname, moveto_mod, options); + if (!rc) { + /* add matching node into result set */ + set_insert_node(&ret_set, elem, 0, LYXP_NODE_ELEM, ret_set.used); + if (set_dup_node_check(set, elem, LYXP_NODE_ELEM, i)) { + /* the node is a duplicate, we'll process it later in the set */ + goto skip_children; + } + } else if (rc == LY_EINCOMPLETE) { + return rc; + } else if (rc == LY_EINVAL) { + goto skip_children; + } + + /* TREE DFS NEXT ELEM */ + /* select element for the next run - children first */ + next = lyd_child(elem); + if (!next) { +skip_children: + /* no children, so try siblings, but only if it's not the start, + * that is considered to be the root and it's siblings are not traversed */ + if (elem != start) { + next = elem->next; + } else { + break; + } + } + while (!next) { + /* no siblings, go back through the parents */ + if (lyd_parent(elem) == start) { + /* we are done, no next element to process */ + break; + } + /* parent is already processed, go to its sibling */ + elem = lyd_parent(elem); + next = elem->next; + } + } + } + + /* make the temporary set the current one */ + ret_set.ctx_pos = set->ctx_pos; + ret_set.ctx_size = set->ctx_size; + lyxp_set_free_content(set); + memcpy(set, &ret_set, sizeof *set); + assert(!set_sort(set)); + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to a child schema node and all its descendants starting from a node. + * Result is LYXP_SET_NODE_SET. + * + * @param[in] set Set to use. + * @param[in] start Start node whose subtree to add. + * @param[in] start_idx Index of @p start in @p set. + * @param[in] moveto_mod Matching node module, NULL for no prefix. + * @param[in] ncname Matching node name in the dictionary, NULL for any. + * @param[in] options XPath options. + * @return LY_ERR value. + */ +static LY_ERR +moveto_scnode_dfs(struct lyxp_set *set, const struct lysc_node *start, uint32_t start_idx, + const struct lys_module *moveto_mod, const char *ncname, uint32_t options) +{ + const struct lysc_node *next, *elem; + uint32_t idx; + LY_ERR rc; + + /* TREE DFS */ + for (elem = next = start; elem; elem = next) { + if ((elem == start) || (elem->nodetype & (LYS_CHOICE | LYS_CASE))) { + /* schema-only nodes, skip root */ + goto next_iter; + } + + rc = moveto_scnode_check(elem, start, set, ncname, moveto_mod); + if (!rc) { + if (lyxp_set_scnode_contains(set, elem, LYXP_NODE_ELEM, start_idx, &idx)) { + set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + if (idx > start_idx) { + /* we will process it later in the set */ + goto skip_children; + } + } else { + LY_CHECK_RET(set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL)); + } + } else if (rc == LY_EINVAL) { + goto skip_children; + } + +next_iter: + /* TREE DFS NEXT ELEM */ + /* select element for the next run - children first */ + next = lysc_node_child(elem); + if (next && (next->nodetype == LYS_INPUT) && (options & LYXP_SCNODE_OUTPUT)) { + next = next->next; + } else if (next && (next->nodetype == LYS_OUTPUT) && !(options & LYXP_SCNODE_OUTPUT)) { + next = next->next; + } + if (!next) { +skip_children: + /* no children, so try siblings, but only if it's not the start, + * that is considered to be the root and it's siblings are not traversed */ + if (elem != start) { + next = elem->next; + } else { + break; + } + } + while (!next) { + /* no siblings, go back through the parents */ + if (elem->parent == start) { + /* we are done, no next element to process */ + break; + } + /* parent is already processed, go to its sibling */ + elem = elem->parent; + next = elem->next; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to a child schema node and all its descendants. Result is LYXP_SET_NODE_SET. + * + * @param[in] set Set to use. + * @param[in] moveto_mod Matching node module, NULL for no prefix. + * @param[in] ncname Matching node name in the dictionary, NULL for any. + * @param[in] options XPath options. + * @return LY_ERR value. + */ +static LY_ERR +moveto_scnode_alldesc_child(struct lyxp_set *set, const struct lys_module *moveto_mod, const char *ncname, uint32_t options) +{ + uint32_t i, orig_used, mod_idx; + const struct lys_module *mod; + const struct lysc_node *root; + + if (options & LYXP_SKIP_EXPR) { + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_SCNODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + return LY_EVALID; + } + + orig_used = set->used; + for (i = 0; i < orig_used; ++i) { + if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_ATOM_CTX) { + if (set->val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START) { + continue; + } + + /* remember context node */ + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_START_USED; + } else { + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + } + + if ((set->val.scnodes[i].type == LYXP_NODE_ROOT_CONFIG) || (set->val.scnodes[i].type == LYXP_NODE_ROOT)) { + /* traverse all top-level nodes in all the modules */ + mod_idx = 0; + while ((mod = ly_ctx_get_module_iter(set->ctx, &mod_idx))) { + /* module may not be implemented or not compiled yet */ + if (!mod->compiled) { + continue; + } + + root = NULL; + /* no getnext opts needed */ + while ((root = lys_getnext(root, NULL, mod->compiled, 0))) { + LY_CHECK_RET(moveto_scnode_dfs(set, root, i, moveto_mod, ncname, options)); + } + } + + } else if (set->val.scnodes[i].type == LYXP_NODE_ELEM) { + /* add all the descendants recursively */ + LY_CHECK_RET(moveto_scnode_dfs(set, set->val.scnodes[i].scnode, i, moveto_mod, ncname, options)); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to an attribute. Result is LYXP_SET_NODE_SET. + * Indirectly context position aware. + * + * @param[in,out] set Set to use. + * @param[in] mod Matching metadata module, NULL for any. + * @param[in] ncname Matching metadata name in the dictionary, NULL for any. + * @param[in] options XPath options. + * @return LY_ERR + */ +static LY_ERR +moveto_attr(struct lyxp_set *set, const struct lys_module *mod, const char *ncname, uint32_t options) +{ + struct lyd_meta *sub; + + if (options & LYXP_SKIP_EXPR) { + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + return LY_EVALID; + } + + for (uint32_t i = 0; i < set->used; ) { + ly_bool replaced = 0; + + /* only attributes of an elem (not dummy) can be in the result, skip all the rest; + * our attributes are always qualified */ + if (set->val.nodes[i].type == LYXP_NODE_ELEM) { + for (sub = set->val.nodes[i].node->meta; sub; sub = sub->next) { + + /* check "namespace" */ + if (mod && (sub->annotation->module != mod)) { + continue; + } + + if (!ncname || (sub->name == ncname)) { + /* match */ + if (!replaced) { + set->val.meta[i].meta = sub; + set->val.meta[i].type = LYXP_NODE_META; + /* pos does not change */ + replaced = 1; + } else { + set_insert_node(set, (struct lyd_node *)sub, set->val.nodes[i].pos, LYXP_NODE_META, i + 1); + } + ++i; + } + } + } + + if (!replaced) { + /* no match */ + set_remove_node(set, i); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set1 to union with @p set2. @p set2 is emptied afterwards. + * Result is LYXP_SET_NODE_SET. Context position aware. + * + * @param[in,out] set1 Set to use for the result. + * @param[in] set2 Set that is copied to @p set1. + * @return LY_ERR + */ +static LY_ERR +moveto_union(struct lyxp_set *set1, struct lyxp_set *set2) +{ + LY_ERR rc; + + if ((set1->type != LYXP_SET_NODE_SET) || (set2->type != LYXP_SET_NODE_SET)) { + LOGVAL(set1->ctx, LY_VCODE_XP_INOP_2, "union", print_set_type(set1), print_set_type(set2)); + return LY_EVALID; + } + + /* set2 is empty or both set1 and set2 */ + if (!set2->used) { + return LY_SUCCESS; + } + + if (!set1->used) { + /* release hidden allocated data (lyxp_set.size) */ + lyxp_set_free_content(set1); + /* direct copying of the entire structure */ + memcpy(set1, set2, sizeof *set1); + /* dynamic memory belongs to set1 now, do not free */ + memset(set2, 0, sizeof *set2); + return LY_SUCCESS; + } + + /* we assume sets are sorted */ + assert(!set_sort(set1) && !set_sort(set2)); + + /* sort, remove duplicates */ + rc = set_sorted_merge(set1, set2); + LY_CHECK_RET(rc); + + /* final set must be sorted */ + assert(!set_sort(set1)); + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to an attribute in any of the descendants. Result is LYXP_SET_NODE_SET. + * Context position aware. + * + * @param[in,out] set Set to use. + * @param[in] mod Matching metadata module, NULL for any. + * @param[in] ncname Matching metadata name in the dictionary, NULL for any. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static int +moveto_attr_alldesc(struct lyxp_set *set, const struct lys_module *mod, const char *ncname, uint32_t options) +{ + struct lyd_meta *sub; + struct lyxp_set *set_all_desc = NULL; + LY_ERR rc; + + if (options & LYXP_SKIP_EXPR) { + return LY_SUCCESS; + } + + if (set->type != LYXP_SET_NODE_SET) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + return LY_EVALID; + } + + /* can be optimized similarly to moveto_node_alldesc() and save considerable amount of memory, + * but it likely won't be used much, so it's a waste of time */ + /* copy the context */ + set_all_desc = set_copy(set); + /* get all descendant nodes (the original context nodes are removed) */ + rc = moveto_node_alldesc_child(set_all_desc, NULL, NULL, options); + if (rc != LY_SUCCESS) { + lyxp_set_free(set_all_desc); + return rc; + } + /* prepend the original context nodes */ + rc = moveto_union(set, set_all_desc); + if (rc != LY_SUCCESS) { + lyxp_set_free(set_all_desc); + return rc; + } + lyxp_set_free(set_all_desc); + + for (uint32_t i = 0; i < set->used; ) { + ly_bool replaced = 0; + + /* only attributes of an elem can be in the result, skip all the rest, + * we have all attributes qualified in lyd tree */ + if (set->val.nodes[i].type == LYXP_NODE_ELEM) { + for (sub = set->val.nodes[i].node->meta; sub; sub = sub->next) { + /* check "namespace" */ + if (mod && (sub->annotation->module != mod)) { + continue; + } + + if (!ncname || (sub->name == ncname)) { + /* match */ + if (!replaced) { + set->val.meta[i].meta = sub; + set->val.meta[i].type = LYXP_NODE_META; + /* pos does not change */ + replaced = 1; + } else { + set_insert_node(set, (struct lyd_node *)sub, set->val.meta[i].pos, LYXP_NODE_META, i + 1); + } + ++i; + } + } + } + + if (!replaced) { + /* no match */ + set_remove_node(set, i); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set1 single item to the result of a comparison. + * + * @param[in] set1 First set with the item to compare. + * @param[in] idx1 Index of the item in @p set1. + * @param[in] set2 Second set. + * @param[in] op Comparison operator to process. + * @param[in] switch_operands Whether to switch sets as operands; whether it is `set1 op set2` or `set2 op set1`. + * @param[out] result Result of the comparison. + * @return LY_ERR value. + */ +static LY_ERR +moveto_op_comp_item(const struct lyxp_set *set1, uint32_t idx1, struct lyxp_set *set2, const char *op, + ly_bool switch_operands, ly_bool *result) +{ + struct lyxp_set tmp1 = {0}; + LY_ERR rc = LY_SUCCESS; + + assert(set1->type == LYXP_SET_NODE_SET); + + /* cast set1 */ + switch (set2->type) { + case LYXP_SET_NUMBER: + rc = set_comp_cast(&tmp1, set1, LYXP_SET_NUMBER, idx1); + break; + case LYXP_SET_BOOLEAN: + rc = set_comp_cast(&tmp1, set1, LYXP_SET_BOOLEAN, idx1); + break; + default: + rc = set_comp_cast(&tmp1, set1, LYXP_SET_STRING, idx1); + break; + } + LY_CHECK_GOTO(rc, cleanup); + + /* canonize set2 */ + LY_CHECK_GOTO(rc = set_comp_canonize(set2, &set1->val.nodes[idx1]), cleanup); + + /* compare recursively and store the result */ + if (switch_operands) { + LY_CHECK_GOTO(rc = moveto_op_comp(set2, &tmp1, op, result), cleanup); + } else { + LY_CHECK_GOTO(rc = moveto_op_comp(&tmp1, set2, op, result), cleanup); + } + +cleanup: + lyxp_set_free_content(&tmp1); + return rc; +} + +/** + * @brief Move context @p set1 to the result of a comparison. Handles '=', '!=', '<=', '<', '>=', or '>'. + * Result is LYXP_SET_BOOLEAN. Indirectly context position aware. + * + * @param[in] set1 Set acting as the first operand for @p op. + * @param[in] set2 Set acting as the second operand for @p op. + * @param[in] op Comparison operator to process. + * @param[out] result Result of the comparison. + * @return LY_ERR + */ +static LY_ERR +moveto_op_comp(struct lyxp_set *set1, struct lyxp_set *set2, const char *op, ly_bool *result) +{ + /* + * NODE SET + NODE SET = NODE SET + STRING /(1 NODE SET) 2 STRING + * NODE SET + STRING = STRING + STRING /1 STRING (2 STRING) + * NODE SET + NUMBER = NUMBER + NUMBER /1 NUMBER (2 NUMBER) + * NODE SET + BOOLEAN = BOOLEAN + BOOLEAN /1 BOOLEAN (2 BOOLEAN) + * STRING + NODE SET = STRING + STRING /(1 STRING) 2 STRING + * NUMBER + NODE SET = NUMBER + NUMBER /(1 NUMBER) 2 NUMBER + * BOOLEAN + NODE SET = BOOLEAN + BOOLEAN /(1 BOOLEAN) 2 BOOLEAN + * + * '=' or '!=' + * BOOLEAN + BOOLEAN + * BOOLEAN + STRING = BOOLEAN + BOOLEAN /(1 BOOLEAN) 2 BOOLEAN + * BOOLEAN + NUMBER = BOOLEAN + BOOLEAN /(1 BOOLEAN) 2 BOOLEAN + * STRING + BOOLEAN = BOOLEAN + BOOLEAN /1 BOOLEAN (2 BOOLEAN) + * NUMBER + BOOLEAN = BOOLEAN + BOOLEAN /1 BOOLEAN (2 BOOLEAN) + * NUMBER + NUMBER + * NUMBER + STRING = NUMBER + NUMBER /(1 NUMBER) 2 NUMBER + * STRING + NUMBER = NUMBER + NUMBER /1 NUMBER (2 NUMBER) + * STRING + STRING + * + * '<=', '<', '>=', '>' + * NUMBER + NUMBER + * BOOLEAN + BOOLEAN = NUMBER + NUMBER /1 NUMBER, 2 NUMBER + * BOOLEAN + NUMBER = NUMBER + NUMBER /1 NUMBER (2 NUMBER) + * BOOLEAN + STRING = NUMBER + NUMBER /1 NUMBER, 2 NUMBER + * NUMBER + STRING = NUMBER + NUMBER /(1 NUMBER) 2 NUMBER + * STRING + STRING = NUMBER + NUMBER /1 NUMBER, 2 NUMBER + * STRING + NUMBER = NUMBER + NUMBER /1 NUMBER (2 NUMBER) + * NUMBER + BOOLEAN = NUMBER + NUMBER /(1 NUMBER) 2 NUMBER + * STRING + BOOLEAN = NUMBER + NUMBER /(1 NUMBER) 2 NUMBER + */ + uint32_t i; + LY_ERR rc; + + /* iterative evaluation with node-sets */ + if ((set1->type == LYXP_SET_NODE_SET) || (set2->type == LYXP_SET_NODE_SET)) { + if (set1->type == LYXP_SET_NODE_SET) { + for (i = 0; i < set1->used; ++i) { + /* evaluate for the single item */ + LY_CHECK_RET(moveto_op_comp_item(set1, i, set2, op, 0, result)); + + /* lazy evaluation until true */ + if (*result) { + return LY_SUCCESS; + } + } + } else { + for (i = 0; i < set2->used; ++i) { + /* evaluate for the single item */ + LY_CHECK_RET(moveto_op_comp_item(set2, i, set1, op, 1, result)); + + /* lazy evaluation until true */ + if (*result) { + return LY_SUCCESS; + } + } + } + + /* false for all the nodes */ + *result = 0; + return LY_SUCCESS; + } + + /* first convert properly */ + if ((op[0] == '=') || (op[0] == '!')) { + if ((set1->type == LYXP_SET_BOOLEAN) || (set2->type == LYXP_SET_BOOLEAN)) { + lyxp_set_cast(set1, LYXP_SET_BOOLEAN); + lyxp_set_cast(set2, LYXP_SET_BOOLEAN); + } else if ((set1->type == LYXP_SET_NUMBER) || (set2->type == LYXP_SET_NUMBER)) { + rc = lyxp_set_cast(set1, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(set2, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + } /* else we have 2 strings */ + } else { + rc = lyxp_set_cast(set1, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(set2, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + } + + assert(set1->type == set2->type); + + /* compute result */ + if (op[0] == '=') { + if (set1->type == LYXP_SET_BOOLEAN) { + *result = (set1->val.bln == set2->val.bln); + } else if (set1->type == LYXP_SET_NUMBER) { + *result = (set1->val.num == set2->val.num); + } else { + assert(set1->type == LYXP_SET_STRING); + *result = strcmp(set1->val.str, set2->val.str) ? 0 : 1; + } + } else if (op[0] == '!') { + if (set1->type == LYXP_SET_BOOLEAN) { + *result = (set1->val.bln != set2->val.bln); + } else if (set1->type == LYXP_SET_NUMBER) { + *result = (set1->val.num != set2->val.num); + } else { + assert(set1->type == LYXP_SET_STRING); + *result = strcmp(set1->val.str, set2->val.str) ? 1 : 0; + } + } else { + assert(set1->type == LYXP_SET_NUMBER); + if (op[0] == '<') { + if (op[1] == '=') { + *result = (set1->val.num <= set2->val.num); + } else { + *result = (set1->val.num < set2->val.num); + } + } else { + if (op[1] == '=') { + *result = (set1->val.num >= set2->val.num); + } else { + *result = (set1->val.num > set2->val.num); + } + } + } + + return LY_SUCCESS; +} + +/** + * @brief Move context @p set to the result of a basic operation. Handles '+', '-', unary '-', '*', 'div', + * or 'mod'. Result is LYXP_SET_NUMBER. Indirectly context position aware. + * + * @param[in,out] set1 Set to use for the result. + * @param[in] set2 Set acting as the second operand for @p op. + * @param[in] op Operator to process. + * @return LY_ERR + */ +static LY_ERR +moveto_op_math(struct lyxp_set *set1, struct lyxp_set *set2, const char *op) +{ + LY_ERR rc; + + /* unary '-' */ + if (!set2 && (op[0] == '-')) { + rc = lyxp_set_cast(set1, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + set1->val.num *= -1; + lyxp_set_free(set2); + return LY_SUCCESS; + } + + assert(set1 && set2); + + rc = lyxp_set_cast(set1, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + rc = lyxp_set_cast(set2, LYXP_SET_NUMBER); + LY_CHECK_RET(rc); + + switch (op[0]) { + /* '+' */ + case '+': + set1->val.num += set2->val.num; + break; + + /* '-' */ + case '-': + set1->val.num -= set2->val.num; + break; + + /* '*' */ + case '*': + set1->val.num *= set2->val.num; + break; + + /* 'div' */ + case 'd': + set1->val.num /= set2->val.num; + break; + + /* 'mod' */ + case 'm': + set1->val.num = ((long long)set1->val.num) % ((long long)set2->val.num); + break; + + default: + LOGINT_RET(set1->ctx); + } + + return LY_SUCCESS; +} + +/** + * @brief Evaluate Predicate. Logs directly on error. + * + * [9] Predicate ::= '[' Expr ']' + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @param[in] axis Axis to search on. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_predicate(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set, uint32_t options, enum lyxp_axis axis) +{ + LY_ERR rc; + uint32_t i, orig_exp, orig_pos, orig_size; + int32_t pred_in_ctx; + ly_bool reverse_axis = 0; + struct lyxp_set set2 = {0}; + + /* '[' */ + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { +only_parse: + rc = eval_expr_select(exp, tok_idx, 0, set, options | LYXP_SKIP_EXPR); + LY_CHECK_RET(rc); + } else if (set->type == LYXP_SET_NODE_SET) { + /* we (possibly) need the set sorted, it can affect the result (if the predicate result is a number) */ + assert(!set_sort(set)); + + /* empty set, nothing to evaluate */ + if (!set->used) { + goto only_parse; + } + + /* decide forward or reverse axis */ + switch (axis) { + case LYXP_AXIS_ANCESTOR: + case LYXP_AXIS_ANCESTOR_OR_SELF: + case LYXP_AXIS_PRECEDING: + case LYXP_AXIS_PRECEDING_SIBLING: + reverse_axis = 1; + break; + case LYXP_AXIS_DESCENDANT: + case LYXP_AXIS_DESCENDANT_OR_SELF: + case LYXP_AXIS_FOLLOWING: + case LYXP_AXIS_FOLLOWING_SIBLING: + case LYXP_AXIS_PARENT: + case LYXP_AXIS_CHILD: + case LYXP_AXIS_SELF: + case LYXP_AXIS_ATTRIBUTE: + reverse_axis = 0; + break; + } + + orig_exp = *tok_idx; + orig_pos = reverse_axis ? set->used + 1 : 0; + orig_size = set->used; + for (i = 0; i < set->used; ++i) { + set_init(&set2, set); + set_insert_node(&set2, set->val.nodes[i].node, set->val.nodes[i].pos, set->val.nodes[i].type, 0); + + /* remember the node context position for position() and context size for last() */ + orig_pos += reverse_axis ? -1 : 1; + + set2.ctx_pos = orig_pos; + set2.ctx_size = orig_size; + *tok_idx = orig_exp; + + rc = eval_expr_select(exp, tok_idx, 0, &set2, options); + if (rc != LY_SUCCESS) { + lyxp_set_free_content(&set2); + return rc; + } + + /* number is a proximity position */ + if (set2.type == LYXP_SET_NUMBER) { + if ((long long)set2.val.num == orig_pos) { + set2.val.num = 1; + } else { + set2.val.num = 0; + } + } + lyxp_set_cast(&set2, LYXP_SET_BOOLEAN); + + /* predicate satisfied or not? */ + if (!set2.val.bln) { + set_remove_node_none(set, i); + } + } + set_remove_nodes_none(set); + + } else if (set->type == LYXP_SET_SCNODE_SET) { + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + /* there is a currently-valid node */ + break; + } + } + /* empty set, nothing to evaluate */ + if (i == set->used) { + goto only_parse; + } + + orig_exp = *tok_idx; + + /* set special in_ctx to all the valid snodes */ + pred_in_ctx = set_scnode_new_in_ctx(set); + + /* use the valid snodes one-by-one */ + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx != pred_in_ctx) { + continue; + } + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + + *tok_idx = orig_exp; + + rc = eval_expr_select(exp, tok_idx, 0, set, options); + LY_CHECK_RET(rc); + + set->val.scnodes[i].in_ctx = pred_in_ctx; + } + + /* restore the state as it was before the predicate */ + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + } else if (set->val.scnodes[i].in_ctx == pred_in_ctx) { + set->val.scnodes[i].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + } + } + + } else { + set2.type = LYXP_SET_NODE_SET; + set_fill_set(&set2, set); + + rc = eval_expr_select(exp, tok_idx, 0, &set2, options); + if (rc != LY_SUCCESS) { + lyxp_set_free_content(&set2); + return rc; + } + + lyxp_set_cast(&set2, LYXP_SET_BOOLEAN); + if (!set2.val.bln) { + lyxp_set_free_content(set); + } + lyxp_set_free_content(&set2); + } + + /* ']' */ + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_BRACK2); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + return LY_SUCCESS; +} + +/** + * @brief Evaluate Literal. Logs directly on error. + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in,out] set Context and result set. On NULL the rule is only parsed. + */ +static void +eval_literal(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set) +{ + if (set) { + if (exp->tok_len[*tok_idx] == 2) { + set_fill_string(set, "", 0); + } else { + set_fill_string(set, &exp->expr[exp->tok_pos[*tok_idx] + 1], exp->tok_len[*tok_idx] - 2); + } + } + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); +} + +/** + * @brief Check that a nametest in a predicate matches a key node. + * + * @param[in] nametest Nametest to check. + * @param[in] len Length of @p nametest. + * @param[in] ctx_scnode Found schema node as the context for the predicate. + * @param[in] set Context set. + * @param[in] key Expected key node. + * @return LY_SUCCESS on success, + * @return LY_ENOT if a predicate could not be compiled. + * @return LY_ERR on any error. + */ +static LY_ERR +eval_name_test_try_compile_predicate_key(const char *nametest, uint32_t len, const struct lysc_node *ctx_scnode, + const struct lyxp_set *set, const struct lysc_node *key) +{ + const struct lys_module *mod; + + /* prefix (module) */ + LY_CHECK_RET(moveto_resolve_model(&nametest, &len, set, ctx_scnode, &mod)); + if (mod != key->module) { + return LY_ENOT; + } + + /* node name */ + if (ly_strncmp(key->name, nametest, len)) { + return LY_ENOT; + } + + return LY_SUCCESS; +} + +/** + * @brief Append a simple predicate for the node. + * + * @param[in] exp Full parsed XPath expression. + * @param[in] tok_idx Predicate start index in @p exp. + * @param[in] end_tok_idx Predicate end index in @p exp. + * @param[in] ctx_scnode Found schema node as the context for the predicate. + * @param[in] set Context set. + * @param[in] pred_node Node with the value referenced in the predicate. + * @param[in,out] pred Predicate to append to. + * @param[in,out] pred_len Length of @p pred, is updated. + * @return LY_SUCCESS on success, + * @return LY_ENOT if a predicate could not be compiled. + * @return LY_ERR on any error. + */ +static LY_ERR +eval_name_test_try_compile_predicate_append(const struct lyxp_expr *exp, uint32_t tok_idx, uint32_t end_tok_idx, + const struct lysc_node *ctx_scnode, const struct lyxp_set *set, const struct lysc_node *pred_node, char **pred, + uint32_t *pred_len) +{ + LY_ERR rc = LY_SUCCESS; + uint32_t i; + const struct lyd_node *siblings; + struct lyd_node *ctx_node; + const struct lysc_node *sparent, *cur_scnode; + struct lyxp_expr *val_exp = NULL; + struct lyxp_set set2 = {0}; + char quot; + + /* duplicate the value expression */ + LY_CHECK_GOTO(rc = lyxp_expr_dup(set->ctx, exp, tok_idx, end_tok_idx, &val_exp), cleanup); + + /* get its atoms */ + cur_scnode = set->cur_node ? set->cur_node->schema : NULL; + LY_CHECK_GOTO(rc = lyxp_atomize(set->ctx, val_exp, set->cur_mod, set->format, set->prefix_data, cur_scnode, + ctx_scnode, &set2, LYXP_SCNODE), cleanup); + + /* check whether we can compile a single predicate (evaluation result value is always the same) */ + for (i = 0; i < set2.used; ++i) { + if ((set2.val.scnodes[i].type != LYXP_NODE_ELEM) || (set2.val.scnodes[i].in_ctx < LYXP_SET_SCNODE_ATOM_NODE)) { + /* skip root and context node */ + continue; + } + + /* 1) context node descendants are traversed - do best-effort detection of the value dependency on the + * context node instance */ + if ((set2.val.scnodes[i].axis == LYXP_AXIS_CHILD) && (set2.val.scnodes[i].scnode->parent == ctx_scnode)) { + /* 1.1) context node child was accessed on the child axis, certain dependency */ + rc = LY_ENOT; + goto cleanup; + } + if ((set2.val.scnodes[i].axis == LYXP_AXIS_DESCENDANT) || (set2.val.scnodes[i].axis == LYXP_AXIS_DESCENDANT_OR_SELF)) { + for (sparent = set2.val.scnodes[i].scnode->parent; sparent && (sparent != ctx_scnode); sparent = sparent->parent) {} + if (sparent) { + /* 1.2) context node descendant was accessed on the descendant axis, probable dependency */ + rc = LY_ENOT; + goto cleanup; + } + } + + /* 2) multi-instance nodes (list or leaf-list) are traversed - all the instances need to be considered, + * but the current node can be safely ignored, it is always the same data instance */ + if ((set2.val.scnodes[i].scnode->nodetype & (LYS_LIST | LYS_LEAFLIST)) && (cur_scnode != set2.val.scnodes[i].scnode)) { + rc = LY_ENOT; + goto cleanup; + } + } + + /* get any data instance of the context node, we checked it makes no difference */ + siblings = set->val.nodes[0].node ? lyd_child(set->val.nodes[0].node) : set->tree; + LY_CHECK_GOTO(rc = lyd_find_sibling_schema(siblings, ctx_scnode, &ctx_node), cleanup); + + /* evaluate the value subexpression with the root context node */ + lyxp_set_free_content(&set2); + LY_CHECK_GOTO(rc = lyxp_eval(set->ctx, val_exp, set->cur_mod, set->format, set->prefix_data, set->cur_node, + ctx_node, set->tree, NULL, &set2, 0), cleanup); + + /* cast it into a string */ + LY_CHECK_GOTO(rc = lyxp_set_cast(&set2, LYXP_SET_STRING), cleanup); + + /* append the JSON predicate */ + *pred = ly_realloc(*pred, *pred_len + 1 + strlen(pred_node->name) + 2 + strlen(set2.val.str) + 3); + LY_CHECK_ERR_GOTO(!*pred, LOGMEM(set->ctx); rc = LY_EMEM, cleanup); + quot = strchr(set2.val.str, '\'') ? '\"' : '\''; + *pred_len += sprintf(*pred + *pred_len, "[%s=%c%s%c]", pred_node->name, quot, set2.val.str, quot); + +cleanup: + lyxp_expr_free(set->ctx, val_exp); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Try to compile list or leaf-list predicate in the known format to be used for hash-based instance search. + * + * @param[in] exp Full parsed XPath expression. + * @param[in,out] tok_idx Index in @p exp at the beginning of the predicate, is updated on success. + * @param[in] ctx_scnode Found schema node as the context for the predicate. + * @param[in] set Context set. + * @param[out] predicates Parsed predicates. + * @param[out] pred_type Type of @p predicates. + * @return LY_SUCCESS on success, + * @return LY_ENOT if a predicate could not be compiled. + * @return LY_ERR on any error. + */ +static LY_ERR +eval_name_test_try_compile_predicates(const struct lyxp_expr *exp, uint32_t *tok_idx, const struct lysc_node *ctx_scnode, + const struct lyxp_set *set, struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type) +{ + LY_ERR rc = LY_SUCCESS; + uint32_t e_idx, val_start_idx, pred_idx = 0, temp_lo = 0, pred_len = 0, nested_pred; + const struct lysc_node *key; + char *pred = NULL; + struct lyxp_expr *exp2 = NULL; + + assert(ctx_scnode->nodetype & (LYS_LIST | LYS_LEAFLIST)); + + /* turn logging off */ + ly_temp_log_options(&temp_lo); + + if (ctx_scnode->nodetype == LYS_LIST) { + /* check for predicates "[key1=...][key2=...]..." */ + + /* get key count */ + if (ctx_scnode->flags & LYS_KEYLESS) { + rc = LY_ENOT; + goto cleanup; + } + + /* learn where the predicates end */ + e_idx = *tok_idx; + for (key = lysc_node_child(ctx_scnode); key && (key->flags & LYS_KEY); key = key->next) { + /* '[' */ + if (lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_BRACK1)) { + rc = LY_ENOT; + goto cleanup; + } + ++e_idx; + + if (lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_NAMETEST)) { + /* not a key */ + rc = LY_ENOT; + goto cleanup; + } + + /* check key */ + LY_CHECK_GOTO(rc = eval_name_test_try_compile_predicate_key(exp->expr + exp->tok_pos[e_idx], + exp->tok_len[e_idx], ctx_scnode, set, key), cleanup); + + ++e_idx; + + if (lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_OPER_EQUAL)) { + /* not '=' */ + rc = LY_ENOT; + goto cleanup; + } + ++e_idx; + + /* value start */ + val_start_idx = e_idx; + + /* ']' */ + nested_pred = 1; + do { + ++e_idx; + + if ((nested_pred == 1) && !lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_OPER_LOG)) { + /* higher priority than '=' */ + rc = LY_ENOT; + goto cleanup; + } else if (!lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_BRACK1)) { + /* nested predicate */ + ++nested_pred; + } else if (!lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_BRACK2)) { + /* predicate end */ + --nested_pred; + } + } while (nested_pred); + + /* try to evaluate the value */ + LY_CHECK_GOTO(rc = eval_name_test_try_compile_predicate_append(exp, val_start_idx, e_idx - 1, ctx_scnode, + set, key, &pred, &pred_len), cleanup); + + ++e_idx; + } + } else { + /* check for predicate "[.=...]" */ + + /* learn just where this single predicate ends */ + e_idx = *tok_idx; + + /* '[' */ + if (lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_BRACK1)) { + rc = LY_ENOT; + goto cleanup; + } + ++e_idx; + + if (lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_DOT)) { + /* not the node value */ + rc = LY_ENOT; + goto cleanup; + } + ++e_idx; + + if (lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_OPER_EQUAL)) { + /* not '=' */ + rc = LY_ENOT; + goto cleanup; + } + ++e_idx; + + /* value start */ + val_start_idx = e_idx; + + /* ']' */ + nested_pred = 1; + do { + ++e_idx; + + if ((nested_pred == 1) && !lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_OPER_LOG)) { + /* higher priority than '=' */ + rc = LY_ENOT; + goto cleanup; + } else if (!lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_BRACK1)) { + /* nested predicate */ + ++nested_pred; + } else if (!lyxp_check_token(NULL, exp, e_idx, LYXP_TOKEN_BRACK2)) { + /* predicate end */ + --nested_pred; + } + } while (nested_pred); + + /* try to evaluate the value */ + LY_CHECK_GOTO(rc = eval_name_test_try_compile_predicate_append(exp, val_start_idx, e_idx - 1, ctx_scnode, set, + ctx_scnode, &pred, &pred_len), cleanup); + + ++e_idx; + } + + /* parse the predicate(s) */ + LY_CHECK_GOTO(rc = ly_path_parse_predicate(set->ctx, ctx_scnode, pred, pred_len, LY_PATH_PREFIX_OPTIONAL, + LY_PATH_PRED_SIMPLE, &exp2), cleanup); + + /* compile */ + rc = ly_path_compile_predicate(set->ctx, set->cur_node ? set->cur_node->schema : NULL, set->cur_mod, ctx_scnode, exp2, + &pred_idx, LY_VALUE_JSON, NULL, predicates, pred_type); + LY_CHECK_GOTO(rc, cleanup); + + /* success, the predicate must include all the needed information for hash-based search */ + *tok_idx = e_idx; + +cleanup: + ly_temp_log_options(NULL); + lyxp_expr_free(set->ctx, exp2); + free(pred); + return rc; +} + +/** + * @brief Search for/check the next schema node that could be the only matching schema node meaning the + * data node(s) could be found using a single hash-based search. + * + * @param[in] ctx libyang context. + * @param[in] node Next context node to check. + * @param[in] name Expected node name. + * @param[in] name_len Length of @p name. + * @param[in] moveto_mod Expected node module, can be NULL for JSON format with no prefix. + * @param[in] root_type XPath root type. + * @param[in] format Prefix format. + * @param[in,out] found Previously found node, is updated. + * @return LY_SUCCESS on success, + * @return LY_ENOT if the whole check failed and hashes cannot be used. + */ +static LY_ERR +eval_name_test_with_predicate_get_scnode(const struct ly_ctx *ctx, const struct lyd_node *node, const char *name, + uint32_t name_len, const struct lys_module *moveto_mod, enum lyxp_node_type root_type, LY_VALUE_FORMAT format, + const struct lysc_node **found) +{ + const struct lysc_node *scnode; + const struct lys_module *mod; + uint32_t idx = 0; + + assert((format == LY_VALUE_JSON) || moveto_mod); + +continue_search: + scnode = NULL; + if (!node) { + if ((format == LY_VALUE_JSON) && !moveto_mod) { + /* search all modules for a single match */ + while ((mod = ly_ctx_get_module_iter(ctx, &idx))) { + if (!mod->implemented) { + continue; + } + + scnode = lys_find_child(NULL, mod, name, name_len, 0, 0); + if (scnode) { + /* we have found a match */ + break; + } + } + + if (!scnode) { + /* all modules searched */ + idx = 0; + } + } else { + /* search in top-level */ + scnode = lys_find_child(NULL, moveto_mod, name, name_len, 0, 0); + } + } else if (!*found || (lysc_data_parent(*found) != node->schema)) { + if ((format == LY_VALUE_JSON) && !moveto_mod) { + /* we must adjust the module to inherit the one from the context node */ + moveto_mod = node->schema->module; + } + + /* search in children, do not repeat the same search */ + scnode = lys_find_child(node->schema, moveto_mod, name, name_len, 0, 0); + } /* else skip redundant search */ + + /* additional context check */ + if (scnode && (root_type == LYXP_NODE_ROOT_CONFIG) && (scnode->flags & LYS_CONFIG_R)) { + scnode = NULL; + } + + if (scnode) { + if (*found) { + /* we found a schema node with the same name but at different level, give up, too complicated + * (more hash-based searches would be required, not supported) */ + return LY_ENOT; + } else { + /* remember the found schema node and continue to make sure it can be used */ + *found = scnode; + } + } + + if (idx) { + /* continue searching all the following models */ + goto continue_search; + } + + return LY_SUCCESS; +} + +/** + * @brief Generate message when no matching schema nodes were found for a path segment. + * + * @param[in] set XPath set. + * @param[in] scparent Previous schema parent in the context, if only one. + * @param[in] ncname XPath NCName being evaluated. + * @param[in] ncname_len Length of @p ncname. + * @param[in] expr Whole XPath expression. + * @param[in] options XPath options. + */ +static void +eval_name_test_scnode_no_match_msg(struct lyxp_set *set, const struct lyxp_set_scnode *scparent, const char *ncname, + uint32_t ncname_len, const char *expr, uint32_t options) +{ + const char *format; + char *path = NULL, *ppath = NULL; + + path = lysc_path(set->cur_scnode, LYSC_PATH_LOG, NULL, 0); + if (scparent) { + /* generate path for the parent */ + if (scparent->type == LYXP_NODE_ELEM) { + ppath = lysc_path(scparent->scnode, LYSC_PATH_LOG, NULL, 0); + } else if (scparent->type == LYXP_NODE_ROOT) { + ppath = strdup(""); + } else if (scparent->type == LYXP_NODE_ROOT_CONFIG) { + ppath = strdup(""); + } + } + if (ppath) { + format = "Schema node \"%.*s\" for parent \"%s\" not found; in expr \"%.*s\" with context node \"%s\"."; + if (options & LYXP_SCNODE_ERROR) { + LOGERR(set->ctx, LY_EVALID, format, ncname_len, ncname, ppath, (ncname - expr) + ncname_len, expr, path); + } else { + LOGWRN(set->ctx, format, ncname_len, ncname, ppath, (ncname - expr) + ncname_len, expr, path); + } + } else { + format = "Schema node \"%.*s\" not found; in expr \"%.*s\" with context node \"%s\"."; + if (options & LYXP_SCNODE_ERROR) { + LOGERR(set->ctx, LY_EVALID, format, ncname_len, ncname, (ncname - expr) + ncname_len, expr, path); + } else { + LOGWRN(set->ctx, format, ncname_len, ncname, (ncname - expr) + ncname_len, expr, path); + } + } + free(path); + free(ppath); +} + +/** + * @brief Evaluate NameTest and any following Predicates. Logs directly on error. + * + * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..' + * [6] NodeTest ::= NameTest | NodeType '(' ')' + * [7] NameTest ::= '*' | NCName ':' '*' | QName + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] axis What axis to search on. + * @param[in] all_desc Whether to search all the descendants or children only. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when, LY_ENOT for not found schema node) + */ +static LY_ERR +eval_name_test_with_predicate(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_axis axis, ly_bool all_desc, + struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc = LY_SUCCESS, r; + const char *ncname, *ncname_dict = NULL; + uint32_t i, ncname_len; + const struct lys_module *moveto_mod = NULL; + const struct lysc_node *scnode = NULL; + struct ly_path_predicate *predicates = NULL; + enum ly_path_pred_type pred_type = 0; + int scnode_skip_pred = 0; + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { + goto moveto; + } + + ncname = &exp->expr[exp->tok_pos[*tok_idx - 1]]; + ncname_len = exp->tok_len[*tok_idx - 1]; + + if ((ncname[0] == '*') && (ncname_len == 1)) { + /* all nodes will match */ + goto moveto; + } + + /* parse (and skip) module name */ + rc = moveto_resolve_model(&ncname, &ncname_len, set, NULL, &moveto_mod); + LY_CHECK_GOTO(rc, cleanup); + + if ((ncname[0] == '*') && (ncname_len == 1)) { + /* all nodes from the module will match */ + goto moveto; + } + + if (((set->format == LY_VALUE_JSON) || moveto_mod) && (axis == LYXP_AXIS_CHILD) && !all_desc && + (set->type == LYXP_SET_NODE_SET)) { + /* find the matching schema node in some parent in the context */ + for (i = 0; i < set->used; ++i) { + if (eval_name_test_with_predicate_get_scnode(set->ctx, set->val.nodes[i].node, ncname, ncname_len, + moveto_mod, set->root_type, set->format, &scnode)) { + /* check failed */ + scnode = NULL; + break; + } + } + + if (scnode && (scnode->nodetype & (LYS_LIST | LYS_LEAFLIST))) { + /* try to create the predicates */ + if (eval_name_test_try_compile_predicates(exp, tok_idx, scnode, set, &predicates, &pred_type)) { + /* hashes cannot be used */ + scnode = NULL; + } + } + } + + if (!scnode) { + /* we are not able to match based on a schema node and not all the modules match ("*"), + * use dictionary for efficient comparison */ + LY_CHECK_GOTO(rc = lydict_insert(set->ctx, ncname, ncname_len, &ncname_dict), cleanup); + } + +moveto: + /* move to the attribute(s), data node(s), or schema node(s) */ + if (axis == LYXP_AXIS_ATTRIBUTE) { + if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + } else { + if (all_desc) { + rc = moveto_attr_alldesc(set, moveto_mod, ncname_dict, options); + } else { + rc = moveto_attr(set, moveto_mod, ncname_dict, options); + } + LY_CHECK_GOTO(rc, cleanup); + } + } else { + if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) { + const struct lyxp_set_scnode *scparent = NULL; + ly_bool found = 0; + + /* remember parent if there is only one, to print in the warning */ + for (i = 0; i < set->used; ++i) { + if (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_CTX) { + if (!scparent) { + /* remember the context node */ + scparent = &set->val.scnodes[i]; + } else { + /* several context nodes, no reasonable error possible */ + scparent = NULL; + break; + } + } + } + + if (all_desc && (axis == LYXP_AXIS_CHILD)) { + /* efficient evaluation that does not add all the descendants into the set */ + rc = moveto_scnode_alldesc_child(set, moveto_mod, ncname_dict, options); + } else { + if (all_desc) { + /* "//" == "/descendant-or-self::node()/" */ + rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options); + LY_CHECK_GOTO(rc, cleanup); + } + rc = moveto_scnode(set, moveto_mod, ncname_dict, axis, options); + } + LY_CHECK_GOTO(rc, cleanup); + + i = set->used; + do { + --i; + if (set->val.scnodes[i].in_ctx > LYXP_SET_SCNODE_ATOM_NODE) { + found = 1; + break; + } + } while (i); + if (!found) { + /* generate message */ + eval_name_test_scnode_no_match_msg(set, scparent, ncname, ncname_len, exp->expr, options); + + if (options & LYXP_SCNODE_ERROR) { + /* error */ + rc = LY_EVALID; + goto cleanup; + } + + /* skip the predicates and the rest of this path to not generate invalid warnings */ + rc = LY_ENOT; + scnode_skip_pred = 1; + } + } else { + if (all_desc && (axis == LYXP_AXIS_CHILD)) { + /* efficient evaluation */ + rc = moveto_node_alldesc_child(set, moveto_mod, ncname_dict, options); + } else if (scnode && (axis == LYXP_AXIS_CHILD)) { + /* we can find the child nodes using hashes */ + rc = moveto_node_hash_child(set, scnode, predicates, options); + } else { + if (all_desc) { + /* "//" == "/descendant-or-self::node()/" */ + rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options); + LY_CHECK_GOTO(rc, cleanup); + } + rc = moveto_node(set, moveto_mod, ncname_dict, axis, options); + } + LY_CHECK_GOTO(rc, cleanup); + } + } + + if (scnode_skip_pred) { + /* skip predicates */ + options |= LYXP_SKIP_EXPR; + } + + /* Predicate* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) { + r = eval_predicate(exp, tok_idx, set, options, axis); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } + +cleanup: + if (scnode_skip_pred) { + /* restore options */ + options &= ~LYXP_SKIP_EXPR; + } + if (!(options & LYXP_SKIP_EXPR)) { + lydict_remove(set->ctx, ncname_dict); + ly_path_predicates_free(set->ctx, pred_type, predicates); + } + return rc; +} + +/** + * @brief Evaluate NodeType and any following Predicates. Logs directly on error. + * + * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..' + * [6] NodeTest ::= NameTest | NodeType '(' ')' + * [8] NodeType ::= 'text' | 'node' + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] axis Axis to search on. + * @param[in] all_desc Whether to search all the descendants or axis only. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_node_type_with_predicate(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_axis axis, ly_bool all_desc, + struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + + (void)all_desc; + + if (!(options & LYXP_SKIP_EXPR)) { + assert(exp->tok_len[*tok_idx] == 4); + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "node", 4)) { + rc = xpath_pi_node(set, axis, options); + } else { + assert(!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "text", 4)); + rc = xpath_pi_text(set, axis, options); + } + LY_CHECK_RET(rc); + } + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* '(' */ + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR1); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* ')' */ + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR2); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* Predicate* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) { + rc = eval_predicate(exp, tok_idx, set, options, axis); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Evaluate RelativeLocationPath. Logs directly on error. + * + * [4] RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + * [5] Step ::= '@'? NodeTest Predicate* | '.' | '..' + * [6] NodeTest ::= NameTest | NodeType '(' ')' + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] all_desc Whether to search all the descendants or children only. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (YL_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_relative_location_path(const struct lyxp_expr *exp, uint32_t *tok_idx, ly_bool all_desc, struct lyxp_set *set, + uint32_t options) +{ + LY_ERR rc = LY_SUCCESS; + enum lyxp_axis axis; + int scnode_skip_path = 0; + + goto step; + do { + /* evaluate '/' or '//' */ + if (exp->tok_len[*tok_idx] == 1) { + all_desc = 0; + } else { + assert(exp->tok_len[*tok_idx] == 2); + all_desc = 1; + } + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + +step: + /* AxisSpecifier */ + if (exp->tokens[*tok_idx] == LYXP_TOKEN_AXISNAME) { + axis = str2axis(exp->expr + exp->tok_pos[*tok_idx], exp->tok_len[*tok_idx]); + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_DCOLON); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + } else if (exp->tokens[*tok_idx] == LYXP_TOKEN_AT) { + axis = LYXP_AXIS_ATTRIBUTE; + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + } else { + /* default */ + axis = LYXP_AXIS_CHILD; + } + + /* NodeTest Predicate* */ + switch (exp->tokens[*tok_idx]) { + case LYXP_TOKEN_DOT: + /* evaluate '.' */ + if (!(options & LYXP_SKIP_EXPR)) { + if (((options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_SCNODE_SET)) || + (!(options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_NODE_SET))) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + rc = LY_EVALID; + goto cleanup; + } + + if (all_desc) { + rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options); + LY_CHECK_GOTO(rc, cleanup); + } + rc = xpath_pi_node(set, LYXP_AXIS_SELF, options); + LY_CHECK_GOTO(rc, cleanup); + } + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + break; + + case LYXP_TOKEN_DDOT: + /* evaluate '..' */ + if (!(options & LYXP_SKIP_EXPR)) { + if (((options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_SCNODE_SET)) || + (!(options & LYXP_SCNODE_ALL) && (set->type != LYXP_SET_NODE_SET))) { + LOGVAL(set->ctx, LY_VCODE_XP_INOP_1, "path operator", print_set_type(set)); + rc = LY_EVALID; + goto cleanup; + } + + if (all_desc) { + rc = xpath_pi_node(set, LYXP_AXIS_DESCENDANT_OR_SELF, options); + LY_CHECK_GOTO(rc, cleanup); + } + rc = xpath_pi_node(set, LYXP_AXIS_PARENT, options); + LY_CHECK_GOTO(rc, cleanup); + } + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + break; + + case LYXP_TOKEN_NAMETEST: + /* evaluate NameTest Predicate* */ + rc = eval_name_test_with_predicate(exp, tok_idx, axis, all_desc, set, options); + if (rc == LY_ENOT) { + assert(options & LYXP_SCNODE_ALL); + /* skip the rest of this path */ + rc = LY_SUCCESS; + scnode_skip_path = 1; + options |= LYXP_SKIP_EXPR; + } + LY_CHECK_GOTO(rc, cleanup); + break; + + case LYXP_TOKEN_NODETYPE: + /* evaluate NodeType Predicate* */ + rc = eval_node_type_with_predicate(exp, tok_idx, axis, all_desc, set, options); + LY_CHECK_GOTO(rc, cleanup); + break; + + default: + LOGINT(set->ctx); + rc = LY_EINT; + goto cleanup; + } + } while (!exp_check_token2(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_PATH, LYXP_TOKEN_OPER_RPATH)); + +cleanup: + if (scnode_skip_path) { + options &= ~LYXP_SKIP_EXPR; + } + return rc; +} + +/** + * @brief Evaluate AbsoluteLocationPath. Logs directly on error. + * + * [3] AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_absolute_location_path(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set, uint32_t options) +{ + ly_bool all_desc; + + if (!(options & LYXP_SKIP_EXPR)) { + /* no matter what tokens follow, we need to be at the root */ + LY_CHECK_RET(moveto_root(set, options)); + } + + /* '/' RelativeLocationPath? */ + if (exp->tok_len[*tok_idx] == 1) { + /* evaluate '/' - deferred */ + all_desc = 0; + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_NONE)) { + return LY_SUCCESS; + } + switch (exp->tokens[*tok_idx]) { + case LYXP_TOKEN_DOT: + case LYXP_TOKEN_DDOT: + case LYXP_TOKEN_AXISNAME: + case LYXP_TOKEN_AT: + case LYXP_TOKEN_NAMETEST: + case LYXP_TOKEN_NODETYPE: + LY_CHECK_RET(eval_relative_location_path(exp, tok_idx, all_desc, set, options)); + break; + default: + break; + } + + } else { + /* '//' RelativeLocationPath */ + /* evaluate '//' - deferred so as not to waste memory by remembering all the nodes */ + all_desc = 1; + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + LY_CHECK_RET(eval_relative_location_path(exp, tok_idx, all_desc, set, options)); + } + + return LY_SUCCESS; +} + +/** + * @brief Evaluate FunctionCall. Logs directly on error. + * + * [11] FunctionCall ::= FunctionName '(' ( Expr ( ',' Expr )* )? ')' + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_function_call(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + + LY_ERR (*xpath_func)(struct lyxp_set **, uint32_t, struct lyxp_set *, uint32_t) = NULL; + uint32_t arg_count = 0, i; + struct lyxp_set **args = NULL, **args_aux; + + if (!(options & LYXP_SKIP_EXPR)) { + /* FunctionName */ + switch (exp->tok_len[*tok_idx]) { + case 3: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "not", 3)) { + xpath_func = &xpath_not; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "sum", 3)) { + xpath_func = &xpath_sum; + } + break; + case 4: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "lang", 4)) { + xpath_func = &xpath_lang; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "last", 4)) { + xpath_func = &xpath_last; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "name", 4)) { + xpath_func = &xpath_name; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "true", 4)) { + xpath_func = &xpath_true; + } + break; + case 5: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "count", 5)) { + xpath_func = &xpath_count; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "false", 5)) { + xpath_func = &xpath_false; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "floor", 5)) { + xpath_func = &xpath_floor; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "round", 5)) { + xpath_func = &xpath_round; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "deref", 5)) { + xpath_func = &xpath_deref; + } + break; + case 6: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "concat", 6)) { + xpath_func = &xpath_concat; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "number", 6)) { + xpath_func = &xpath_number; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "string", 6)) { + xpath_func = &xpath_string; + } + break; + case 7: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "boolean", 7)) { + xpath_func = &xpath_boolean; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "ceiling", 7)) { + xpath_func = &xpath_ceiling; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "current", 7)) { + xpath_func = &xpath_current; + } + break; + case 8: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "contains", 8)) { + xpath_func = &xpath_contains; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "position", 8)) { + xpath_func = &xpath_position; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "re-match", 8)) { + xpath_func = &xpath_re_match; + } + break; + case 9: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "substring", 9)) { + xpath_func = &xpath_substring; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "translate", 9)) { + xpath_func = &xpath_translate; + } + break; + case 10: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "local-name", 10)) { + xpath_func = &xpath_local_name; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "enum-value", 10)) { + xpath_func = &xpath_enum_value; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "bit-is-set", 10)) { + xpath_func = &xpath_bit_is_set; + } + break; + case 11: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "starts-with", 11)) { + xpath_func = &xpath_starts_with; + } + break; + case 12: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "derived-from", 12)) { + xpath_func = &xpath_derived_from; + } + break; + case 13: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "namespace-uri", 13)) { + xpath_func = &xpath_namespace_uri; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "string-length", 13)) { + xpath_func = &xpath_string_length; + } + break; + case 15: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "normalize-space", 15)) { + xpath_func = &xpath_normalize_space; + } else if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "substring-after", 15)) { + xpath_func = &xpath_substring_after; + } + break; + case 16: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "substring-before", 16)) { + xpath_func = &xpath_substring_before; + } + break; + case 20: + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "derived-from-or-self", 20)) { + xpath_func = &xpath_derived_from_or_self; + } + break; + } + + if (!xpath_func) { + LOGVAL(set->ctx, LY_VCODE_XP_INFUNC, exp->tok_len[*tok_idx], &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EVALID; + } + } + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* '(' */ + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR1); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* ( Expr ( ',' Expr )* )? */ + if (exp->tokens[*tok_idx] != LYXP_TOKEN_PAR2) { + if (!(options & LYXP_SKIP_EXPR)) { + args = malloc(sizeof *args); + LY_CHECK_ERR_GOTO(!args, LOGMEM(set->ctx); rc = LY_EMEM, cleanup); + arg_count = 1; + args[0] = set_copy(set); + if (!args[0]) { + rc = LY_EMEM; + goto cleanup; + } + + rc = eval_expr_select(exp, tok_idx, 0, args[0], options); + LY_CHECK_GOTO(rc, cleanup); + } else { + rc = eval_expr_select(exp, tok_idx, 0, set, options | LYXP_SKIP_EXPR); + LY_CHECK_GOTO(rc, cleanup); + } + } + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_COMMA)) { + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (!(options & LYXP_SKIP_EXPR)) { + ++arg_count; + args_aux = realloc(args, arg_count * sizeof *args); + LY_CHECK_ERR_GOTO(!args_aux, arg_count--; LOGMEM(set->ctx); rc = LY_EMEM, cleanup); + args = args_aux; + args[arg_count - 1] = set_copy(set); + if (!args[arg_count - 1]) { + rc = LY_EMEM; + goto cleanup; + } + + rc = eval_expr_select(exp, tok_idx, 0, args[arg_count - 1], options); + LY_CHECK_GOTO(rc, cleanup); + } else { + rc = eval_expr_select(exp, tok_idx, 0, set, options | LYXP_SKIP_EXPR); + LY_CHECK_GOTO(rc, cleanup); + } + } + + /* ')' */ + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR2); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (!(options & LYXP_SKIP_EXPR)) { + /* evaluate function */ + rc = xpath_func(args, arg_count, set, options); + + if (options & LYXP_SCNODE_ALL) { + /* merge all nodes from arg evaluations */ + for (i = 0; i < arg_count; ++i) { + set_scnode_clear_ctx(args[i], LYXP_SET_SCNODE_ATOM_NODE); + lyxp_set_scnode_merge(set, args[i]); + } + } + } else { + rc = LY_SUCCESS; + } + +cleanup: + for (i = 0; i < arg_count; ++i) { + lyxp_set_free(args[i]); + } + free(args); + return rc; +} + +/** + * @brief Evaluate Number. Logs directly on error. + * + * @param[in] ctx Context for errors. + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in,out] set Context and result set. On NULL the rule is only parsed. + * @return LY_ERR + */ +static LY_ERR +eval_number(struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set) +{ + long double num; + char *endptr; + + if (set) { + errno = 0; + num = strtold(&exp->expr[exp->tok_pos[*tok_idx]], &endptr); + if (errno) { + LOGVAL(ctx, LY_VCODE_XP_INTOK, "Unknown", &exp->expr[exp->tok_pos[*tok_idx]]); + LOGVAL(ctx, LYVE_XPATH, "Failed to convert \"%.*s\" into a long double (%s).", + exp->tok_len[*tok_idx], &exp->expr[exp->tok_pos[*tok_idx]], strerror(errno)); + return LY_EVALID; + } else if (endptr - &exp->expr[exp->tok_pos[*tok_idx]] != exp->tok_len[*tok_idx]) { + LOGVAL(ctx, LY_VCODE_XP_INTOK, "Unknown", &exp->expr[exp->tok_pos[*tok_idx]]); + LOGVAL(ctx, LYVE_XPATH, "Failed to convert \"%.*s\" into a long double.", + exp->tok_len[*tok_idx], &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EVALID; + } + + set_fill_number(set, num); + } + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + return LY_SUCCESS; +} + +LY_ERR +lyxp_vars_find(struct lyxp_var *vars, const char *name, size_t name_len, struct lyxp_var **var) +{ + LY_ERR ret = LY_ENOTFOUND; + LY_ARRAY_COUNT_TYPE u; + + assert(vars && name); + + name_len = name_len ? name_len : strlen(name); + + LY_ARRAY_FOR(vars, u) { + if (!strncmp(vars[u].name, name, name_len)) { + ret = LY_SUCCESS; + break; + } + } + + if (var && !ret) { + *var = &vars[u]; + } + + return ret; +} + +/** + * @brief Evaluate VariableReference. + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR value. + */ +static LY_ERR +eval_variable_reference(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set, uint32_t options) +{ + LY_ERR ret; + const char *name; + struct lyxp_var *var; + const struct lyxp_var *vars; + struct lyxp_expr *tokens = NULL; + uint32_t token_index, name_len; + + vars = set->vars; + + /* find out the name and value of the variable */ + name = &exp->expr[exp->tok_pos[*tok_idx]]; + name_len = exp->tok_len[*tok_idx]; + ret = lyxp_vars_find((struct lyxp_var *)vars, name, name_len, &var); + LY_CHECK_ERR_RET(ret, LOGERR(set->ctx, ret, "XPath variable \"%.*s\" not defined.", (int)name_len, name), ret); + + /* parse value */ + ret = lyxp_expr_parse(set->ctx, var->value, 0, 1, &tokens); + LY_CHECK_GOTO(ret, cleanup); + + /* evaluate value */ + token_index = 0; + ret = eval_expr_select(tokens, &token_index, 0, set, options); + LY_CHECK_GOTO(ret, cleanup); + +cleanup: + lyxp_expr_free(set->ctx, tokens); + + return ret; +} + +/** + * @brief Evaluate PathExpr. Logs directly on error. + * + * [12] PathExpr ::= LocationPath | PrimaryExpr Predicate* + * | PrimaryExpr Predicate* '/' RelativeLocationPath + * | PrimaryExpr Predicate* '//' RelativeLocationPath + * [2] LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + * [10] PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_path_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_set *set, uint32_t options) +{ + ly_bool all_desc; + LY_ERR rc; + + switch (exp->tokens[*tok_idx]) { + case LYXP_TOKEN_PAR1: + /* '(' Expr ')' */ + + /* '(' */ + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* Expr */ + rc = eval_expr_select(exp, tok_idx, 0, set, options); + LY_CHECK_RET(rc); + + /* ')' */ + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_PAR2); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + goto predicate; + + case LYXP_TOKEN_DOT: + case LYXP_TOKEN_DDOT: + case LYXP_TOKEN_AXISNAME: + case LYXP_TOKEN_AT: + case LYXP_TOKEN_NAMETEST: + case LYXP_TOKEN_NODETYPE: + /* RelativeLocationPath */ + rc = eval_relative_location_path(exp, tok_idx, 0, set, options); + LY_CHECK_RET(rc); + break; + + case LYXP_TOKEN_VARREF: + /* VariableReference */ + rc = eval_variable_reference(exp, tok_idx, set, options); + LY_CHECK_RET(rc); + ++(*tok_idx); + + goto predicate; + + case LYXP_TOKEN_FUNCNAME: + /* FunctionCall */ + rc = eval_function_call(exp, tok_idx, set, options); + LY_CHECK_RET(rc); + + goto predicate; + + case LYXP_TOKEN_OPER_PATH: + case LYXP_TOKEN_OPER_RPATH: + /* AbsoluteLocationPath */ + rc = eval_absolute_location_path(exp, tok_idx, set, options); + LY_CHECK_RET(rc); + break; + + case LYXP_TOKEN_LITERAL: + /* Literal */ + if ((options & LYXP_SKIP_EXPR) || (options & LYXP_SCNODE_ALL)) { + if (!(options & LYXP_SKIP_EXPR)) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + } + eval_literal(exp, tok_idx, NULL); + } else { + eval_literal(exp, tok_idx, set); + } + + goto predicate; + + case LYXP_TOKEN_NUMBER: + /* Number */ + if ((options & LYXP_SKIP_EXPR) || (options & LYXP_SCNODE_ALL)) { + if (!(options & LYXP_SKIP_EXPR)) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + } + rc = eval_number(NULL, exp, tok_idx, NULL); + } else { + rc = eval_number(set->ctx, exp, tok_idx, set); + } + LY_CHECK_RET(rc); + + goto predicate; + + default: + LOGVAL(set->ctx, LY_VCODE_XP_INTOK, lyxp_token2str(exp->tokens[*tok_idx]), &exp->expr[exp->tok_pos[*tok_idx]]); + return LY_EVALID; + } + + return LY_SUCCESS; + +predicate: + /* Predicate* */ + while (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_BRACK1)) { + rc = eval_predicate(exp, tok_idx, set, options, LYXP_AXIS_CHILD); + LY_CHECK_RET(rc); + } + + /* ('/' or '//') RelativeLocationPath */ + if (!exp_check_token2(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_PATH, LYXP_TOKEN_OPER_RPATH)) { + + /* evaluate '/' or '//' */ + if (exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_PATH) { + all_desc = 0; + } else { + all_desc = 1; + } + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + rc = eval_relative_location_path(exp, tok_idx, all_desc, set, options); + LY_CHECK_RET(rc); + } + + return LY_SUCCESS; +} + +/** + * @brief Evaluate UnionExpr. Logs directly on error. + * + * [20] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_union_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc = LY_SUCCESS; + struct lyxp_set orig_set, set2; + uint32_t i; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_UNION, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* ('|' PathExpr)* */ + for (i = 0; i < repeat; ++i) { + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_UNI); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_UNION, set, options); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_UNION, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval */ + if (options & LYXP_SCNODE_ALL) { + lyxp_set_scnode_merge(set, &set2); + } else { + rc = moveto_union(set, &set2); + LY_CHECK_GOTO(rc, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Evaluate UnaryExpr. Logs directly on error. + * + * [19] UnaryExpr ::= UnionExpr | '-' UnaryExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_unary_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + uint32_t this_op, i; + + assert(repeat); + + /* ('-')+ */ + this_op = *tok_idx; + for (i = 0; i < repeat; ++i) { + assert(!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_OPER_MATH) && (exp->expr[exp->tok_pos[*tok_idx]] == '-')); + + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + } + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_UNARY, set, options); + LY_CHECK_RET(rc); + + if (!(options & LYXP_SKIP_EXPR) && (repeat % 2)) { + if (options & LYXP_SCNODE_ALL) { + warn_operands(set->ctx, set, NULL, 1, exp->expr, exp->tok_pos[this_op]); + } else { + rc = moveto_op_math(set, NULL, &exp->expr[exp->tok_pos[this_op]]); + LY_CHECK_RET(rc); + } + } + + return LY_SUCCESS; +} + +/** + * @brief Evaluate MultiplicativeExpr. Logs directly on error. + * + * [18] MultiplicativeExpr ::= UnaryExpr + * | MultiplicativeExpr '*' UnaryExpr + * | MultiplicativeExpr 'div' UnaryExpr + * | MultiplicativeExpr 'mod' UnaryExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_multiplicative_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, + uint32_t options) +{ + LY_ERR rc; + uint32_t i, this_op; + struct lyxp_set orig_set, set2; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_MULTIPLICATIVE, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* ('*' / 'div' / 'mod' UnaryExpr)* */ + for (i = 0; i < repeat; ++i) { + this_op = *tok_idx; + + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_MATH); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_MULTIPLICATIVE, set, options); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_MULTIPLICATIVE, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval */ + if (options & LYXP_SCNODE_ALL) { + warn_operands(set->ctx, set, &set2, 1, exp->expr, exp->tok_pos[this_op - 1]); + lyxp_set_scnode_merge(set, &set2); + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + } else { + rc = moveto_op_math(set, &set2, &exp->expr[exp->tok_pos[this_op]]); + LY_CHECK_GOTO(rc, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Evaluate AdditiveExpr. Logs directly on error. + * + * [17] AdditiveExpr ::= MultiplicativeExpr + * | AdditiveExpr '+' MultiplicativeExpr + * | AdditiveExpr '-' MultiplicativeExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_additive_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + uint32_t i, this_op; + struct lyxp_set orig_set, set2; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_ADDITIVE, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* ('+' / '-' MultiplicativeExpr)* */ + for (i = 0; i < repeat; ++i) { + this_op = *tok_idx; + + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_MATH); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (set ? "parsed" : "skipped"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_ADDITIVE, set, options); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_ADDITIVE, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval */ + if (options & LYXP_SCNODE_ALL) { + warn_operands(set->ctx, set, &set2, 1, exp->expr, exp->tok_pos[this_op - 1]); + lyxp_set_scnode_merge(set, &set2); + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + } else { + rc = moveto_op_math(set, &set2, &exp->expr[exp->tok_pos[this_op]]); + LY_CHECK_GOTO(rc, cleanup); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Evaluate RelationalExpr. Logs directly on error. + * + * [16] RelationalExpr ::= AdditiveExpr + * | RelationalExpr '<' AdditiveExpr + * | RelationalExpr '>' AdditiveExpr + * | RelationalExpr '<=' AdditiveExpr + * | RelationalExpr '>=' AdditiveExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_relational_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + uint32_t i, this_op; + struct lyxp_set orig_set, set2; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_RELATIONAL, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* ('<' / '>' / '<=' / '>=' AdditiveExpr)* */ + for (i = 0; i < repeat; ++i) { + this_op = *tok_idx; + + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_COMP); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_RELATIONAL, set, options); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_RELATIONAL, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval */ + if (options & LYXP_SCNODE_ALL) { + warn_operands(set->ctx, set, &set2, 1, exp->expr, exp->tok_pos[this_op - 1]); + lyxp_set_scnode_merge(set, &set2); + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + } else { + ly_bool result; + + rc = moveto_op_comp(set, &set2, &exp->expr[exp->tok_pos[this_op]], &result); + LY_CHECK_GOTO(rc, cleanup); + set_fill_boolean(set, result); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Evaluate EqualityExpr. Logs directly on error. + * + * [15] EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr + * | EqualityExpr '!=' RelationalExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_equality_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + uint32_t i, this_op; + struct lyxp_set orig_set, set2; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_EQUALITY, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* ('=' / '!=' RelationalExpr)* */ + for (i = 0; i < repeat; ++i) { + this_op = *tok_idx; + + assert((exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL) || (exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_NEQUAL)); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + if (options & LYXP_SKIP_EXPR) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_EQUALITY, set, options); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_EQUALITY, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval */ + if (options & LYXP_SCNODE_ALL) { + warn_operands(set->ctx, set, &set2, 0, exp->expr, exp->tok_pos[this_op - 1]); + warn_equality_value(exp, set, *tok_idx - 1, this_op - 1, *tok_idx - 1); + warn_equality_value(exp, &set2, this_op - 1, this_op - 1, *tok_idx - 1); + lyxp_set_scnode_merge(set, &set2); + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + } else { + ly_bool result; + + rc = moveto_op_comp(set, &set2, &exp->expr[exp->tok_pos[this_op]], &result); + LY_CHECK_GOTO(rc, cleanup); + set_fill_boolean(set, result); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Evaluate AndExpr. Logs directly on error. + * + * [14] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_and_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + struct lyxp_set orig_set, set2; + uint32_t i; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_AND, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* cast to boolean, we know that will be the final result */ + if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + } else { + lyxp_set_cast(set, LYXP_SET_BOOLEAN); + } + + /* ('and' EqualityExpr)* */ + for (i = 0; i < repeat; ++i) { + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_LOG); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, ((options & LYXP_SKIP_EXPR) || !set->val.bln ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* lazy evaluation */ + if ((options & LYXP_SKIP_EXPR) || ((set->type == LYXP_SET_BOOLEAN) && !set->val.bln)) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_AND, set, options | LYXP_SKIP_EXPR); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_AND, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval - just get boolean value actually */ + if (set->type == LYXP_SET_SCNODE_SET) { + set_scnode_clear_ctx(&set2, LYXP_SET_SCNODE_ATOM_NODE); + lyxp_set_scnode_merge(set, &set2); + } else { + lyxp_set_cast(&set2, LYXP_SET_BOOLEAN); + set_fill_set(set, &set2); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Evaluate OrExpr. Logs directly on error. + * + * [13] OrExpr ::= AndExpr | OrExpr 'or' AndExpr + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] repeat How many times this expression is repeated. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_or_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) +{ + LY_ERR rc; + struct lyxp_set orig_set, set2; + uint32_t i; + + assert(repeat); + + set_init(&orig_set, set); + set_init(&set2, set); + + set_fill_set(&orig_set, set); + + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_OR, set, options); + LY_CHECK_GOTO(rc, cleanup); + + /* cast to boolean, we know that will be the final result */ + if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + } else { + lyxp_set_cast(set, LYXP_SET_BOOLEAN); + } + + /* ('or' AndExpr)* */ + for (i = 0; i < repeat; ++i) { + assert(exp->tokens[*tok_idx] == LYXP_TOKEN_OPER_LOG); + LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, ((options & LYXP_SKIP_EXPR) || set->val.bln ? "skipped" : "parsed"), + lyxp_token2str(exp->tokens[*tok_idx]), exp->tok_pos[*tok_idx]); + ++(*tok_idx); + + /* lazy evaluation */ + if ((options & LYXP_SKIP_EXPR) || ((set->type == LYXP_SET_BOOLEAN) && set->val.bln)) { + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_OR, set, options | LYXP_SKIP_EXPR); + LY_CHECK_GOTO(rc, cleanup); + continue; + } + + set_fill_set(&set2, &orig_set); + /* expr_type cound have been LYXP_EXPR_NONE in all these later calls (except for the first one), + * but it does not matter */ + rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_OR, &set2, options); + LY_CHECK_GOTO(rc, cleanup); + + /* eval - just get boolean value actually */ + if (set->type == LYXP_SET_SCNODE_SET) { + set_scnode_clear_ctx(&set2, LYXP_SET_SCNODE_ATOM_NODE); + lyxp_set_scnode_merge(set, &set2); + } else { + lyxp_set_cast(&set2, LYXP_SET_BOOLEAN); + set_fill_set(set, &set2); + } + } + +cleanup: + lyxp_set_free_content(&orig_set); + lyxp_set_free_content(&set2); + return rc; +} + +/** + * @brief Decide what expression is at the pointer @p tok_idx and evaluate it accordingly. + * + * @param[in] exp Parsed XPath expression. + * @param[in] tok_idx Position in the expression @p exp. + * @param[in] etype Expression type to evaluate. + * @param[in,out] set Context and result set. + * @param[in] options XPath options. + * @return LY_ERR (LY_EINCOMPLETE on unresolved when) + */ +static LY_ERR +eval_expr_select(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_expr_type etype, struct lyxp_set *set, + uint32_t options) +{ + uint32_t i, count; + enum lyxp_expr_type next_etype; + LY_ERR rc; + + /* process operator repeats */ + if (!exp->repeat[*tok_idx]) { + next_etype = LYXP_EXPR_NONE; + } else { + /* find etype repeat */ + for (i = 0; exp->repeat[*tok_idx][i] > etype; ++i) {} + + /* select one-priority lower because etype expression called us */ + if (i) { + next_etype = exp->repeat[*tok_idx][i - 1]; + /* count repeats for that expression */ + for (count = 0; i && exp->repeat[*tok_idx][i - 1] == next_etype; ++count, --i) {} + } else { + next_etype = LYXP_EXPR_NONE; + } + } + + /* decide what expression are we parsing based on the repeat */ + switch (next_etype) { + case LYXP_EXPR_OR: + rc = eval_or_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_AND: + rc = eval_and_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_EQUALITY: + rc = eval_equality_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_RELATIONAL: + rc = eval_relational_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_ADDITIVE: + rc = eval_additive_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_MULTIPLICATIVE: + rc = eval_multiplicative_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_UNARY: + rc = eval_unary_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_UNION: + rc = eval_union_expr(exp, tok_idx, count, set, options); + break; + case LYXP_EXPR_NONE: + rc = eval_path_expr(exp, tok_idx, set, options); + break; + default: + LOGINT_RET(set->ctx); + } + + return rc; +} + +/** + * @brief Get root type. + * + * @param[in] ctx_node Context node. + * @param[in] ctx_scnode Schema context node. + * @param[in] options XPath options. + * @return Root type. + */ +static enum lyxp_node_type +lyxp_get_root_type(const struct lyd_node *ctx_node, const struct lysc_node *ctx_scnode, uint32_t options) +{ + const struct lysc_node *op; + + /* explicit */ + if (options & LYXP_ACCESS_TREE_ALL) { + return LYXP_NODE_ROOT; + } else if (options & LYXP_ACCESS_TREE_CONFIG) { + return LYXP_NODE_ROOT_CONFIG; + } + + if (options & LYXP_SCNODE_ALL) { + /* schema */ + for (op = ctx_scnode; op && !(op->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); op = op->parent) {} + + if (op || (options & LYXP_SCNODE)) { + /* general root that can access everything */ + return LYXP_NODE_ROOT; + } else if (!ctx_scnode || (ctx_scnode->flags & LYS_CONFIG_W)) { + /* root context node can access only config data (because we said so, it is unspecified) */ + return LYXP_NODE_ROOT_CONFIG; + } + return LYXP_NODE_ROOT; + } + + /* data */ + op = ctx_node ? ctx_node->schema : NULL; + for ( ; op && !(op->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); op = op->parent) {} + + if (op || !(options & LYXP_SCHEMA)) { + /* general root that can access everything */ + return LYXP_NODE_ROOT; + } else if (!ctx_node || !ctx_node->schema || (ctx_node->schema->flags & LYS_CONFIG_W)) { + /* root context node can access only config data (because we said so, it is unspecified) */ + return LYXP_NODE_ROOT_CONFIG; + } + return LYXP_NODE_ROOT; +} + +LY_ERR +lyxp_eval(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct lys_module *cur_mod, + LY_VALUE_FORMAT format, void *prefix_data, const struct lyd_node *cur_node, const struct lyd_node *ctx_node, + const struct lyd_node *tree, const struct lyxp_var *vars, struct lyxp_set *set, uint32_t options) +{ + uint32_t tok_idx = 0; + LY_ERR rc; + + LY_CHECK_ARG_RET(ctx, ctx, exp, set, LY_EINVAL); + if (!cur_mod && ((format == LY_VALUE_SCHEMA) || (format == LY_VALUE_SCHEMA_RESOLVED))) { + LOGERR(ctx, LY_EINVAL, "Current module must be set if schema format is used."); + return LY_EINVAL; + } + + if (tree) { + /* adjust the pointer to be the first top-level sibling */ + while (tree->parent) { + tree = lyd_parent(tree); + } + tree = lyd_first_sibling(tree); + + if (lysc_data_parent(tree->schema)) { + /* unable to evaluate absolute paths */ + LOGERR(ctx, LY_EINVAL, "Data node \"%s\" has no parent but is not instance of a top-level schema node.", + LYD_NAME(tree)); + return LY_EINVAL; + } + } + + /* prepare set for evaluation */ + memset(set, 0, sizeof *set); + set->type = LYXP_SET_NODE_SET; + set->root_type = lyxp_get_root_type(ctx_node, NULL, options); + set_insert_node(set, (struct lyd_node *)ctx_node, 0, ctx_node ? LYXP_NODE_ELEM : set->root_type, 0); + + set->ctx = (struct ly_ctx *)ctx; + set->cur_node = cur_node; + for (set->context_op = cur_node ? cur_node->schema : NULL; + set->context_op && !(set->context_op->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); + set->context_op = set->context_op->parent) {} + set->tree = tree; + set->cur_mod = cur_mod; + set->format = format; + set->prefix_data = prefix_data; + set->vars = vars; + + LOG_LOCSET(NULL, set->cur_node, NULL, NULL); + + /* evaluate */ + rc = eval_expr_select(exp, &tok_idx, 0, set, options); + if (rc != LY_SUCCESS) { + lyxp_set_free_content(set); + } + + if (set->cur_node) { + LOG_LOCBACK(0, 1, 0, 0); + } + return rc; +} + +#if 0 + +/* full xml printing of set elements, not used currently */ + +void +lyxp_set_print_xml(FILE *f, struct lyxp_set *set) +{ + uint32_t i; + char *str_num; + struct lyout out; + + memset(&out, 0, sizeof out); + + out.type = LYOUT_STREAM; + out.method.f = f; + + switch (set->type) { + case LYXP_SET_EMPTY: + ly_print_(&out, "Empty XPath set\n\n"); + break; + case LYXP_SET_BOOLEAN: + ly_print_(&out, "Boolean XPath set:\n"); + ly_print_(&out, "%s\n\n", set->value.bool ? "true" : "false"); + break; + case LYXP_SET_STRING: + ly_print_(&out, "String XPath set:\n"); + ly_print_(&out, "\"%s\"\n\n", set->value.str); + break; + case LYXP_SET_NUMBER: + ly_print_(&out, "Number XPath set:\n"); + + if (isnan(set->value.num)) { + str_num = strdup("NaN"); + } else if ((set->value.num == 0) || (set->value.num == -0.0f)) { + str_num = strdup("0"); + } else if (isinf(set->value.num) && !signbit(set->value.num)) { + str_num = strdup("Infinity"); + } else if (isinf(set->value.num) && signbit(set->value.num)) { + str_num = strdup("-Infinity"); + } else if ((long long)set->value.num == set->value.num) { + if (asprintf(&str_num, "%lld", (long long)set->value.num) == -1) { + str_num = NULL; + } + } else { + if (asprintf(&str_num, "%03.1Lf", set->value.num) == -1) { + str_num = NULL; + } + } + if (!str_num) { + LOGMEM; + return; + } + ly_print_(&out, "%s\n\n", str_num); + free(str_num); + break; + case LYXP_SET_NODE_SET: + ly_print_(&out, "Node XPath set:\n"); + + for (i = 0; i < set->used; ++i) { + ly_print_(&out, "%d. ", i + 1); + switch (set->node_type[i]) { + case LYXP_NODE_ROOT_ALL: + ly_print_(&out, "ROOT all\n\n"); + break; + case LYXP_NODE_ROOT_CONFIG: + ly_print_(&out, "ROOT config\n\n"); + break; + case LYXP_NODE_ROOT_STATE: + ly_print_(&out, "ROOT state\n\n"); + break; + case LYXP_NODE_ROOT_NOTIF: + ly_print_(&out, "ROOT notification \"%s\"\n\n", set->value.nodes[i]->schema->name); + break; + case LYXP_NODE_ROOT_RPC: + ly_print_(&out, "ROOT rpc \"%s\"\n\n", set->value.nodes[i]->schema->name); + break; + case LYXP_NODE_ROOT_OUTPUT: + ly_print_(&out, "ROOT output \"%s\"\n\n", set->value.nodes[i]->schema->name); + break; + case LYXP_NODE_ELEM: + ly_print_(&out, "ELEM \"%s\"\n", set->value.nodes[i]->schema->name); + xml_print_node(&out, 1, set->value.nodes[i], 1, LYP_FORMAT); + ly_print_(&out, "\n"); + break; + case LYXP_NODE_TEXT: + ly_print_(&out, "TEXT \"%s\"\n\n", ((struct lyd_node_leaf_list *)set->value.nodes[i])->value_str); + break; + case LYXP_NODE_ATTR: + ly_print_(&out, "ATTR \"%s\" = \"%s\"\n\n", set->value.attrs[i]->name, set->value.attrs[i]->value); + break; + } + } + break; + } +} + +#endif + +LY_ERR +lyxp_set_cast(struct lyxp_set *set, enum lyxp_set_type target) +{ + long double num; + char *str; + LY_ERR rc; + + if (!set || (set->type == target)) { + return LY_SUCCESS; + } + + /* it's not possible to convert anything into a node set */ + assert(target != LYXP_SET_NODE_SET); + + if (set->type == LYXP_SET_SCNODE_SET) { + lyxp_set_free_content(set); + return LY_EINVAL; + } + + /* to STRING */ + if ((target == LYXP_SET_STRING) || ((target == LYXP_SET_NUMBER) && (set->type == LYXP_SET_NODE_SET))) { + switch (set->type) { + case LYXP_SET_NUMBER: + if (isnan(set->val.num)) { + set->val.str = strdup("NaN"); + LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), -1); + } else if ((set->val.num == 0) || (set->val.num == -0.0f)) { + set->val.str = strdup("0"); + LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), -1); + } else if (isinf(set->val.num) && !signbit(set->val.num)) { + set->val.str = strdup("Infinity"); + LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), -1); + } else if (isinf(set->val.num) && signbit(set->val.num)) { + set->val.str = strdup("-Infinity"); + LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), -1); + } else if ((long long)set->val.num == set->val.num) { + if (asprintf(&str, "%lld", (long long)set->val.num) == -1) { + LOGMEM_RET(set->ctx); + } + set->val.str = str; + } else { + if (asprintf(&str, "%03.1Lf", set->val.num) == -1) { + LOGMEM_RET(set->ctx); + } + set->val.str = str; + } + break; + case LYXP_SET_BOOLEAN: + if (set->val.bln) { + set->val.str = strdup("true"); + } else { + set->val.str = strdup("false"); + } + LY_CHECK_ERR_RET(!set->val.str, LOGMEM(set->ctx), LY_EMEM); + break; + case LYXP_SET_NODE_SET: + /* we need the set sorted, it affects the result */ + assert(!set_sort(set)); + + rc = cast_node_set_to_string(set, &str); + LY_CHECK_RET(rc); + lyxp_set_free_content(set); + set->val.str = str; + break; + default: + LOGINT_RET(set->ctx); + } + set->type = LYXP_SET_STRING; + } + + /* to NUMBER */ + if (target == LYXP_SET_NUMBER) { + switch (set->type) { + case LYXP_SET_STRING: + num = cast_string_to_number(set->val.str); + lyxp_set_free_content(set); + set->val.num = num; + break; + case LYXP_SET_BOOLEAN: + if (set->val.bln) { + set->val.num = 1; + } else { + set->val.num = 0; + } + break; + default: + LOGINT_RET(set->ctx); + } + set->type = LYXP_SET_NUMBER; + } + + /* to BOOLEAN */ + if (target == LYXP_SET_BOOLEAN) { + switch (set->type) { + case LYXP_SET_NUMBER: + if ((set->val.num == 0) || (set->val.num == -0.0f) || isnan(set->val.num)) { + set->val.bln = 0; + } else { + set->val.bln = 1; + } + break; + case LYXP_SET_STRING: + if (set->val.str[0]) { + lyxp_set_free_content(set); + set->val.bln = 1; + } else { + lyxp_set_free_content(set); + set->val.bln = 0; + } + break; + case LYXP_SET_NODE_SET: + if (set->used) { + lyxp_set_free_content(set); + set->val.bln = 1; + } else { + lyxp_set_free_content(set); + set->val.bln = 0; + } + break; + default: + LOGINT_RET(set->ctx); + } + set->type = LYXP_SET_BOOLEAN; + } + + return LY_SUCCESS; +} + +LY_ERR +lyxp_atomize(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct lys_module *cur_mod, + LY_VALUE_FORMAT format, void *prefix_data, const struct lysc_node *cur_scnode, + const struct lysc_node *ctx_scnode, struct lyxp_set *set, uint32_t options) +{ + LY_ERR ret; + uint32_t tok_idx = 0; + + LY_CHECK_ARG_RET(ctx, ctx, exp, set, LY_EINVAL); + if (!cur_mod && ((format == LY_VALUE_SCHEMA) || (format == LY_VALUE_SCHEMA_RESOLVED))) { + LOGARG(NULL, "Current module must be set if schema format is used."); + return LY_EINVAL; + } + + /* prepare set for evaluation */ + memset(set, 0, sizeof *set); + set->type = LYXP_SET_SCNODE_SET; + set->root_type = lyxp_get_root_type(NULL, ctx_scnode, options); + LY_CHECK_RET(set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL)); + set->val.scnodes[0].in_ctx = LYXP_SET_SCNODE_START; + + set->ctx = (struct ly_ctx *)ctx; + set->cur_scnode = cur_scnode; + for (set->context_op = cur_scnode; + set->context_op && !(set->context_op->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); + set->context_op = set->context_op->parent) {} + set->cur_mod = cur_mod; + set->format = format; + set->prefix_data = prefix_data; + + LOG_LOCSET(set->cur_scnode, NULL, NULL, NULL); + + /* evaluate */ + ret = eval_expr_select(exp, &tok_idx, 0, set, options); + + LOG_LOCBACK(set->cur_scnode ? 1 : 0, 0, 0, 0); + return ret; +} + +LIBYANG_API_DEF const char * +lyxp_get_expr(const struct lyxp_expr *path) +{ + if (!path) { + return NULL; + } + + return path->expr; +} diff --git a/src/xpath.h b/src/xpath.h new file mode 100644 index 0000000..3e61bb0 --- /dev/null +++ b/src/xpath.h @@ -0,0 +1,517 @@ +/** + * @file xpath.h + * @author Michal Vasko + * @brief YANG XPath evaluation functions header + * + * Copyright (c) 2015 - 2022 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_XPATH_H +#define LY_XPATH_H + +#include +#include + +#include "compat.h" +#include "log.h" +#include "tree.h" +#include "tree_schema.h" + +struct ly_ctx; +struct lyd_node; + +/** + * @internal + * @page internals + * @section internalsXpath XPath Implementation + * + * XPath evaluator fully compliant with http://www.w3.org/TR/1999/REC-xpath-19991116/ + * except the following restrictions in the grammar. + * + * @subsection internalsXpathGrammar Parsed Grammar + * + * Full axes are not supported, abbreviated forms must be used, + * "id()" function is not supported, and processing instruction and comment nodes are not supported, + * which is also reflected in the grammar. Undefined rules and constants are tokens. + * + * Modified full grammar: + * @code + * [1] Expr ::= OrExpr // just an alias + * + * [2] LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + * [3] AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + * [4] RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + * [5] Step ::= (AxisName '::' | '@')? NodeTest Predicate* | '.' | '..' + * [6] NodeTest ::= NameTest | NodeType '(' ')' + * [7] NameTest ::= '*' | NCName ':' '*' | QName + * [8] NodeType ::= 'text' | 'node' + * [9] Predicate ::= '[' Expr ']' + * [10] PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + * [11] FunctionCall ::= FunctionName '(' ( Expr ( ',' Expr )* )? ')' + * [12] PathExpr ::= LocationPath | PrimaryExpr Predicate* + * | PrimaryExpr Predicate* '/' RelativeLocationPath + * | PrimaryExpr Predicate* '//' RelativeLocationPath + * [13] OrExpr ::= AndExpr | OrExpr 'or' AndExpr + * [14] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + * [15] EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr + * | EqualityExpr '!=' RelationalExpr + * [16] RelationalExpr ::= AdditiveExpr + * | RelationalExpr '<' AdditiveExpr + * | RelationalExpr '>' AdditiveExpr + * | RelationalExpr '<=' AdditiveExpr + * | RelationalExpr '>=' AdditiveExpr + * [17] AdditiveExpr ::= MultiplicativeExpr + * | AdditiveExpr '+' MultiplicativeExpr + * | AdditiveExpr '-' MultiplicativeExpr + * [18] MultiplicativeExpr ::= UnaryExpr + * | MultiplicativeExpr '*' UnaryExpr + * | MultiplicativeExpr 'div' UnaryExpr + * | MultiplicativeExpr 'mod' UnaryExpr + * [19] UnaryExpr ::= UnionExpr | '-' UnaryExpr + * [20] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + * @endcode + */ + +/* expression tokens allocation */ +#define LYXP_EXPR_SIZE_START 10 +#define LYXP_EXPR_SIZE_STEP 5 + +/* XPath matches allocation */ +#define LYXP_SET_SIZE_START 4 +#define LYXP_SET_SIZE_MUL_STEP 2 + +/* building string when casting */ +#define LYXP_STRING_CAST_SIZE_START 64 +#define LYXP_STRING_CAST_SIZE_STEP 16 + +/* Maximum number of nested expressions. */ +#define LYXP_MAX_BLOCK_DEPTH 100 + +/** + * @brief Tokens that can be in an XPath expression. + */ +enum lyxp_token { + LYXP_TOKEN_NONE = 0, + LYXP_TOKEN_PAR1, /* '(' */ + LYXP_TOKEN_PAR2, /* ')' */ + LYXP_TOKEN_BRACK1, /* '[' */ + LYXP_TOKEN_BRACK2, /* ']' */ + LYXP_TOKEN_DOT, /* '.' */ + LYXP_TOKEN_DDOT, /* '..' */ + LYXP_TOKEN_AT, /* '@' */ + LYXP_TOKEN_COMMA, /* ',' */ + LYXP_TOKEN_DCOLON, /* '::' */ + LYXP_TOKEN_NAMETEST, /* NameTest */ + LYXP_TOKEN_NODETYPE, /* NodeType */ + LYXP_TOKEN_VARREF, /* VariableReference */ + LYXP_TOKEN_FUNCNAME, /* FunctionName */ + LYXP_TOKEN_OPER_LOG, /* Operator 'and', 'or' */ + LYXP_TOKEN_OPER_EQUAL, /* Operator '=' */ + LYXP_TOKEN_OPER_NEQUAL, /* Operator '!=' */ + LYXP_TOKEN_OPER_COMP, /* Operator '<', '<=', '>', '>=' */ + LYXP_TOKEN_OPER_MATH, /* Operator '+', '-', '*', 'div', 'mod', '-' (unary) */ + LYXP_TOKEN_OPER_UNI, /* Operator '|' */ + LYXP_TOKEN_OPER_PATH, /* Operator '/' */ + LYXP_TOKEN_OPER_RPATH, /* Operator '//' (recursive path) */ + LYXP_TOKEN_AXISNAME, /* AxisName */ + LYXP_TOKEN_LITERAL, /* Literal - with either single or double quote */ + LYXP_TOKEN_NUMBER /* Number */ +}; + +/** + * @brief XPath Axes types. + */ +enum lyxp_axis { + LYXP_AXIS_ANCESTOR, + LYXP_AXIS_ANCESTOR_OR_SELF, + LYXP_AXIS_ATTRIBUTE, + LYXP_AXIS_CHILD, + LYXP_AXIS_DESCENDANT, + LYXP_AXIS_DESCENDANT_OR_SELF, + LYXP_AXIS_FOLLOWING, + LYXP_AXIS_FOLLOWING_SIBLING, + // LYXP_AXIS_NAMESPACE, /* not supported */ + LYXP_AXIS_PARENT, + LYXP_AXIS_PRECEDING, + LYXP_AXIS_PRECEDING_SIBLING, + LYXP_AXIS_SELF +}; + +/** + * @brief XPath (sub)expressions that can be repeated. + */ +enum lyxp_expr_type { + LYXP_EXPR_NONE = 0, + LYXP_EXPR_OR, + LYXP_EXPR_AND, + LYXP_EXPR_EQUALITY, + LYXP_EXPR_RELATIONAL, + LYXP_EXPR_ADDITIVE, + LYXP_EXPR_MULTIPLICATIVE, + LYXP_EXPR_UNARY, + LYXP_EXPR_UNION +}; + +/** + * @brief Types of context nodes, #LYXP_NODE_ROOT_CONFIG used only in when or must conditions. + */ +enum lyxp_node_type { + LYXP_NODE_NONE, /* invalid node type */ + + /* XML document roots */ + LYXP_NODE_ROOT, /* access to all the data (node value first top-level node) */ + LYXP_NODE_ROOT_CONFIG, /* data context, no state data (node value first top-level node) */ + + /* XML elements */ + LYXP_NODE_ELEM, /* YANG data element (most common) */ + LYXP_NODE_TEXT, /* YANG data text element (extremely specific use, unlikely to be ever needed) */ + LYXP_NODE_META /* YANG metadata (do not use for the context node) */ +}; + +/** + * @brief Structure holding a parsed XPath expression. + */ +struct lyxp_expr { + enum lyxp_token *tokens; /**< Array of tokens. */ + uint32_t *tok_pos; /**< Array of the token offsets in expr. */ + uint32_t *tok_len; /**< Array of token lengths in expr. */ + enum lyxp_expr_type **repeat; /**< Array of expression types that this token begins and is repeated ended with 0, + more in the comment after this declaration. */ + uint32_t used; /**< Used array items. */ + uint32_t size; /**< Allocated array items. */ + + const char *expr; /**< The original XPath expression. */ +}; + +/* + * lyxp_expr repeat + * + * This value is NULL for all the tokens that do not begin an + * expression which can be repeated. Otherwise it is an array + * of expression types that this token begins. These values + * are used during evaluation to know whether we need to + * duplicate the current context or not and to decide what + * the current expression is (for example, if we are only + * starting the parsing and the first token has no repeat, + * we do not parse it as an OrExpr but directly as PathExpr). + * Examples: + * + * Expr: "/ *[key1 and key2 or key1 < key2]" + * Tokens: '/' '*' '[' NameTest 'and' NameTest 'or' NameTest '<' NameTest ']' + * Repeat: NULL NULL NULL _ NULL NULL NULL _ NULL NULL NULL + * | v + * v RelationalExpr 0 + * AndExpr OrExpr 0 + * + * Expr: "//node[key and node2]/key | /cont" + * Tokens: '//' NameTest '[' NameTest 'and' NameTest ']' '/' NameTest '|' '/' NameTest + * Repeat: _ NULL NULL _ NULL NULL NULL NULL NULL NULL NULL NULL + * | v + * v AndExpr 0 + * UnionExpr 0 + * + * Operators between expressions which this concerns: + * 'or', 'and', '=', '!=', '<', '>', '<=', '>=', '+', '-', '*', 'div', 'mod', '|' + */ + +/** + * @brief Supported types of (partial) XPath results. + */ +enum lyxp_set_type { + LYXP_SET_NODE_SET = 0, + LYXP_SET_SCNODE_SET, + LYXP_SET_BOOLEAN, + LYXP_SET_NUMBER, + LYXP_SET_STRING +}; + +/** + * @brief Item stored in an XPath set hash table. + */ +struct lyxp_set_hash_node { + struct lyd_node *node; + enum lyxp_node_type type; +} _PACKED; + +/** + * @brief XPath variable bindings. + */ +struct lyxp_var { + char *name; /**< Variable name. In the XPath expression, the name is preceded by a '$' character. */ + char *value; /**< The value of a variable is an object, which can be of any of the type that are possible + for the value of an expression. */ +}; + +/** + * @brief XPath set - (partial) result. + */ +struct lyxp_set { + enum lyxp_set_type type; /**< Type of the object (value). */ + + union { + struct lyxp_set_node { + struct lyd_node *node; /**< Data node. */ + enum lyxp_node_type type; /**< Type of the node. */ + uint32_t pos; /**< Unique node position in the data. */ + } *nodes; /**< Set of data nodes. */ + struct lyxp_set_scnode { + struct lysc_node *scnode; /**< Compiled YANG node. */ + enum lyxp_node_type type; /**< Type of the node. */ + +/* _START and _ATOM values should have grouped values */ +#define LYXP_SET_SCNODE_START -2 /**< scnode not traversed, currently (the only node) in context */ +#define LYXP_SET_SCNODE_START_USED -1 /**< scnode not traversed except for the eval start, not currently in the context */ +#define LYXP_SET_SCNODE_ATOM_NODE 0 /**< scnode was traversed, but not currently in the context */ +#define LYXP_SET_SCNODE_ATOM_VAL 1 /**< scnode was traversed and its value used, but not currently in the context */ +#define LYXP_SET_SCNODE_ATOM_CTX 2 /**< scnode currently in context */ +#define LYXP_SET_SCNODE_ATOM_NEW_CTX 3 /**< scnode in context and just added, so skip it for the current operation */ +#define LYXP_SET_SCNODE_ATOM_PRED_CTX 4 /**< includes any higher value - scnode is not in context because we are in + a predicate and this scnode was used/will be used later */ + int32_t in_ctx; /**< Flag specifies the state of the node in context. Values are defined + as LYXP_SET_SCNODE_* */ + enum lyxp_axis axis; /**< Axis defines on what axis was this schema node reached. */ + } *scnodes; /**< Set of compiled YANG data nodes. */ + struct lyxp_set_meta { + struct lyd_meta *meta; /**< Node that provides information about metadata of a data element. */ + enum lyxp_node_type type; /**< Type of the node. */ + uint32_t pos; /**< Unique node position in the data. if node_type is LYXP_SET_NODE_META, + it is the parent node position */ + } *meta; /**< Set of YANG metadata objects. */ + char *str; /**< String object. */ + long double num; /**< Object of the floating-point number. */ + ly_bool bln; /**< Boolean object. */ + } val; /**< Evaluated object (value). */ + + /* this is valid only for type LYXP_SET_NODE_SET and LYXP_SET_SCNODE_SET */ + uint32_t used; /**< Number of nodes in the set. */ + uint32_t size; /**< Allocated size for the set. */ + struct hash_table *ht; /**< Hash table for quick determination of whether a node is in the set. */ + + /* XPath context information, this is valid only for type LYXP_SET_NODE_SET */ + uint32_t ctx_pos; /**< Position of the current examined node in the set. */ + uint32_t ctx_size; /**< Position of the last node at the time the node was examined. */ + ly_bool non_child_axis; /**< Whether any node change was performed on a non-child axis. */ + + /* general context */ + struct ly_ctx *ctx; /**< General context for logging. */ + + union { + const struct lyd_node *cur_node; /**< Current (original context) node. */ + const struct lysc_node *cur_scnode; /**< Current (original context) compiled node. */ + }; + enum lyxp_node_type root_type; /**< Type of root node. */ + const struct lysc_node *context_op; /**< Schema of the current node. */ + const struct lyd_node *tree; /**< Data tree on which to perform the evaluation. */ + const struct lys_module *cur_mod; /**< Current module for the expression (where it was "instantiated"). */ + LY_VALUE_FORMAT format; /**< Format of the XPath expression. */ + void *prefix_data; /**< Format-specific prefix data (see ::ly_resolve_prefix). */ + const struct lyxp_var *vars; /**< XPath variables. [Sized array](@ref sizedarrays). + Set of variable bindings. */ +}; + +/** + * @brief Get string format of an XPath token. + * + * @param[in] tok Token to transform. + * @return Token type string. + */ +const char *lyxp_token2str(enum lyxp_token tok); + +/** + * @brief Evaluate an XPath expression on data. Be careful when using this function, the result can often + * be confusing without thorough understanding of XPath evaluation rules defined in RFC 7950. + * + * @param[in] ctx libyang context to use. + * @param[in] exp Parsed XPath expression to be evaluated. + * @param[in] cur_mod Current module for the expression (where it was "instantiated"). + * @param[in] format Format of the XPath expression (more specifically, of any used prefixes). + * @param[in] prefix_data Format-specific prefix data (see ::ly_resolve_prefix). + * @param[in] cur_node Current data node, NULL in case of the root node. Equal to @p ctx_node unless a + * subexpression is being evaluated. + * @param[in] ctx_node Starting context data node, NULL in case of the root node. Equal to @p cur_node unless a + * subexpression is being evaluated. + * @param[in] tree Data tree on which to perform the evaluation, it must include all the available data (including + * the tree of @p ctx_node). Can be any node of the tree, it is adjusted. + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[out] set Result set. + * @param[in] options Whether to apply some evaluation restrictions. + * @return LY_EVALID for invalid argument types/count, + * @return LY_EINCOMPLETE for unresolved when, + * @return LY_EINVAL, LY_EMEM, LY_EINT for other errors. + */ +LY_ERR lyxp_eval(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct lys_module *cur_mod, + LY_VALUE_FORMAT format, void *prefix_data, const struct lyd_node *cur_node, const struct lyd_node *ctx_node, + const struct lyd_node *tree, const struct lyxp_var *vars, struct lyxp_set *set, uint32_t options); + +/** + * @brief Get all the partial XPath nodes (atoms) that are required for @p exp to be evaluated. + * + * @param[in] ctx libyang context to use. + * @param[in] exp Parsed XPath expression to be evaluated. + * @param[in] cur_mod Current module for the expression (where it was "instantiated"). + * @param[in] format Format of the XPath expression (more specifically, of any used prefixes). + * @param[in] prefix_data Format-specific prefix data (see ::ly_resolve_prefix). + * @param[in] cur_scnode Current schema node, NULL in case of the root node. Equal to @p ctx_scnode unless a + * subexpression is being atomized. + * @param[in] ctx_scnode Starting context schema node, NULL in case of the root node. Equal to @p cur_scnode unless a + * subexpression is being atomized. + * @param[out] set Result set. + * @param[in] options Whether to apply some evaluation restrictions, one flag must always be used. + * @return LY_ERR (same as ::lyxp_eval()). + */ +LY_ERR lyxp_atomize(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct lys_module *cur_mod, + LY_VALUE_FORMAT format, void *prefix_data, const struct lysc_node *cur_scnode, + const struct lysc_node *ctx_scnode, struct lyxp_set *set, uint32_t options); + +/** used only internally, maps with @ref findxpathoptions */ +#define LYXP_IGNORE_WHEN 0x01 /**< Ignore unevaluated when in data nodes and do not return ::LY_EINCOMPLETE. */ +#define LYXP_SCHEMA 0x02 /**< Apply data node access restrictions defined for 'when' and 'must' evaluation. */ +#define LYXP_SCNODE 0x04 /**< No special tree access modifiers. */ +#define LYXP_SCNODE_SCHEMA LYS_FIND_XP_SCHEMA /**< Apply node access restrictions defined for 'when' and 'must' evaluation. */ +#define LYXP_SCNODE_OUTPUT LYS_FIND_XP_OUTPUT /**< Search RPC/action output nodes instead of input ones. */ +#define LYXP_SCNODE_ALL 0x1C /**< mask for all the LYXP_* values */ +#define LYXP_SKIP_EXPR 0x20 /**< The rest of the expression will not be evaluated (lazy evaluation) */ +#define LYXP_SCNODE_ERROR LYS_FIND_NO_MATCH_ERROR /**< Return error if a path segment matches no nodes, otherwise only + warning is printed. */ +#define LYXP_ACCESS_TREE_ALL 0x80 /**< Explicit accessible tree of all the nodes. */ +#define LYXP_ACCESS_TREE_CONFIG 0x0100 /**< Explicit accessible tree of only configuration data. */ + +/** + * @brief Cast XPath set to another type. + * Indirectly context position aware. + * + * @param[in] set Set to cast. + * @param[in] target Target type to cast \p set into. + * @return LY_ERR + */ +LY_ERR lyxp_set_cast(struct lyxp_set *set, enum lyxp_set_type target); + +/** + * @brief Free dynamic content of a set. + * + * @param[in] set Set to modify. + */ +void lyxp_set_free_content(struct lyxp_set *set); + +/** + * @brief Check for duplicates in a schema node set. + * + * @param[in] set Set to check. + * @param[in] node Node to look for in @p set. + * @param[in] node_type Type of @p node. + * @param[in] skip_idx Index from @p set to skip. + * @param[out] index_p Optional pointer to store index if the node is found. + * @return Boolean value whether the @p node found or not. + */ +ly_bool lyxp_set_scnode_contains(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, + int skip_idx, uint32_t *index_p); + +/** + * @brief Merge 2 schema node sets. + * + * @param[in] set1 Set to merge into. + * @param[in] set2 Set to merge. Its content is freed. + */ +void lyxp_set_scnode_merge(struct lyxp_set *set1, struct lyxp_set *set2); + +/** + * @brief Parse an XPath expression into a structure of tokens. + * Logs directly. + * + * https://www.w3.org/TR/1999/REC-xpath-19991116/#exprlex + * + * @param[in] ctx Context for errors. + * @param[in] expr_str XPath expression to parse. It is duplicated. + * @param[in] expr_len Length of @p expr, can be 0 if @p expr is 0-terminated. + * @param[in] reparse Whether to re-parse the expression to finalize full XPath parsing and fill + * information about expressions and their operators (fill repeat). + * @param[out] expr_p Pointer to return the filled expression structure. + * @return LY_SUCCESS in case of success. + * @return LY_EMEM in case of memory allocation failure. + * @return LY_EVALID in case of invalid XPath expression in @p expr_str. + */ +LY_ERR lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, ly_bool reparse, + struct lyxp_expr **expr_p); + +/** + * @brief Duplicate parsed XPath expression. + * + * If @p start_idx and @p end_idx are both 0, the whole expression is duplicated. + * + * @param[in] ctx Context with a dictionary. + * @param[in] exp Parsed expression. + * @param[in] start_idx Starting @p exp index to duplicate. + * @param[in] end_idx Last @p exp index to duplicate. + * @param[out] dup Duplicated structure. + * @return LY_ERR value. + */ +LY_ERR lyxp_expr_dup(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t start_idx, uint32_t end_idx, + struct lyxp_expr **dup); + +/** + * @brief Look at the next token and check its kind. + * + * @param[in] ctx Context for logging, not logged if NULL. + * @param[in] exp Expression to use. + * @param[in] tok_idx Token index in the expression \p exp. + * @param[in] want_tok Expected token. + * @return LY_EINCOMPLETE on EOF, + * @return LY_ENOT on non-matching token, + * @return LY_SUCCESS on success. + */ +LY_ERR lyxp_check_token(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t tok_idx, enum lyxp_token want_tok); + +/** + * @brief Look at the next token and skip it if it matches the expected one. + * + * @param[in] ctx Context for logging, not logged if NULL. + * @param[in] exp Expression to use. + * @param[in,out] tok_idx Token index in the expression \p exp, is updated. + * @param[in] want_tok Expected token. + * @return LY_EINCOMPLETE on EOF, + * @return LY_ENOT on non-matching token, + * @return LY_SUCCESS on success. + */ +LY_ERR lyxp_next_token(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_token want_tok); + +/** + * @brief Look at the next token and skip it if it matches either of the 2 expected ones. + * + * @param[in] ctx Context for logging, not logged if NULL. + * @param[in] exp Expression to use. + * @param[in,out] tok_idx Token index in the expression \p exp, is updated. + * @param[in] want_tok1 Expected token 1. + * @param[in] want_tok2 Expected token 2. + * @return LY_EINCOMPLETE on EOF, + * @return LY_ENOT on non-matching token, + * @return LY_SUCCESS on success. + */ +LY_ERR lyxp_next_token2(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t *tok_idx, + enum lyxp_token want_tok1, enum lyxp_token want_tok2); + +/** + * @brief Find variable named @name in @p vars. + * + * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. + * @param[in] name Name of the variable being searched. + * @param[in] name_len Name length can be set to 0 if @p name is terminated by null byte. + * @param[out] var Variable that was found. The parameter is optional. + * @return LY_SUCCESS if the variable was found, otherwise LY_ENOTFOUND. + */ +LY_ERR lyxp_vars_find(struct lyxp_var *vars, const char *name, size_t name_len, struct lyxp_var **var); + +/** + * @brief Frees a parsed XPath expression. @p expr should not be used afterwards. + * + * @param[in] ctx libyang context of the expression. + * @param[in] expr Expression to free. + */ +void lyxp_expr_free(const struct ly_ctx *ctx, struct lyxp_expr *expr); + +#endif /* LY_XPATH_H */ -- cgit v1.2.3