summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common.c770
-rw-r--r--src/common.h626
-rw-r--r--src/config.h.in74
-rw-r--r--src/context.c1277
-rw-r--r--src/context.h668
-rw-r--r--src/dict.h122
-rw-r--r--src/diff.c2151
-rw-r--r--src/diff.h62
-rw-r--r--src/hash_table.c880
-rw-r--r--src/hash_table.h279
-rw-r--r--src/in.c329
-rw-r--r--src/in.h252
-rw-r--r--src/in_internal.h50
-rw-r--r--src/json.c1047
-rw-r--r--src/json.h139
-rw-r--r--src/libyang.h167
-rw-r--r--src/log.c773
-rw-r--r--src/log.h404
-rw-r--r--src/lyb.c125
-rw-r--r--src/lyb.h199
-rw-r--r--src/out.c768
-rw-r--r--src/out.h307
-rw-r--r--src/out_internal.h110
-rw-r--r--src/parser_common.c3567
-rw-r--r--src/parser_data.h461
-rw-r--r--src/parser_internal.h392
-rw-r--r--src/parser_json.c1819
-rw-r--r--src/parser_lyb.c1792
-rw-r--r--src/parser_schema.h179
-rw-r--r--src/parser_xml.c1816
-rw-r--r--src/parser_yang.c4827
-rw-r--r--src/parser_yin.c4012
-rw-r--r--src/path.c1193
-rw-r--r--src/path.h263
-rw-r--r--src/plugins.c550
-rw-r--r--src/plugins.h94
-rw-r--r--src/plugins_exts.c680
-rw-r--r--src/plugins_exts.h1048
-rw-r--r--src/plugins_exts/metadata.c243
-rw-r--r--src/plugins_exts/metadata.h66
-rw-r--r--src/plugins_exts/nacm.c223
-rw-r--r--src/plugins_exts/schema_mount.c1332
-rw-r--r--src/plugins_exts/structure.c558
-rw-r--r--src/plugins_exts/yangdata.c277
-rw-r--r--src/plugins_internal.h85
-rw-r--r--src/plugins_types.c1043
-rw-r--r--src/plugins_types.h1214
-rw-r--r--src/plugins_types/binary.c466
-rw-r--r--src/plugins_types/bits.c510
-rw-r--r--src/plugins_types/boolean.c165
-rw-r--r--src/plugins_types/date_and_time.c339
-rw-r--r--src/plugins_types/decimal64.c239
-rw-r--r--src/plugins_types/empty.c103
-rw-r--r--src/plugins_types/enumeration.c202
-rw-r--r--src/plugins_types/identityref.c352
-rw-r--r--src/plugins_types/instanceid.c382
-rw-r--r--src/plugins_types/instanceid_keys.c229
-rw-r--r--src/plugins_types/integer.c585
-rw-r--r--src/plugins_types/ipv4_address.c377
-rw-r--r--src/plugins_types/ipv4_address_no_zone.c221
-rw-r--r--src/plugins_types/ipv4_prefix.c337
-rw-r--r--src/plugins_types/ipv6_address.c378
-rw-r--r--src/plugins_types/ipv6_address_no_zone.c312
-rw-r--r--src/plugins_types/ipv6_prefix.c351
-rw-r--r--src/plugins_types/leafref.c140
-rw-r--r--src/plugins_types/node_instanceid.c320
-rw-r--r--src/plugins_types/string.c109
-rw-r--r--src/plugins_types/union.c585
-rw-r--r--src/plugins_types/xpath1.0.c521
-rw-r--r--src/printer_data.c159
-rw-r--r--src/printer_data.h196
-rw-r--r--src/printer_internal.h218
-rw-r--r--src/printer_json.c1010
-rw-r--r--src/printer_lyb.c1335
-rw-r--r--src/printer_schema.c222
-rw-r--r--src/printer_schema.h232
-rw-r--r--src/printer_tree.c4673
-rw-r--r--src/printer_xml.c607
-rw-r--r--src/printer_yang.c2657
-rw-r--r--src/printer_yin.c1501
-rw-r--r--src/schema_compile.c1798
-rw-r--r--src/schema_compile.h397
-rw-r--r--src/schema_compile_amend.c2547
-rw-r--r--src/schema_compile_amend.h181
-rw-r--r--src/schema_compile_node.c4218
-rw-r--r--src/schema_compile_node.h202
-rw-r--r--src/schema_features.c714
-rw-r--r--src/schema_features.h67
-rw-r--r--src/set.c247
-rw-r--r--src/set.h181
-rw-r--r--src/tree.h250
-rw-r--r--src/tree_data.c2943
-rw-r--r--src/tree_data.h2601
-rw-r--r--src/tree_data_common.c1626
-rw-r--r--src/tree_data_free.c241
-rw-r--r--src/tree_data_hash.c237
-rw-r--r--src/tree_data_internal.h590
-rw-r--r--src/tree_data_new.c1914
-rw-r--r--src/tree_edit.h306
-rw-r--r--src/tree_schema.c2178
-rw-r--r--src/tree_schema.h2248
-rw-r--r--src/tree_schema_common.c2617
-rw-r--r--src/tree_schema_free.c1737
-rw-r--r--src/tree_schema_free.h221
-rw-r--r--src/tree_schema_internal.h735
-rw-r--r--src/validation.c2029
-rw-r--r--src/validation.h107
-rw-r--r--src/version.h.in23
-rw-r--r--src/xml.c1402
-rw-r--r--src/xml.h209
-rw-r--r--src/xpath.c9883
-rw-r--r--src/xpath.h517
112 files changed, 104512 insertions, 0 deletions
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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _WIN32
+#include <sys/mman.h>
+#else
+#include <io.h>
+#endif
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#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 <windows.h>
+# 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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 : "<none>");
+ 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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <a href="https://tools.ietf.org/html/rfc7895">ietf-yang-library</a> 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 <rkrejci@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdio.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdio.h>
+#include <sys/types.h>
+#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 <rkrejci@cesnet.cz>
+ * @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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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, &notif->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, &notif->dsc, Y_STR_ARG, &notif->exts));
+ break;
+ case LY_STMT_IF_FEATURE:
+ LY_CHECK_RET(lysp_stmt_qnames(ctx, child, &notif->iffeatures, Y_STR_ARG, &notif->exts));
+ break;
+ case LY_STMT_REFERENCE:
+ LY_CHECK_RET(lysp_stmt_text_field(ctx, child, 0, &notif->ref, Y_STR_ARG, &notif->exts));
+ break;
+ case LY_STMT_STATUS:
+ LY_CHECK_RET(lysp_stmt_status(ctx, child, &notif->flags, &notif->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, &notif->node, &notif->child));
+ break;
+ case LY_STMT_CHOICE:
+ LY_CHECK_RET(lysp_stmt_case(ctx, child, &notif->node, &notif->child));
+ break;
+ case LY_STMT_CONTAINER:
+ LY_CHECK_RET(lysp_stmt_container(ctx, child, &notif->node, &notif->child));
+ break;
+ case LY_STMT_LEAF:
+ LY_CHECK_RET(lysp_stmt_leaf(ctx, child, &notif->node, &notif->child));
+ break;
+ case LY_STMT_LEAF_LIST:
+ LY_CHECK_RET(lysp_stmt_leaflist(ctx, child, &notif->node, &notif->child));
+ break;
+ case LY_STMT_LIST:
+ LY_CHECK_RET(lysp_stmt_list(ctx, child, &notif->node, &notif->child));
+ break;
+ case LY_STMT_USES:
+ LY_CHECK_RET(lysp_stmt_uses(ctx, child, &notif->node, &notif->child));
+ break;
+
+ case LY_STMT_MUST:
+ PARSER_CHECK_STMTVER2_RET(ctx, "must", "notification");
+ LY_CHECK_RET(lysp_stmt_restrs(ctx, child, &notif->musts));
+ break;
+ case LY_STMT_TYPEDEF:
+ LY_CHECK_RET(lysp_stmt_typedef(ctx, child, &notif->node, &notif->typedefs));
+ break;
+ case LY_STMT_GROUPING:
+ LY_CHECK_RET(lysp_stmt_grouping(ctx, child, &notif->node, &notif->groupings));
+ break;
+ case LY_STMT_EXTENSION_INSTANCE:
+ LY_CHECK_RET(lysp_stmt_ext(ctx, child, LY_STMT_NOTIFICATION, 0, &notif->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 <rkrejci@cesnet.cz>
+ * @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 \<get\> or \<get-config\>
+ * 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 \<get-config\> 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 \<get\> 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 <rkrejci@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, &current));
+
+ 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, &current));
+ }
+
+ 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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rpc> 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 <notification> 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 <rpc-reply> 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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, &notif->dsc, Y_STR_ARG, &notif->exts));
+ break;
+ case LY_STMT_IF_FEATURE:
+ LY_CHECK_RET(parse_qnames(ctx, LY_STMT_IF_FEATURE, &notif->iffeatures, Y_STR_ARG, &notif->exts));
+ break;
+ case LY_STMT_REFERENCE:
+ LY_CHECK_RET(parse_text_field(ctx, notif->ref, LY_STMT_REFERENCE, 0, &notif->ref, Y_STR_ARG, &notif->exts));
+ break;
+ case LY_STMT_STATUS:
+ LY_CHECK_RET(parse_status(ctx, &notif->flags, &notif->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, &notif->child));
+ break;
+ case LY_STMT_CHOICE:
+ LY_CHECK_RET(parse_choice(ctx, (struct lysp_node *)notif, &notif->child));
+ break;
+ case LY_STMT_CONTAINER:
+ LY_CHECK_RET(parse_container(ctx, (struct lysp_node *)notif, &notif->child));
+ break;
+ case LY_STMT_LEAF:
+ LY_CHECK_RET(parse_leaf(ctx, (struct lysp_node *)notif, &notif->child));
+ break;
+ case LY_STMT_LEAF_LIST:
+ LY_CHECK_RET(parse_leaflist(ctx, (struct lysp_node *)notif, &notif->child));
+ break;
+ case LY_STMT_LIST:
+ LY_CHECK_RET(parse_list(ctx, (struct lysp_node *)notif, &notif->child));
+ break;
+ case LY_STMT_USES:
+ LY_CHECK_RET(parse_uses(ctx, (struct lysp_node *)notif, &notif->child));
+ break;
+
+ case LY_STMT_MUST:
+ PARSER_CHECK_STMTVER2_RET(ctx, "must", "notification");
+ LY_CHECK_RET(parse_restrs(ctx, kw, &notif->musts));
+ break;
+ case LY_STMT_TYPEDEF:
+ LY_CHECK_RET(parse_typedef(ctx, (struct lysp_node *)notif, &notif->typedefs));
+ break;
+ case LY_STMT_GROUPING:
+ LY_CHECK_RET(parse_grouping(ctx, (struct lysp_node *)notif, &notif->groupings));
+ break;
+ case LY_STMT_EXTENSION_INSTANCE:
+ LY_CHECK_RET(parse_ext(ctx, word, word_len, notif, LY_STMT_NOTIFICATION, 0, &notif->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 <xsedla1d@stud.fit.vutbr.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, &notif->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, &notif->child, 0,
+ LY_STMT_ANYXML, &notif->child, 0,
+ LY_STMT_CHOICE, &notif->child, 0,
+ LY_STMT_CONTAINER, &notif->child, 0,
+ LY_STMT_DESCRIPTION, &notif->dsc, YIN_SUBELEM_UNIQUE,
+ LY_STMT_GROUPING, &notif->groupings, 0,
+ LY_STMT_IF_FEATURE, &notif->iffeatures, 0,
+ LY_STMT_LEAF, &notif->child, 0,
+ LY_STMT_LEAF_LIST, &notif->child, 0,
+ LY_STMT_LIST, &notif->child, 0,
+ LY_STMT_MUST, &notif->musts, YIN_SUBELEM_VER2,
+ LY_STMT_REFERENCE, &notif->ref, YIN_SUBELEM_UNIQUE,
+ LY_STMT_STATUS, &notif->flags, YIN_SUBELEM_UNIQUE,
+ LY_STMT_TYPEDEF, &notif->typedefs, 0,
+ LY_STMT_USES, &notif->child, 0,
+ LY_STMT_EXTENSION_INSTANCE, NULL, 0));
+
+ ret = yin_parse_content(ctx, subelems, subelems_size, notif, LY_STMT_NOTIFICATION, NULL, &notif->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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 `<libyang/plugins_exts.h>` 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 `<libyang/plugins_ext.h>` 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 = {{<filled information of ::lyplg_ext_record>}, ..., {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 \<flags\> in a parent node. If @p ctx is set, ignore this parameter.
+ * @param[out] add_opts Additional tree-diagram \<opts\> string in a parent node which is printed before \<opts\>. 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 \<flags\> string in the @p node.
+ * @param[out] add_opts Additional tree-diagram \<opts\> string in the @p node which is printed before \<opts\>.
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <xvintr04@stud.fit.vutbr.cz>
+ * @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 <assert.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 `<libyang/plugins_types.h>` 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 `<libyang/plugins_types.h>` 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 = {{<filled information of ::lyplg_type_record>}, ..., {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 <rkrejci@cesnet.cz>
+ * @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 <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdio.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdio.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdio.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdio.h>
+
+#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 <piecek@cesnet.cz>
+ * @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 \<type\> 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 <assert.h>
+#include <string.h>
+
+#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 \<name\>. */
+ TRD_INDENT_LONG_LINE_BREAK = 2, /**< The new line should be indented so that it starts below \<name\> 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, /**< "..."___\<keys\>. */
+ TRD_INDENT_BEFORE_TYPE = 4, /**< "..."___\<type\>, but if mark is set then indent == 3. */
+ TRD_INDENT_BEFORE_IFFEATURES = 1 /**< "..."___\<iffeatures\>. */
+} 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 \<opts\>. */
+ int16_t btw_opts_type; /**< Indent between \<opts\> and \<type\>. */
+ int16_t btw_type_iffeatures; /**< Indent between type and features. Ignored if \<type\> 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: <module-name>
+ * +--<node>
+ * |
+ * @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: <module-name>
+ * +--<node>
+ *
+ * augment <target-node>:
+ * +--<node>
+ * @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 \<opts\> next to or
+ * around the \<name\>.
+ */
+typedef enum {
+ TRD_NODE_ELSE = 0, /**< For some node which does not require special treatment. \<name\> */
+ TRD_NODE_CASE, /**< For case node. :(\<name\>) */
+ TRD_NODE_CHOICE, /**< For choice node. (\<name\>) */
+ 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. \<name\>? */
+#define TRD_NODE_CONTAINER "!" /**< For a presence container. \<name\>! */
+#define TRD_NODE_LISTLEAFLIST "*" /**< For a leaf-list or list. \<name\>* */
+
+/**
+ * @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 [\<keys\>] 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 \<opts\> 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 \<type\>
+ */
+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 \<type\> in the \<node\>.
+ *
+ * @see TRP_EMPTY_TRT_TYPE, TRP_TRT_TYPE_IS_EMPTY, trp_print_type
+ */
+struct trt_type {
+ trt_type_type type; /**< Type of the \<type\>. */
+ 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 \<if-features\>.
+ */
+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 \<node\> data for printing.
+ *
+ * It contains RFC's:
+ * \<status\>--\<flags\> \<name\>\<opts\> \<type\> \<if-features\>.
+ * Item \<opts\> is moved to part struct trt_node_name.
+ * For printing [\<keys\>] 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; /**< \<status\>. */
+ const char *flags; /**< \<flags\>. */
+ struct trt_node_name name; /**< \<node\> with \<opts\> mark or [\<keys\>]. */
+ struct trt_type type; /**< \<type\> contains the name of the type or type for leafref. */
+ struct trt_iffeatures iffeatures; /**< \<if-features\>. */
+ 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: <module_name>:" label. */
+ TRD_SECT_AUGMENT, /**< The node belongs to some "augment <target-node>:" 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 <grouping-name>:" 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 \<flags\>. */
+ const char *add_opts; /**< Additional symbols for \<opts\>. */
+};
+
+/**
+ * @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);
+ /* +--<node>
+ * +--<node>
+ */
+ 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 [\<keys\>], \<type\> and
+ * \<iffeatures\> are empty/not_set.
+ * @param[in] node is item to test.
+ * @return 1 if node has no \<keys\> \<type\> or \<iffeatures\>
+ * 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;
+ }
+
+ /* <name><mark>___<keys>*/
+ 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 \<status\>--\<flags\> \<name\> 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;
+ }
+ /* <status>--<flags> */
+ ly_print_(out, "%s", node->status);
+ ly_print_(out, "--");
+ /* If the node is a case node, there is no space before the <name>
+ * also case node has no flags.
+ */
+ if (node->flags && (node->name.type != TRD_NODE_CASE)) {
+ ly_print_(out, "%s", node->flags);
+ ly_print_(out, " ");
+ }
+ /* <name> */
+ trp_print_node_name(node->name, out);
+}
+
+/**
+ * @brief Print alignment (spaces) instead of
+ * \<status\>--\<flags\> \<name\> 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) {
+ /* :(<name> */
+ space += strlen(TRD_NODE_NAME_PREFIX_CASE);
+ } else if (node->name.type == TRD_NODE_CHOICE) {
+ /* (<name> */
+ space += strlen(TRD_NODE_NAME_PREFIX_CHOICE);
+ } else {
+ /* _<name> */
+ space += strlen(" ");
+ }
+
+ /* <name>
+ * __
+ */
+ 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 [\<keys\>] and \<iffeatures\>.
+ * @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;
+ }
+
+ /* <status>--<flags> <name><opts> <type> <if-features> */
+ 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);
+ }
+
+ /* <opts> */
+ /* <name>___<opts>*/
+ 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);
+
+ /* <opts>__<type> */
+ if (indent.btw_opts_type > 0) {
+ ly_print_(out, "%*c", indent.btw_opts_type, ' ');
+ }
+
+ /* <type> */
+ trp_print_type(node->type, out);
+
+ /* <type>__<iffeatures> */
+ if (indent.btw_type_iffeatures > 0) {
+ ly_print_(out, "%*c", indent.btw_type_iffeatures, ' ');
+ }
+
+ /* <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 \<node\> 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
+ * \<status\>--\<flags\> \<name\>\<option_mark\>.
+ * @param[in] node is \<node\> 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 \<node\> 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 \<node\>
+ * 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 \<node\>
+ * 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 <opts> 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 \<node\> 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 <flags> 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 \<status\>.
+ * @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
+ * \<flags\> 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 \<status\> 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 \<flags\> 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 \<type\> 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;
+
+ /* <status> */
+ ret.status = trop_resolve_status(pn->nodetype, pn->flags, ca.lys_status);
+
+ /* <flags> */
+ 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;
+
+ /* <type> */
+ ret.type = trop_resolve_type(pn);
+
+ /* <iffeature> */
+ 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 \<node\> representation of the current
+ * node's child. The @p tc is modified.
+ * @return Empty \<node\> 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 \<node\> representation if sibling exists.
+ * The @p tc is modified.
+ * @return Empty \<node\> 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 \<flags\> 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 (\<prefix\>:\<name\>) 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;
+
+ /* <status> */
+ ret.status = tro_flags2status(cn->flags);
+
+ /* <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);
+
+ /* <prefix> */
+ ret.name.module_prefix = troc_resolve_node_prefix(cn, tc->cmod);
+
+ /* set node's name */
+ ret.name.str = cn->name;
+
+ /* <type> */
+ ret.type = trop_resolve_type(TRP_TREE_CTX_GET_LYSP_NODE(cn));
+
+ /* <iffeature> */
+ 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 \<opts\> and \<type\> 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 \<type\>.
+ * @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 \<type\>.
+ *
+ * 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 \<type\>.
+ *
+ * The goal is for all node siblings to have the same alignment
+ * for \<type\> 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 \<type\> 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 <mvasko@cesnet.cz>
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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>%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>%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>%s", INDENT, node->schema->name, DO_FORMAT ? "\n" : "");
+ } else {
+ ly_print_(pctx->out, "</%s>%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>%s", INDENT, node->name.name, DO_FORMAT ? "\n" : "");
+ } else if (node->value[0]) {
+ ly_print_(pctx->out, "</%s>%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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#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, &notif->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, &notif->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, &notif->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 <ganshaolong@vip.qq.com>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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</%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, "</%s>\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, "</%.*s:%s>\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, "%*s<if-feature name=\"%s", INDENT, iffs[u].str);
+
+ /* extensions */
+ LEVEL++;
+ yprp_extension_instances(pctx, LY_STMT_IF_FEATURE, u, exts, &extflag);
+ LEVEL--;
+ ly_print_(pctx->out, "\"/>\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, "%*s<when condition=\"", INDENT);
+ lyxml_dump_text(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, "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, "%*s<bit name=\"", INDENT);
+ lyxml_dump_text(pctx->out, items[u].name, 1);
+ ly_print_(pctx->out, "\"");
+ } else { /* LY_TYPE_ENUM */
+ ly_print_(pctx->out, "%*s<enum name=\"", INDENT);
+ lyxml_dump_text(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_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, &notif->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, &notif->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, "%*s<deviate value=\"", 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, "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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ ly_print_(pctx->out, "%*s<module name=\"%s\"\n", INDENT, module->name);
+ 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</module>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ ly_print_(pctx->out, "%*s<submodule name=\"%s\"\n", INDENT, submodp->name);
+ 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</submodule>\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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 : "<none>", m->revision ? m->revision : "<none>");
+ } else {
+ LOGERR(mod->ctx, LY_EDENIED, "Module \"%s@%s\" is already implemented in revision \"%s\".",
+ mod->name, mod->revision ? mod->revision : "<none>", m->revision ? m->revision : "<none>");
+ 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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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, &not_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>", &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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <inttypes.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <winsock2.h>
+# include <ws2tcpip.h>
+#else
+# include <arpa/inet.h>
+# if defined (__FreeBSD__) || defined (__NetBSD__) || defined (__OpenBSD__)
+# include <netinet/in.h>
+# include <sys/socket.h>
+# endif
+#endif
+#include <stddef.h>
+#include <stdint.h>
+#include <time.h>
+
+#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:
+ * <pre>
+ * 1
+ * / \
+ * 2 4
+ * / / \
+ * 3 5 6
+ * </pre>
+ *
+ * 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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+
+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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <stdlib.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 : "<none>");
+ 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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <pcre2.h>
+
+#include <stdint.h>
+#include <stdio.h>
+
+#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:
+ * <pre>
+ * 1
+ * / \
+ * 2 4
+ * / / \
+ * 3 5 6
+ * </pre>
+ *
+ * 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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <stdlib.h>
+
+#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(&notif->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 <mvasko@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <stdint.h>
+
+#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 <rkrejci@cesnet.cz>
+ * @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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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("&lt;");
+ } else if (!strncmp(&in[offset], "gt;", ly_strlen_const("gt;"))) {
+ buf[len++] = '>';
+ in += ly_strlen_const("&gt;");
+ } else if (!strncmp(&in[offset], "amp;", ly_strlen_const("amp;"))) {
+ buf[len++] = '&';
+ in += ly_strlen_const("&amp;");
+ } else if (!strncmp(&in[offset], "apos;", ly_strlen_const("apos;"))) {
+ buf[len++] = '\'';
+ in += ly_strlen_const("&apos;");
+ } else if (!strncmp(&in[offset], "quot;", ly_strlen_const("quot;"))) {
+ buf[len++] = '\"';
+ in += ly_strlen_const("&quot;");
+ } 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, "<![CDATA[", ly_strlen_const("<![CDATA["))) {
+ /* CDATA, find the end */
+ in_aux = strstr(in + offset + ly_strlen_const("<![CDATA["), "]]>");
+ if (!in_aux) {
+ LOGVAL(xmlctx->ctx, LY_VCODE_NTERM, "CDATA");
+ goto error;
+ }
+ u = in_aux - (in + offset + ly_strlen_const("<![CDATA["));
+
+ /* use buffer, allocate enough for the whole CDATA */
+ LY_CHECK_RET(lyxml_parse_value_use_buf(ctx, &in, &offset, u, &buf, &len, &size));
+
+ /* skip CDATA tag */
+ in += ly_strlen_const("<![CDATA[");
+ assert(!offset);
+
+ /* analyze CDATA for non WS and newline chars */
+ for (n = 0; n < u; ++n) {
+ if (in[n] == '\n') {
+ LY_IN_NEW_LINE(xmlctx->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 "<elem/>" 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 |</elem> */
+
+ /* handle special case when empty content for "<elem/>" 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:
+ /* </elem>| <elem2>* */
+
+ /* 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:
+ /* <elem| attr='val'* > 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:
+ /* </elem> |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, "&amp;");
+ break;
+ case '<':
+ ret = ly_print_(out, "&lt;");
+ break;
+ case '>':
+ /* not needed, just for readability */
+ ret = ly_print_(out, "&gt;");
+ break;
+ case '"':
+ if (attribute) {
+ ret = ly_print_(out, "&quot;");
+ 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 <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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 <mvasko@cesnet.cz>
+ * @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 <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <pos< - 2nd META */
+ /* 2nd is after 1st */
+ return -1;
+}
+
+/**
+ * @brief Set cast for comparisons.
+ *
+ * @param[in,out] trg Target set to cast source into.
+ * @param[in] src Source set.
+ * @param[in] type Target set type.
+ * @param[in] src_idx Source set node index.
+ * @return LY_SUCCESS on success.
+ * @return LY_ERR value on error.
+ */
+static LY_ERR
+set_comp_cast(struct lyxp_set *trg, const struct lyxp_set *src, enum lyxp_set_type type, uint32_t src_idx)
+{
+ assert(src->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 <running>, 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("<root>");
+ } else if (scparent->type == LYXP_NODE_ROOT_CONFIG) {
+ ppath = strdup("<config-root>");
+ }
+ }
+ 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 <mvasko@cesnet.cz>
+ * @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 <stddef.h>
+#include <stdint.h>
+
+#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, /* <running> 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 */