lyb UPDATE support for nested ext data

Needed LYB format change.
diff --git a/src/lyb.h b/src/lyb.h
index 165dfe5..67f76c7 100644
--- a/src/lyb.h
+++ b/src/lyb.h
@@ -51,7 +51,7 @@
  sb          = siblings_start
  se          = siblings_end
  siblings    = zero-LYB_SIZE_BYTES | (sb instance+ se)
- instance    = model hash node
+ 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
@@ -66,6 +66,16 @@
  */
 
 /**
+ * @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 {
@@ -101,7 +111,7 @@
 #define LYB_SIBLING_STEP 4
 
 /* current LYB format version */
-#define LYB_VERSION_NUM 0x03
+#define LYB_VERSION_NUM 0x04
 
 /* LYB format version mask of the header byte */
 #define LYB_VERSION_MASK 0x0F
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
index 43eafc1..25295ef 100644
--- a/src/parser_lyb.c
+++ b/src/parser_lyb.c
@@ -30,6 +30,7 @@
 #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"
@@ -331,67 +332,86 @@
 }
 
 /**
- * @brief Parse YANG model info.
+ * @brief Read YANG model info.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parse_options Flag with options for parsing.
- * @param[out] model Parsed module.
+ * @param[out] mod_name Module name, if any.
+ * @param[out] mod_rev Module revision, "" if none.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyb_parse_model(struct lylyb_ctx *lybctx, uint32_t parse_options, const struct lys_module **model)
+lyb_read_model(struct lylyb_ctx *lybctx, char **mod_name, char mod_rev[])
 {
-    LY_ERR ret = LY_SUCCESS;
-    const struct lys_module *mod = NULL;
-    char *mod_name = NULL, mod_rev[LY_REV_SIZE];
     uint16_t rev, length;
 
-    lyb_read_number(&length, 2, 2, lybctx);
+    *mod_name = NULL;
+    mod_rev[0] = '\0';
 
-    if (length) {
-        mod_name = malloc((length + 1) * sizeof *mod_name);
-        LY_CHECK_ERR_RET(!mod_name, LOGMEM(lybctx->ctx), LY_EMEM);
-        lyb_read(((uint8_t *)mod_name), length, lybctx);
-        mod_name[length] = '\0';
-    } else {
-        goto cleanup;
+    lyb_read_number(&length, 2, 2, lybctx);
+    if (!length) {
+        return LY_SUCCESS;
     }
 
-    /* revision */
+    /* module name */
+    *mod_name = malloc((length + 1) * sizeof *mod_name);
+    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);
-        mod = ly_ctx_get_module(lybctx->ctx, mod_name, mod_rev);
-        if ((parse_options & LYD_PARSE_LYB_MOD_UPDATE) && !mod) {
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Parse YANG model info.
+ *
+ * @param[in] lybctx LYB context.
+ * @param[in] parse_options Flag with options for parsing.
+ * @param[out] mod Parsed module.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyb_parse_model(struct lylyb_ctx *lybctx, uint32_t parse_options, 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];
+
+    /* read module info */
+    if ((ret = lyb_read_model(lybctx, &mod_name, mod_rev))) {
+        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 */
-            mod = ly_ctx_get_module_implemented(lybctx->ctx, mod_name);
-            if (mod && (!mod->revision || (strcmp(mod->revision, mod_rev) < 0))) {
+            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 */
-                mod = NULL;
+                m = NULL;
             }
         }
     } else {
-        mod = ly_ctx_get_module_latest(lybctx->ctx, mod_name);
+        m = ly_ctx_get_module_latest(lybctx->ctx, mod_name);
     }
-    /* TODO data_clb supported?
-    if (lybctx->ctx->data_clb) {
-        if (!*mod) {
-            *mod = lybctx->ctx->data_clb(lybctx->ctx, mod_name, NULL, 0, lybctx->ctx->data_clb_data);
-        } else if (!(*mod)->implemented) {
-            *mod = lybctx->ctx->data_clb(lybctx->ctx, mod_name, (*mod)->ns, LY_MODCLB_NOT_IMPLEMENTED, lybctx->ctx->data_clb_data);
-        }
-    }*/
 
-    if (!mod || !mod->implemented) {
+    if (!m || !m->implemented) {
         if (parse_options & LYD_PARSE_STRICT) {
-            if (!mod) {
+            if (!m) {
                 LOGERR(lybctx->ctx, LY_EINVAL, "Invalid context for LYB data parsing, missing module \"%s%s%s\".",
-                        mod_name, rev ? "@" : "", rev ? mod_rev : "");
-            } else if (!mod->implemented) {
+                        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, rev ? "@" : "", rev ? mod_rev : "");
+                        mod_name, mod_rev[0] ? "@" : "", mod_rev[0] ? mod_rev : "");
             }
             ret = LY_EINVAL;
             goto cleanup;
@@ -399,13 +419,13 @@
 
     }
 
-    if (mod) {
+    if (m) {
         /* fill cached hashes, if not already */
-        lyb_cache_module_hash(mod);
+        lyb_cache_module_hash(m);
     }
 
 cleanup:
-    *model = mod;
+    *mod = m;
     free(mod_name);
     return ret;
 }
@@ -748,7 +768,7 @@
             break;
         }
         /* skip schema nodes from models not present during printing */
-        if (lyb_has_schema_model(sibling, lybctx->lybctx->models) &&
+        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;
@@ -776,6 +796,50 @@
 }
 
 /**
+ * @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.
@@ -849,7 +913,11 @@
         struct ly_set *parsed)
 {
     /* insert, keep first pointer correct */
-    lyd_insert_node(parent, first_p, node, lybctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0);
+    if (parent && (LYD_CTX(parent) != LYD_CTX(node))) {
+        lyd_insert_ext(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;
     }
@@ -1363,12 +1431,12 @@
 }
 
 /**
- * @brief Parse node.
+ * @brief Parse a 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.
+ * @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
@@ -1378,19 +1446,33 @@
     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];
 
-    if (!parent || !parent->schema) {
-        /* top-level or opaque, read module name */
-        ret = lyb_parse_model(lybctx->lybctx, lybctx->parse_opts, &mod);
-        LY_CHECK_RET(ret);
+    /* 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, &mod), cleanup);
 
         /* read hash, find the schema node starting from mod */
-        ret = lyb_parse_schema_hash(lybctx, NULL, mod, &snode);
-    } else {
-        /* read hash, find the schema node starting from parent schema */
-        ret = lyb_parse_schema_hash(lybctx, parent->schema, NULL, &snode);
+        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), 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;
     }
-    LY_CHECK_RET(ret);
 
     if (!snode) {
         ret = lyb_parse_node_opaq(lybctx, parent, first_p, parsed);
@@ -1405,8 +1487,10 @@
     } else {
         ret = lyb_parse_node_leaf(lybctx, parent, snode, first_p, parsed);
     }
-    LY_CHECK_RET(ret);
+    LY_CHECK_GOTO(ret, cleanup);
 
+cleanup:
+    free(mod_name);
     return ret;
 }
 
@@ -1423,18 +1507,15 @@
 lyb_parse_siblings(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p,
         struct ly_set *parsed)
 {
-    LY_ERR ret;
     ly_bool top_level;
 
     top_level = !LY_ARRAY_COUNT(lybctx->lybctx->siblings);
 
     /* register a new siblings */
-    ret = lyb_read_start_siblings(lybctx->lybctx);
-    LY_CHECK_RET(ret);
+    LY_CHECK_RET(lyb_read_start_siblings(lybctx->lybctx));
 
     while (LYB_LAST_SIBLING(lybctx->lybctx).written) {
-        ret = lyb_parse_node(lybctx, parent, first_p, parsed);
-        LY_CHECK_RET(ret);
+        LY_CHECK_RET(lyb_parse_node(lybctx, parent, first_p, parsed));
 
         if (top_level && !(lybctx->int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
             break;
@@ -1442,10 +1523,9 @@
     }
 
     /* end the siblings */
-    ret = lyb_read_stop_siblings(lybctx->lybctx);
-    LY_CHECK_RET(ret);
+    LY_CHECK_RET(lyb_read_stop_siblings(lybctx->lybctx));
 
-    return ret;
+    return LY_SUCCESS;
 }
 
 /**
diff --git a/src/printer_lyb.c b/src/printer_lyb.c
index b330e4c..9364826 100644
--- a/src/printer_lyb.c
+++ b/src/printer_lyb.c
@@ -397,7 +397,7 @@
  *
  * @param[in] str String to write.
  * @param[in] str_len Length of @p str.
- * @param[in] len_size Size of @ str_len in bytes.
+ * @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.
@@ -456,20 +456,16 @@
 lyb_print_model(struct ly_out *out, const struct lys_module *mod, struct lylyb_ctx *lybctx)
 {
     uint16_t revision;
+    int r;
 
     /* model name length and model name */
-    if (mod) {
-        LY_CHECK_RET(lyb_write_string(mod->name, 0, sizeof(uint16_t), out, lybctx));
-    } else {
-        LY_CHECK_RET(lyb_write_number(0, 2, out, lybctx));
-        return LY_SUCCESS;
-    }
+    LY_CHECK_RET(lyb_write_string(mod->name, 0, sizeof(uint16_t), out, lybctx));
 
     /* model revision as XXXX XXXX XXXX XXXX (2B) (year is offset from 2000)
      *                   YYYY YYYM MMMD DDDD */
     revision = 0;
-    if (mod && mod->revision) {
-        int r = atoi(mod->revision);
+    if (mod->revision) {
+        r = atoi(mod->revision);
         r -= LYB_REV_YEAR_OFFSET;
         r <<= LYB_REV_YEAR_SHIFT;
 
@@ -486,10 +482,8 @@
     }
     LY_CHECK_RET(lyb_write_number(revision, sizeof revision, out, lybctx));
 
-    if (mod) {
-        /* fill cached hashes, if not already */
-        lyb_cache_module_hash(mod);
-    }
+    /* fill cached hashes, if not already */
+    lyb_cache_module_hash(mod);
 
     return LY_SUCCESS;
 }
@@ -923,6 +917,35 @@
 }
 
 /**
+ * @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.
@@ -1156,15 +1179,21 @@
 {
     const struct lyd_node *node = *printed_node;
 
-    /* write model info first, for all opaque and top-level nodes */
-    if (!node->schema && (!node->parent || !node->parent->schema)) {
-        LY_CHECK_RET(lyb_print_model(out, NULL, lybctx->lybctx));
-    } else if (node->schema && !lysc_data_parent(node->schema)) {
+    /* 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, lybctx->lybctx));
     }
 
-    /* write schema hash */
-    LY_CHECK_RET(lyb_print_schema_hash(out, (struct lysc_node *)node->schema, sibling_ht, 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));
diff --git a/tests/utests/extensions/test_schema_mount.c b/tests/utests/extensions/test_schema_mount.c
index 8b62281..29117d5 100644
--- a/tests/utests/extensions/test_schema_mount.c
+++ b/tests/utests/extensions/test_schema_mount.c
@@ -462,6 +462,7 @@
 test_parse_inline(void **state)
 {
     const char *xml, *json;
+    char *lyb;
     struct lyd_node *data;
 
     /* valid */
@@ -649,6 +650,12 @@
 
     CHECK_PARSE_LYD_PARAM(json, LYD_JSON, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, data);
     CHECK_LYD_STRING_PARAM(data, json, LYD_JSON, LYD_PRINT_WITHSIBLINGS);
+
+    assert_int_equal(LY_SUCCESS, lyd_print_mem(&lyb, data, LYD_LYB, 0));
+    lyd_free_siblings(data);
+
+    CHECK_PARSE_LYD_PARAM(lyb, LYD_LYB, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, data);
+    free(lyb);
     lyd_free_siblings(data);
 }
 
@@ -656,6 +663,7 @@
 test_parse_shared(void **state)
 {
     const char *xml, *json;
+    char *lyb;
     struct lyd_node *data;
 
     ly_ctx_set_ext_data_clb(UTEST_LYCTX, test_ext_data_clb,
@@ -1011,6 +1019,12 @@
             "}\n";
     CHECK_PARSE_LYD_PARAM(json, LYD_JSON, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, data);
     CHECK_LYD_STRING_PARAM(data, json, LYD_JSON, LYD_PRINT_WITHSIBLINGS);
+
+    assert_int_equal(LY_SUCCESS, lyd_print_mem(&lyb, data, LYD_LYB, LYD_PRINT_WITHSIBLINGS));
+    lyd_free_siblings(data);
+
+    CHECK_PARSE_LYD_PARAM(lyb, LYD_LYB, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, data);
+    free(lyb);
     lyd_free_siblings(data);
 }