lyb FEATURE subtree meatadata change to sibling

Metadata now delimits siblings instead of subtrees. This change also
provides new possibilities for optimizations. For example it is now
possible to have a optimal LYB format for some type of node. The change
is that the node hash is written as soon as possible and the subtree
metadata optionally follows afterwards.
diff --git a/src/lyb.h b/src/lyb.h
index 5c9077d..42c5d1e 100644
--- a/src/lyb.h
+++ b/src/lyb.h
@@ -33,12 +33,12 @@
 
     const struct lys_module **models;
 
-    struct lyd_lyb_subtree {
+    struct lyd_lyb_sibling {
         size_t written;
         size_t position;
         uint8_t inner_chunks;
-    } *subtrees;
-    LY_ARRAY_COUNT_TYPE subtree_size;
+    } *siblings;
+    LY_ARRAY_COUNT_TYPE sibling_size;
 
     /* LYB printer only */
     struct lyd_lyb_sib_ht {
@@ -62,12 +62,12 @@
  * 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 subtrees. Each subtree begins
- * with its metadata, which consist of 1) the whole subtree length in bytes and 2) number
- * of included metadata chunks of nested subtrees.
+ * - 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 subtree is not known before it is printed, holes are first written and
- * after the subtree is printed, they are filled with actual valid metadata. As a consequence,
+ * - 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
@@ -76,10 +76,10 @@
  */
 
 /* just a shortcut */
-#define LYB_LAST_SUBTREE(lybctx) lybctx->subtrees[LY_ARRAY_COUNT(lybctx->subtrees) - 1]
+#define LYB_LAST_SIBLING(lybctx) lybctx->siblings[LY_ARRAY_COUNT(lybctx->siblings) - 1]
 
-/* struct lyd_lyb_subtree allocation step */
-#define LYB_SUBTREE_STEP 4
+/* struct lyd_lyb_sibling allocation step */
+#define LYB_SIBLING_STEP 4
 
 /* current LYB format version */
 #define LYB_VERSION_NUM 0x10
@@ -143,9 +143,6 @@
 #define LYB_REV_MONTH_SHIFT 5
 #define LYB_REV_DAY_MASK    0x001fU
 
-/* Type large enough for all meta data */
-#define LYB_META uint16_t
-
 /**
  * @brief Get single hash for a schema node to be used for LYB data. Read from cache, if possible.
  *
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
index d8c0ed6..81b640a 100644
--- a/src/parser_lyb.c
+++ b/src/parser_lyb.c
@@ -43,15 +43,16 @@
         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, struct lyd_ctx **lydctx_p);
 
-static LY_ERR lyb_parse_subtree_r(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed);
+static LY_ERR lyb_parse_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node *snode, struct lyd_node **first_p, struct ly_set *parsed);
 static LY_ERR lyb_parse_node_header(struct lyd_lyb_ctx *lybctx, uint32_t *flags, struct lyd_meta **meta);
+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->subtrees);
+    LY_ARRAY_FREE(ctx->siblings);
     LY_ARRAY_FREE(ctx->models);
 
     LY_ARRAY_FOR(ctx->sib_hts, u) {
@@ -83,7 +84,7 @@
 lyb_read(uint8_t *buf, size_t count, struct lylyb_ctx *lybctx)
 {
     LY_ARRAY_COUNT_TYPE u;
-    struct lyd_lyb_subtree *empty;
+    struct lyd_lyb_sibling *empty;
     size_t to_read;
     uint8_t meta_buf[LYB_META_BYTES];
 
@@ -93,13 +94,13 @@
         /* check for fully-read (empty) data chunks */
         to_read = count;
         empty = NULL;
-        LY_ARRAY_FOR(lybctx->subtrees, u) {
+        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->subtrees[u].written <= to_read) && lybctx->subtrees[u].position) {
+            if ((lybctx->siblings[u].written <= to_read) && lybctx->siblings[u].position) {
                 /* empty chunk, do not read more */
-                to_read = lybctx->subtrees[u].written;
-                empty = &lybctx->subtrees[u];
+                to_read = lybctx->siblings[u].written;
+                empty = &lybctx->siblings[u];
             }
         }
 
@@ -115,10 +116,10 @@
                 ly_in_skip(lybctx->in, to_read);
             }
 
-            LY_ARRAY_FOR(lybctx->subtrees, u) {
+            LY_ARRAY_FOR(lybctx->siblings, u) {
                 /* decrease all written counters */
-                lybctx->subtrees[u].written -= to_read;
-                assert(lybctx->subtrees[u].written <= LYB_SIZE_MAX);
+                lybctx->siblings[u].written -= to_read;
+                assert(lybctx->siblings[u].written <= LYB_SIZE_MAX);
             }
             /* decrease count/buf */
             count -= to_read;
@@ -179,7 +180,7 @@
  * @brief Read a string.
  *
  * @param[in] str Destination buffer, is allocated.
- * @param[in] with_length Whether the string is preceded with its length or it ends at the end of this subtree.
+ * @param[in] with_length Whether the string is preceded with its length or it ends at the end of this "sibling".
  * @param[in] lybctx LYB context.
  * @return LY_ERR value.
  */
@@ -194,9 +195,9 @@
     if (with_length) {
         lyb_read_number(&len, sizeof len, 2, lybctx);
     } else {
-        /* read until the end of this subtree */
-        len = LYB_LAST_SUBTREE(lybctx).written;
-        if (LYB_LAST_SUBTREE(lybctx).position) {
+        /* read until the end of this "sibling" */
+        len = LYB_LAST_SIBLING(lybctx).written;
+        if (LYB_LAST_SIBLING(lybctx).position) {
             next_chunk = 1;
         }
     }
@@ -207,8 +208,8 @@
     lyb_read((uint8_t *)*str, len, lybctx);
 
     while (next_chunk) {
-        cur_len = LYB_LAST_SUBTREE(lybctx).written;
-        if (LYB_LAST_SUBTREE(lybctx).position) {
+        cur_len = LYB_LAST_SIBLING(lybctx).written;
+        if (LYB_LAST_SIBLING(lybctx).position) {
             next_chunk = 1;
         } else {
             next_chunk = 0;
@@ -282,46 +283,46 @@
 }
 
 /**
- * @brief Stop the current subtree - change LYB context state.
+ * @brief Stop the current "siblings" - change LYB context state.
  *
  * @param[in] lybctx LYB context.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyb_read_stop_subtree(struct lylyb_ctx *lybctx)
+lyb_read_stop_siblings(struct lylyb_ctx *lybctx)
 {
-    if (LYB_LAST_SUBTREE(lybctx).written) {
+    if (LYB_LAST_SIBLING(lybctx).written) {
         LOGINT_RET(lybctx->ctx);
     }
 
-    LY_ARRAY_DECREMENT(lybctx->subtrees);
+    LY_ARRAY_DECREMENT(lybctx->siblings);
     return LY_SUCCESS;
 }
 
 /**
- * @brief Start a new subtree - change LYB context state but also read the expected metadata.
+ * @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_subtree(struct lylyb_ctx *lybctx)
+lyb_read_start_siblings(struct lylyb_ctx *lybctx)
 {
     uint8_t meta_buf[LYB_META_BYTES];
     LY_ARRAY_COUNT_TYPE u;
 
-    u = LY_ARRAY_COUNT(lybctx->subtrees);
-    if (u == lybctx->subtree_size) {
-        LY_ARRAY_CREATE_RET(lybctx->ctx, lybctx->subtrees, u + LYB_SUBTREE_STEP, LY_EMEM);
-        lybctx->subtree_size = u + LYB_SUBTREE_STEP;
+    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_CHECK_RET(ly_in_read(lybctx->in, meta_buf, LYB_META_BYTES));
 
-    LY_ARRAY_INCREMENT(lybctx->subtrees);
-    LYB_LAST_SUBTREE(lybctx).written = meta_buf[0];
-    LYB_LAST_SUBTREE(lybctx).inner_chunks = meta_buf[LYB_SIZE_BYTES];
-    LYB_LAST_SUBTREE(lybctx).position = (LYB_LAST_SUBTREE(lybctx).written == LYB_SIZE_MAX ? 1 : 0);
+    LY_ARRAY_INCREMENT(lybctx->siblings);
+    LYB_LAST_SIBLING(lybctx).written = meta_buf[0];
+    LYB_LAST_SIBLING(lybctx).inner_chunks = meta_buf[LYB_SIZE_BYTES];
+    LYB_LAST_SIBLING(lybctx).position = (LYB_LAST_SIBLING(lybctx).written == LYB_SIZE_MAX ? 1 : 0);
 
     return LY_SUCCESS;
 }
@@ -338,24 +339,24 @@
 lyb_parse_model(struct lylyb_ctx *lybctx, uint32_t parse_options, const struct lys_module **model)
 {
     LY_ERR ret = LY_SUCCESS;
-    const struct lys_module *mod;
+    const struct lys_module *mod = NULL;
     char *mod_name = NULL, mod_rev[LY_REV_SIZE];
-    uint16_t rev;
+    uint16_t rev, length;
 
-    *model = NULL;
+    lyb_read_number(&length, 2, 2, lybctx);
 
-    /* model name */
-    ret = lyb_read_string(&mod_name, 1, lybctx);
-    LY_CHECK_GOTO(ret, cleanup);
+    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;
+    }
 
     /* revision */
     lyb_read_number(&rev, sizeof rev, 2, lybctx);
 
-    if (!mod_name[0]) {
-        /* opaq node, no module */
-        goto cleanup;
-    }
-
     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);
@@ -400,9 +401,8 @@
         lyb_cache_module_hash(mod);
     }
 
-    *model = mod;
-
 cleanup:
+    *model = mod;
     free(mod_name);
     return ret;
 }
@@ -428,7 +428,7 @@
 
     /* read attributes */
     for (i = 0; i < count; ++i) {
-        ret = lyb_read_start_subtree(lybctx->lybctx);
+        ret = lyb_read_start_siblings(lybctx->lybctx);
         LY_CHECK_GOTO(ret, cleanup);
 
         /* find model */
@@ -438,9 +438,9 @@
         if (!mod) {
             /* skip it */
             do {
-                lyb_read(NULL, LYB_LAST_SUBTREE(lybctx->lybctx).written, lybctx->lybctx);
-            } while (LYB_LAST_SUBTREE(lybctx->lybctx).written);
-            goto stop_subtree;
+                lyb_read(NULL, LYB_LAST_SIBLING(lybctx->lybctx).written, lybctx->lybctx);
+            } while (LYB_LAST_SIBLING(lybctx->lybctx).written);
+            goto stop_sibling;
         }
 
         /* meta name */
@@ -466,8 +466,8 @@
 
         LY_CHECK_GOTO(ret, cleanup);
 
-stop_subtree:
-        ret = lyb_read_stop_subtree(lybctx->lybctx);
+stop_sibling:
+        ret = lyb_read_stop_siblings(lybctx->lybctx);
         LY_CHECK_GOTO(ret, cleanup);
     }
 
@@ -567,7 +567,7 @@
 
     /* read attributes */
     for (i = 0; i < count; ++i) {
-        ret = lyb_read_start_subtree(lybctx);
+        ret = lyb_read_start_siblings(lybctx);
         LY_CHECK_GOTO(ret, cleanup);
 
         /* prefix, may be empty */
@@ -620,7 +620,7 @@
             *attr = attr2;
         }
 
-        ret = lyb_read_stop_subtree(lybctx);
+        ret = lyb_read_stop_siblings(lybctx);
         LY_CHECK_GOTO(ret, cleanup);
     }
 
@@ -729,6 +729,8 @@
     uint32_t getnext_opts;
     uint8_t hash_count;
 
+    *snode = NULL;
+
     ret = lyb_read_hashes(lybctx->lybctx, hash, &hash_count);
     LY_CHECK_RET(ret);
 
@@ -737,7 +739,6 @@
         return LY_SUCCESS;
     }
 
-    *snode = NULL;
     getnext_opts = lybctx->int_opts & LYD_INTOPT_REPLY ? LYS_GETNEXT_OUTPUT : 0;
 
     /* find our node with matching hashes */
@@ -780,20 +781,20 @@
 }
 
 /**
- * @brief Read until the end of the current subtree.
+ * @brief Read until the end of the current siblings.
  *
  * @param[in] lybctx LYB context.
  */
 static void
-lyb_skip_subtree(struct lylyb_ctx *lybctx)
+lyb_skip_siblings(struct lylyb_ctx *lybctx)
 {
     do {
         /* first skip any meta information inside */
-        ly_in_skip(lybctx->in, LYB_LAST_SUBTREE(lybctx).inner_chunks * LYB_META_BYTES);
+        ly_in_skip(lybctx->in, LYB_LAST_SIBLING(lybctx).inner_chunks * LYB_META_BYTES);
 
         /* then read data */
-        lyb_read(NULL, LYB_LAST_SUBTREE(lybctx).written, lybctx);
-    } while (LYB_LAST_SUBTREE(lybctx).written);
+        lyb_read(NULL, LYB_LAST_SIBLING(lybctx).written, lybctx);
+    } while (LYB_LAST_SIBLING(lybctx).written);
 }
 
 /**
@@ -842,7 +843,7 @@
  * Also if needed, correct @p first_p.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree, must be set if @p first_p is not.
+ * @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.
@@ -868,7 +869,7 @@
  * @brief Finish parsing the opaq node.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree, must be set if @p first_p is not.
+ * @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.
@@ -901,7 +902,7 @@
  * @brief Finish parsing the node.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree, must be set if @p first_p is not.
+ * @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.
@@ -998,7 +999,7 @@
  * @brief Parse opaq node.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree.
+ * @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.
@@ -1016,12 +1017,6 @@
     const struct ly_ctx *ctx = lybctx->lybctx->ctx;
     uint32_t flags;
 
-    if (!(lybctx->parse_opts & LYD_PARSE_OPAQ)) {
-        /* unknown data, skip them */
-        lyb_skip_subtree(lybctx->lybctx);
-        return LY_SUCCESS;
-    }
-
     /* parse opaq node attributes */
     ret = lyb_parse_attributes(lybctx->lybctx, &attr);
     LY_CHECK_GOTO(ret, cleanup);
@@ -1053,16 +1048,30 @@
     ret = lyb_parse_prefix_data(lybctx->lybctx, format, &val_prefix_data);
     LY_CHECK_GOTO(ret, cleanup);
 
+    if (!(lybctx->parse_opts & LYD_PARSE_OPAQ)) {
+        if (lybctx->lybctx->in->current[0] == 0) {
+            /* opaq node has no children */
+            lyb_read(NULL, 1, lybctx->lybctx);
+        } else {
+            /* skip children */
+            ret = lyb_read_start_siblings(lybctx->lybctx);
+            LY_CHECK_RET(ret);
+            lyb_skip_siblings(lybctx->lybctx);
+            ret = lyb_read_stop_siblings(lybctx->lybctx);
+            LY_CHECK_RET(ret);
+        }
+
+        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 */
-    while (LYB_LAST_SUBTREE(lybctx->lybctx).written) {
-        ret = lyb_parse_subtree_r(lybctx, node, NULL, NULL);
-        LY_CHECK_GOTO(ret, cleanup);
-    }
+    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);
@@ -1085,7 +1094,7 @@
  * @brief Parse anydata or anyxml node.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree.
+ * @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.
@@ -1105,8 +1114,7 @@
     const struct ly_ctx *ctx = lybctx->lybctx->ctx;
 
     /* read necessary basic data */
-    ret = lyb_parse_node_header(lybctx, &flags, &meta);
-    LY_CHECK_GOTO(ret, error);
+    lyb_parse_node_header(lybctx, &flags, &meta);
 
     /* parse value type */
     lyb_read_number(&value_type, sizeof value_type, sizeof value_type, lybctx->lybctx);
@@ -1118,7 +1126,7 @@
     }
 
     /* read anydata content */
-    ret = lyb_read_string(&value, 0, lybctx->lybctx);
+    ret = lyb_read_string(&value, 1, lybctx->lybctx);
     LY_CHECK_GOTO(ret, error);
 
     if (value_type == LYD_ANYDATA_LYB) {
@@ -1196,66 +1204,30 @@
 }
 
 /**
- * @brief Parse model and hash.
+ * @brief Parse LYB node with children.
  *
  * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree.
- * @param[out] snode Schema of the node to be further parsed. Can be NULL for the opaq node.
- * @return LY_ERR value.
- */
-static LY_ERR
-lyb_print_model_and_hash(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const struct lysc_node **snode)
-{
-    LY_ERR ret;
-    const struct lys_module *mod;
-
-    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 hash, find the schema node starting from mod */
-        ret = lyb_parse_schema_hash(lybctx, NULL, mod, snode);
-        LY_CHECK_RET(ret);
-    } else {
-        /* read hash, find the schema node starting from parent schema */
-        ret = lyb_parse_schema_hash(lybctx, parent->schema, NULL, snode);
-        LY_CHECK_RET(ret);
-    }
-
-    return ret;
-}
-
-/**
- * @brief Parse LYB subtree.
- *
- * @param[in] lybctx LYB context.
- * @param[in] parent Data parent of the subtree, must be set if @p first is not.
+ * @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_subtree_r(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed)
+lyb_parse_node(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;
-    const struct lysc_node *snode = NULL;
     struct lyd_meta *meta = NULL;
     uint32_t flags;
     const struct ly_ctx *ctx = lybctx->lybctx->ctx;
 
-    /* register a new subtree */
-    LY_CHECK_GOTO(ret = lyb_read_start_subtree(lybctx->lybctx), cleanup);
-
-    ret = lyb_print_model_and_hash(lybctx, parent, &snode);
-    LY_CHECK_RET(ret);
-
     if (!snode) {
         ret = lyb_parse_node_opaq(lybctx, parent, first_p, parsed);
         LY_CHECK_GOTO(ret, cleanup);
-        goto stop_subtree;
     } else if (snode->nodetype & LYD_NODE_TERM) {
+        /* read necessary basic data */
         ret = lyb_parse_node_header(lybctx, &flags, &meta);
         LY_CHECK_GOTO(ret, cleanup);
 
@@ -1263,9 +1235,9 @@
         ret = lyb_create_term(lybctx, snode, &node);
         LY_CHECK_GOTO(ret, cleanup);
 
-        /* complete the node processing */
         lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed);
     } else if (snode->nodetype & LYD_NODE_INNER) {
+        /* read necessary basic data */
         ret = lyb_parse_node_header(lybctx, &flags, &meta);
         LY_CHECK_GOTO(ret, cleanup);
 
@@ -1274,10 +1246,8 @@
         LY_CHECK_GOTO(ret, cleanup);
 
         /* process children */
-        while (LYB_LAST_SUBTREE(lybctx->lybctx).written) {
-            ret = lyb_parse_subtree_r(lybctx, node, NULL, NULL);
-            LY_CHECK_GOTO(ret, cleanup);
-        }
+        ret = lyb_parse_siblings(lybctx, node, NULL, NULL);
+        LY_CHECK_GOTO(ret, cleanup);
 
         /* additional procedure for inner node */
         ret = lyb_validate_node_inner(lybctx, snode, node);
@@ -1288,23 +1258,17 @@
             lybctx->op_node = node;
         }
 
-        /* complete the node processing */
+        /* register parsed node */
         lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed);
     } else if (snode->nodetype & LYD_NODE_ANY) {
         ret = lyb_parse_node_any(lybctx, parent, snode, first_p, parsed);
         LY_CHECK_GOTO(ret, cleanup);
-        goto stop_subtree;
     } else {
         LOGINT(ctx);
         ret = LY_EINT;
         goto cleanup;
     }
 
-stop_subtree:
-    /* end the subtree */
-    ret = lyb_read_stop_subtree(lybctx->lybctx);
-    LY_CHECK_GOTO(ret, cleanup);
-
 cleanup:
     lyd_free_meta_siblings(meta);
     lyd_free_tree(node);
@@ -1312,6 +1276,64 @@
 }
 
 /**
+ * @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_ERR ret;
+    const struct lysc_node *snode;
+    const struct lys_module *mod;
+    ly_bool top_level;
+
+    if (lybctx->lybctx->in->current[0] == 0) {
+        lyb_read(NULL, 1, lybctx->lybctx);
+        return LY_SUCCESS;
+    }
+
+    top_level = !LY_ARRAY_COUNT(lybctx->lybctx->siblings);
+
+    /* register a new siblings */
+    ret = lyb_read_start_siblings(lybctx->lybctx);
+    LY_CHECK_RET(ret);
+
+    while (LYB_LAST_SIBLING(lybctx->lybctx).written) {
+        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 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_RET(ret);
+
+        lyb_parse_node(lybctx, parent, snode, first_p, parsed);
+        LY_CHECK_RET(ret);
+
+        if (top_level && !(lybctx->int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
+            break;
+        }
+    }
+
+    /* end the siblings */
+    ret = lyb_read_stop_siblings(lybctx->lybctx);
+    LY_CHECK_RET(ret);
+
+    return ret;
+}
+
+/**
  * @brief Parse used YANG data models.
  *
  * @param[in] lybctx LYB context.
@@ -1436,15 +1458,9 @@
     rc = lyb_parse_data_models(lybctx->lybctx, lybctx->parse_opts);
     LY_CHECK_GOTO(rc, cleanup);
 
-    /* read subtree(s) */
-    while (lybctx->lybctx->in->current[0]) {
-        rc = lyb_parse_subtree_r(lybctx, parent, first_p, parsed);
-        LY_CHECK_GOTO(rc, cleanup);
-
-        if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) {
-            break;
-        }
-    }
+    /* 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.");
@@ -1546,17 +1562,19 @@
         lyb_read(buf, 2, lybctx);
     }
 
-    while (lybctx->in->current[0]) {
-        /* register a new subtree */
-        ret = lyb_read_start_subtree(lybctx);
+    if (lybctx->in->current[0]) {
+        /* register a new sibling */
+        ret = lyb_read_start_siblings(lybctx);
         LY_CHECK_GOTO(ret, cleanup);
 
         /* skip it */
-        lyb_skip_subtree(lybctx);
+        lyb_skip_siblings(lybctx);
 
-        /* subtree finished */
-        ret = lyb_read_stop_subtree(lybctx);
+        /* sibling finished */
+        ret = lyb_read_stop_siblings(lybctx);
         LY_CHECK_GOTO(ret, cleanup);
+    } else {
+        lyb_read(NULL, 1, lybctx);
     }
 
     /* read the last zero, parsing finished */
diff --git a/src/printer_lyb.c b/src/printer_lyb.c
index 72e0700..0b6a48a 100644
--- a/src/printer_lyb.c
+++ b/src/printer_lyb.c
@@ -40,7 +40,7 @@
 
 static LY_ERR lyb_print_schema_hash(struct ly_out *out, struct lysc_node *schema, struct hash_table **sibling_ht, struct lylyb_ctx *lybctx);
 static LY_ERR lyb_print_attributes(struct ly_out *out, const struct lyd_node_opaq *node, struct lylyb_ctx *lybctx);
-static LY_ERR lyb_print_subtree(struct ly_out *out, const struct lyd_node *node, struct hash_table **sibling_ht, struct lyd_lyb_ctx *lybctx);
+static LY_ERR lyb_print_siblings(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx);
 static LY_ERR lyb_print_node_header(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx);
 
 /**
@@ -242,7 +242,7 @@
 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_subtree *full, *iter;
+    struct lyd_lyb_sibling *full, *iter;
     size_t to_write;
     uint8_t meta_buf[LYB_META_BYTES];
 
@@ -250,12 +250,12 @@
         /* check for full data chunks */
         to_write = count;
         full = NULL;
-        LY_ARRAY_FOR(lybctx->subtrees, u) {
+        LY_ARRAY_FOR(lybctx->siblings, u) {
             /* we want the innermost chunks resolved first, so replace previous full chunks */
-            if (lybctx->subtrees[u].written + to_write >= LYB_SIZE_MAX) {
+            if (lybctx->siblings[u].written + to_write >= LYB_SIZE_MAX) {
                 /* full chunk, do not write more than allowed */
-                to_write = LYB_SIZE_MAX - lybctx->subtrees[u].written;
-                full = &lybctx->subtrees[u];
+                to_write = LYB_SIZE_MAX - lybctx->siblings[u].written;
+                full = &lybctx->siblings[u];
             }
         }
 
@@ -267,10 +267,10 @@
         if (to_write) {
             LY_CHECK_RET(ly_write_(out, (char *)buf, to_write));
 
-            LY_ARRAY_FOR(lybctx->subtrees, u) {
+            LY_ARRAY_FOR(lybctx->siblings, u) {
                 /* increase all written counters */
-                lybctx->subtrees[u].written += to_write;
-                assert(lybctx->subtrees[u].written <= LYB_SIZE_MAX);
+                lybctx->siblings[u].written += to_write;
+                assert(lybctx->siblings[u].written <= LYB_SIZE_MAX);
             }
             /* decrease count/buf */
             count -= to_write;
@@ -291,7 +291,7 @@
             LY_CHECK_RET(ly_write_skip(out, LYB_META_BYTES, &full->position));
 
             /* increase inner chunk count */
-            for (iter = &lybctx->subtrees[0]; iter != full; ++iter) {
+            for (iter = &lybctx->siblings[0]; iter != full; ++iter) {
                 if (iter->inner_chunks == LYB_INCHUNK_MAX) {
                     LOGINT(lybctx->ctx);
                     return LY_EINT;
@@ -305,58 +305,58 @@
 }
 
 /**
- * @brief Stop the current subtree - write its final metadata.
+ * @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_subtree(struct ly_out *out, struct lylyb_ctx *lybctx)
+lyb_write_stop_siblings(struct ly_out *out, struct lylyb_ctx *lybctx)
 {
     uint8_t meta_buf[LYB_META_BYTES];
 
     /* write the meta chunk information */
-    meta_buf[0] = LYB_LAST_SUBTREE(lybctx).written & LYB_BYTE_MASK;
-    meta_buf[1] = LYB_LAST_SUBTREE(lybctx).inner_chunks & LYB_BYTE_MASK;
-    LY_CHECK_RET(ly_write_skipped(out, LYB_LAST_SUBTREE(lybctx).position, (char *)&meta_buf, LYB_META_BYTES));
+    meta_buf[0] = LYB_LAST_SIBLING(lybctx).written & LYB_BYTE_MASK;
+    meta_buf[1] = LYB_LAST_SIBLING(lybctx).inner_chunks & LYB_BYTE_MASK;
+    LY_CHECK_RET(ly_write_skipped(out, LYB_LAST_SIBLING(lybctx).position, (char *)&meta_buf, LYB_META_BYTES));
 
-    LY_ARRAY_DECREMENT(lybctx->subtrees);
+    LY_ARRAY_DECREMENT(lybctx->siblings);
     return LY_SUCCESS;
 }
 
 /**
- * @brief Start a new subtree - skip bytes for its metadata.
+ * @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_subtree(struct ly_out *out, struct lylyb_ctx *lybctx)
+lyb_write_start_siblings(struct ly_out *out, struct lylyb_ctx *lybctx)
 {
     LY_ARRAY_COUNT_TYPE u;
 
-    u = LY_ARRAY_COUNT(lybctx->subtrees);
-    if (u == lybctx->subtree_size) {
-        LY_ARRAY_CREATE_RET(lybctx->ctx, lybctx->subtrees, u + LYB_SUBTREE_STEP, LY_EMEM);
-        lybctx->subtree_size = u + LYB_SUBTREE_STEP;
+    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->subtrees);
-    LYB_LAST_SUBTREE(lybctx).written = 0;
-    LYB_LAST_SUBTREE(lybctx).inner_chunks = 0;
+    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->subtrees) - 1; ++u) {
-        if (lybctx->subtrees[u].inner_chunks == LYB_INCHUNK_MAX) {
+    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->subtrees[u].inner_chunks;
+        ++lybctx->siblings[u].inner_chunks;
     }
 
-    LY_CHECK_RET(ly_write_skip(out, LYB_META_BYTES, &LYB_LAST_SUBTREE(lybctx).position));
+    LY_CHECK_RET(ly_write_skip(out, LYB_META_BYTES, &LYB_LAST_SIBLING(lybctx).position));
 
     return LY_SUCCESS;
 }
@@ -431,7 +431,8 @@
     if (mod) {
         LY_CHECK_RET(lyb_write_string(mod->name, 0, 1, out, lybctx));
     } else {
-        LY_CHECK_RET(lyb_write_string("", 0, 1, out, lybctx));
+        lyb_write_number(0, 2, out, lybctx);
+        return LY_SUCCESS;
     }
 
     /* model revision as XXXX XXXX XXXX XXXX (2B) (year is offset from 2000)
@@ -624,8 +625,6 @@
 static LY_ERR
 lyb_print_node_opaq(struct ly_out *out, const struct lyd_node_opaq *opaq, struct lyd_lyb_ctx *lyd_lybctx)
 {
-    const struct lyd_node *node;
-    struct hash_table *child_ht = NULL;
     struct lylyb_ctx *lybctx = lyd_lybctx->lybctx;
 
     /* write attributes */
@@ -653,9 +652,7 @@
     LY_CHECK_RET(lyb_print_prefix_data(out, opaq->format, opaq->val_prefix_data, lybctx));
 
     /* recursively write all the descendants */
-    LY_LIST_FOR(lyd_child((struct lyd_node *)opaq), node) {
-        LY_CHECK_RET(lyb_print_subtree(out, node, &child_ht, lyd_lybctx));
-    }
+    LY_CHECK_RET(lyb_print_siblings(out, opaq->child, lyd_lybctx));
 
     return LY_SUCCESS;
 }
@@ -710,7 +707,7 @@
     }
 
     /* followed by the content */
-    LY_CHECK_GOTO(ret = lyb_write_string(str, (size_t)len, 0, out, lybctx), cleanup);
+    LY_CHECK_GOTO(ret = lyb_write_string(str, (size_t)len, 1, out, lybctx), cleanup);
 
 cleanup:
     ly_out_free(out2, NULL, 1);
@@ -829,17 +826,17 @@
 
     if (wd_mod) {
         /* write the "default" metadata */
-        LY_CHECK_RET(lyb_write_start_subtree(out, lybctx->lybctx));
+        LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx));
         LY_CHECK_RET(lyb_print_model(out, wd_mod, lybctx->lybctx));
         LY_CHECK_RET(lyb_write_string("default", 0, 1, out, lybctx->lybctx));
         LY_CHECK_RET(lyb_write_string("true", 0, 0, out, lybctx->lybctx));
-        LY_CHECK_RET(lyb_write_stop_subtree(out, lybctx->lybctx));
+        LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx->lybctx));
     }
 
     /* write all the node metadata */
     LY_LIST_FOR(node->meta, iter) {
-        /* each metadata is a subtree */
-        LY_CHECK_RET(lyb_write_start_subtree(out, lybctx->lybctx));
+        /* each metadata is a sibling */
+        LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx));
 
         /* model */
         LY_CHECK_RET(lyb_print_model(out, iter->annotation->module, lybctx->lybctx));
@@ -850,8 +847,8 @@
         /* metadata value */
         LY_CHECK_RET(lyb_write_string(lyd_get_meta_value(iter), 0, 0, out, lybctx->lybctx));
 
-        /* finish metadata subtree */
-        LY_CHECK_RET(lyb_write_stop_subtree(out, lybctx->lybctx));
+        /* finish metadata sibling */
+        LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx->lybctx));
     }
 
     return LY_SUCCESS;
@@ -884,8 +881,8 @@
 
     /* write all the attributes */
     LY_LIST_FOR(node->attr, iter) {
-        /* each attribute is a subtree */
-        LY_CHECK_RET(lyb_write_start_subtree(out, lybctx));
+        /* each attribute is a sibling */
+        LY_CHECK_RET(lyb_write_start_siblings(out, lybctx));
 
         /* prefix */
         LY_CHECK_RET(lyb_write_string(iter->name.prefix, 0, 1, out, lybctx));
@@ -905,8 +902,8 @@
         /* value */
         LY_CHECK_RET(lyb_write_string(iter->value, 0, 0, out, lybctx));
 
-        /* finish attribute subtree */
-        LY_CHECK_RET(lyb_write_stop_subtree(out, lybctx));
+        /* finish attribute sibling */
+        LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx));
     }
 
     return LY_SUCCESS;
@@ -933,32 +930,6 @@
 }
 
 /**
- * @brief Print model and hash.
- *
- * @param[in] out Out structure.
- * @param[in] node Current data node 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_model_and_hash(struct ly_out *out, const struct lyd_node *node, struct hash_table **sibling_ht,
-        struct lylyb_ctx *lybctx)
-{
-    /* 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));
-    } else if (node->schema && !lysc_data_parent(node->schema)) {
-        LY_CHECK_RET(lyb_print_model(out, node->schema->module, lybctx));
-    }
-
-    /* write schema hash */
-    LY_CHECK_RET(lyb_print_schema_hash(out, (struct lysc_node *)node->schema, sibling_ht, lybctx));
-
-    return LY_SUCCESS;
-}
-
-/**
  * @brief Print schema node hash.
  *
  * @param[in] out Out structure.
@@ -1036,48 +1007,89 @@
 }
 
 /**
- * @brief Print data subtree.
+ * @brief Print node with childs.
  *
  * @param[in] out Out structure.
- * @param[in] node Root node of the subtree to print.
- * @param[in,out] sibling_ht Cached hash table for these data siblings, created if NULL.
+ * @param[in] node Root node of the sibling to print.
  * @param[in] lybctx LYB context.
  * @return LY_ERR value.
  */
 static LY_ERR
-lyb_print_subtree(struct ly_out *out, const struct lyd_node *node, struct hash_table **sibling_ht, struct lyd_lyb_ctx *lybctx)
+lyb_print_node(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx)
 {
-    struct hash_table *child_ht = NULL;
-
-    /* register a new subtree */
-    LY_CHECK_RET(lyb_write_start_subtree(out, lybctx->lybctx));
-
-    LY_CHECK_RET(lyb_print_model_and_hash(out, node, sibling_ht, lybctx->lybctx));
-
     /* write node content */
     if (!node->schema) {
         LY_CHECK_RET(lyb_print_node_opaq(out, (struct lyd_node_opaq *)node, lybctx));
-        goto stop_subtree;
     } else if (node->schema->nodetype & LYD_NODE_INNER) {
+        /* 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));
     } else if (node->schema->nodetype & LYD_NODE_TERM) {
         LY_CHECK_RET(lyb_print_node_header(out, node, lybctx));
         LY_CHECK_RET(lyb_print_term_value((struct lyd_node_term *)node, out, lybctx->lybctx));
     } else if (node->schema->nodetype & LYD_NODE_ANY) {
         LY_CHECK_RET(lyb_print_node_any(out, (struct lyd_node_any *)node, lybctx));
-        goto stop_subtree;
     } else {
         LOGINT_RET(lybctx->lybctx->ctx);
     }
 
-    /* recursively write all the descendants */
-    LY_LIST_FOR(lyd_child(node), node) {
-        LY_CHECK_RET(lyb_print_subtree(out, node, &child_ht, lybctx));
+    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;
+    uint8_t zero = 0;
+
+    if (!node) {
+        lyb_write(out, &zero, 1, lybctx->lybctx);
+        return LY_SUCCESS;
     }
 
-stop_subtree:
-    /* finish this subtree */
-    LY_CHECK_RET(lyb_write_stop_subtree(out, lybctx->lybctx));
+    top_level = !LY_ARRAY_COUNT(lybctx->lybctx->siblings);
+
+    LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx));
+
+    /* write all the siblings */
+    LY_LIST_FOR(node, node) {
+
+        /* do not reuse sibling hash tables from different modules */
+        if (top_level && (!node->schema || (node->schema->module != prev_mod))) {
+            sibling_ht = NULL;
+            prev_mod = node->schema ? node->schema->module : NULL;
+        }
+
+        /* 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)) {
+            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));
+
+        LY_CHECK_RET(lyb_print_node(out, node, lybctx));
+
+        if (top_level && !(lybctx->print_options & LYD_PRINT_WITHSIBLINGS)) {
+            break;
+        }
+    }
+
+    LY_CHECK_RET(lyb_write_stop_siblings(out, lybctx->lybctx));
 
     return LY_SUCCESS;
 }
@@ -1087,8 +1099,6 @@
 {
     LY_ERR ret = LY_SUCCESS;
     uint8_t zero = 0;
-    struct hash_table *top_sibling_ht = NULL;
-    const struct lys_module *prev_mod = NULL;
     struct lyd_lyb_ctx *lybctx;
     const struct ly_ctx *ctx = root ? LYD_CTX(root) : NULL;
 
@@ -1117,19 +1127,8 @@
     /* all used models */
     LY_CHECK_GOTO(ret = lyb_print_data_models(out, root, lybctx->lybctx), cleanup);
 
-    LY_LIST_FOR(root, root) {
-        /* do not reuse sibling hash tables from different modules */
-        if (!root->schema || (root->schema->module != prev_mod)) {
-            top_sibling_ht = NULL;
-            prev_mod = root->schema ? root->schema->module : NULL;
-        }
-
-        LY_CHECK_GOTO(ret = lyb_print_subtree(out, root, &top_sibling_ht, lybctx), cleanup);
-
-        if (!(options & LYD_PRINT_WITHSIBLINGS)) {
-            break;
-        }
-    }
+    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);