/**
 * @file parser_lyb.c
 * @author Michal Vasko <mvasko@cesnet.cz>
 * @brief LYB data parser 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
 */

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#ifdef __APPLE__
# include <libkern/OSByteOrder.h>

# define le16toh(x) OSSwapLittleToHostInt16(x)
# define le32toh(x) OSSwapLittleToHostInt32(x)
# define le64toh(x) OSSwapLittleToHostInt64(x)
#else
# include <endian.h>
#endif

#include "libyang.h"
#include "common.h"
#include "context.h"
#include "parser.h"
#include "tree_internal.h"

#define LYB_HAVE_READ_GOTO(r, d, go) if (r < 0) goto go; d += r;
#define LYB_HAVE_READ_RETURN(r, d, ret) if (r < 0) return ret; d += r;

static int
lyb_read(const char *data, uint8_t *buf, size_t count, struct lyb_state *lybs)
{
    int ret = 0, i, empty_chunk_i;
    size_t to_read;
    uint8_t meta_buf[LYB_META_BYTES];

    assert(data && lybs);

    while (1) {
        /* check for fully-read (empty) data chunks */
        to_read = count;
        empty_chunk_i = -1;
        for (i = 0; i < lybs->used; ++i) {
            /* 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 ((lybs->written[i] <= to_read) && lybs->position[i]) {
                /* empty chunk, do not read more */
                to_read = lybs->written[i];
                empty_chunk_i = i;
            }
        }

        if ((empty_chunk_i == -1) && !count) {
            break;
        }

        /* we are actually reading some data, not just finishing another chunk */
        if (to_read) {
            if (buf) {
                memcpy(buf, data + ret, to_read);
            }

            for (i = 0; i < lybs->used; ++i) {
                /* decrease all written counters */
                lybs->written[i] -= to_read;
                assert(lybs->written[i] <= LYB_SIZE_MAX);
            }
            /* decrease count/buf */
            count -= to_read;
            if (buf) {
                buf += to_read;
            }

            ret += to_read;
        }

        if (empty_chunk_i > -1) {
            /* read the next chunk meta information */
            memcpy(meta_buf, data + ret, LYB_META_BYTES);
            lybs->written[empty_chunk_i] = meta_buf[0];
            lybs->inner_chunks[empty_chunk_i] = meta_buf[1];

            /* remember whether there is a following chunk or not */
            lybs->position[empty_chunk_i] = (lybs->written[empty_chunk_i] == LYB_SIZE_MAX ? 1 : 0);

            ret += LYB_META_BYTES;
        }
    }

    return ret;
}

static int
lyb_read_number(uint64_t *num, size_t bytes, const char *data, struct lyb_state *lybs)
{
    int r, ret = 0;
    size_t i;
    uint8_t byte;

    for (i = 0; i < bytes; ++i) {
        ret += (r = lyb_read(data, &byte, 1, lybs));
        LYB_HAVE_READ_RETURN(r, data, -1);

        *(((uint8_t *)num) + i) = byte;
    }

    return ret;
}

static int
lyb_read_enum(uint64_t *enum_idx, uint32_t count, const char *data, struct lyb_state *lybs)
{
    int ret = 0;
    size_t bytes;
    uint64_t tmp_enum = 0;

    if (count < (1 << 8)) {
        bytes = 1;
    } else if (count < (1 << 16)) {
        bytes = 2;
    } else if (count < (1 << 24)) {
        bytes = 3;
    } else {
        bytes = 4;
    }

    /* The enum is always read into a uint64_t buffer */
    ret = lyb_read_number(&tmp_enum, bytes, data, lybs);
    *enum_idx = le64toh(tmp_enum);

    return ret;
}

static int
lyb_read_string(const char *data, char **str, int with_length, struct lyb_state *lybs)
{
    int next_chunk = 0, r, ret = 0;
    size_t len = 0, cur_len;
    uint8_t len_buf[2];

    if (with_length) {
        ret += (r = lyb_read(data, len_buf, 2, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);
        len = len_buf[0] | (len_buf[1] << 8);
    } else {
        /* read until the end of this subtree */
        len = lybs->written[lybs->used - 1];
        if (lybs->position[lybs->used - 1]) {
            next_chunk = 1;
        }
    }

    *str = malloc((len + 1) * sizeof **str);
    LY_CHECK_ERR_RETURN(!*str, LOGMEM(NULL), -1);

    ret += (r = lyb_read(data, (uint8_t *)*str, len, lybs));
    LYB_HAVE_READ_GOTO(r, data, error);

    while (next_chunk) {
        cur_len = lybs->written[lybs->used - 1];
        if (lybs->position[lybs->used - 1]) {
            next_chunk = 1;
        } else {
            next_chunk = 0;
        }

        *str = ly_realloc(*str, (len + cur_len + 1) * sizeof **str);
        LY_CHECK_ERR_RETURN(!*str, LOGMEM(NULL), -1);

        ret += (r = lyb_read(data, ((uint8_t *)*str) + len, cur_len, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);

        len += cur_len;
    }

    ((char *)*str)[len] = '\0';
    return ret;

error:
    free((char *)*str);
    *str = NULL;
    return -1;
}

static void
lyb_read_stop_subtree(struct lyb_state *lybs)
{
    if (lybs->written[lybs->used - 1]) {
        LOGINT(NULL);
    }

    --lybs->used;
}

static int
lyb_read_start_subtree(const char *data, struct lyb_state *lybs)
{
    uint8_t meta_buf[LYB_META_BYTES];

    if (lybs->used == lybs->size) {
        lybs->size += LYB_STATE_STEP;
        lybs->written = ly_realloc(lybs->written, lybs->size * sizeof *lybs->written);
        lybs->position = ly_realloc(lybs->position, lybs->size * sizeof *lybs->position);
        lybs->inner_chunks = ly_realloc(lybs->inner_chunks, lybs->size * sizeof *lybs->inner_chunks);
        LY_CHECK_ERR_RETURN(!lybs->written || !lybs->position || !lybs->inner_chunks, LOGMEM(NULL), -1);
    }

    memcpy(meta_buf, data, LYB_META_BYTES);

    ++lybs->used;
    lybs->written[lybs->used - 1] = meta_buf[0];
    lybs->inner_chunks[lybs->used - 1] = meta_buf[LYB_SIZE_BYTES];
    lybs->position[lybs->used - 1] = (lybs->written[lybs->used - 1] == LYB_SIZE_MAX ? 1 : 0);

    return LYB_META_BYTES;
}

static int
lyb_parse_model(struct ly_ctx *ctx, const char *data, const struct lys_module **mod, struct lyb_state *lybs)
{
    int r, ret = 0;
    char *mod_name = NULL, mod_rev[11];
    uint16_t rev = 0;
    uint8_t tmp_buf[2];

    /* model name */
    ret += (r = lyb_read_string(data, &mod_name, 1, lybs));
    LYB_HAVE_READ_GOTO(r, data, error);

    /* revision */
    ret += (r = lyb_read(data, tmp_buf, sizeof tmp_buf, lybs));
    LYB_HAVE_READ_GOTO(r, data, error);
    rev = tmp_buf[0] | (tmp_buf[1] << 8);

    if (rev) {
        sprintf(mod_rev, "%04u-%02u-%02u", ((rev & 0xFE00) >> 9) + 2000, (rev & 0x01E0) >> 5, (rev & 0x001F));
        *mod = ly_ctx_get_module(ctx, mod_name, mod_rev, 0);
    } else {
        *mod = ly_ctx_get_module(ctx, mod_name, NULL, 0);
    }
    if (ctx->data_clb) {
        if (!*mod) {
            *mod = ctx->data_clb(ctx, mod_name, NULL, 0, ctx->data_clb_data);
        } else if (!(*mod)->implemented) {
            *mod = ctx->data_clb(ctx, mod_name, (*mod)->ns, LY_MODCLB_NOT_IMPLEMENTED, ctx->data_clb_data);
        }
    }

    if (!*mod) {
        LOGERR(ctx, LY_EINVAL, "Invalid context for LYB data parsing, missing module \"%s%s%s\".",
               mod_name, rev ? "@" : "", rev ? mod_rev : "");
        goto error;
    } else if (!(*mod)->implemented) {
        LOGERR(ctx, LY_EINVAL, "Invalid context for LYB data parsing, module \"%s%s%s\" not implemented.",
               mod_name, rev ? "@" : "", rev ? mod_rev : "");
        goto error;
    }

    free(mod_name);
    return ret;

error:
    free(mod_name);
    return -1;
}

static struct lyd_node *
lyb_new_node(const struct lys_node *schema)
{
    struct lyd_node *node;

    switch (schema->nodetype) {
    case LYS_CONTAINER:
    case LYS_LIST:
    case LYS_NOTIF:
    case LYS_RPC:
    case LYS_ACTION:
        node = calloc(sizeof(struct lyd_node), 1);
        break;
    case LYS_LEAF:
    case LYS_LEAFLIST:
        node = calloc(sizeof(struct lyd_node_leaf_list), 1);

        if (((struct lys_node_leaf *)schema)->type.base == LY_TYPE_LEAFREF) {
            node->validity |= LYD_VAL_LEAFREF;
        }
        break;
    case LYS_ANYDATA:
    case LYS_ANYXML:
        node = calloc(sizeof(struct lyd_node_anydata), 1);
        break;
    default:
        return NULL;
    }
    LY_CHECK_ERR_RETURN(!node, LOGMEM(schema->module->ctx), NULL);

    /* fill basic info */
    node->schema = (struct lys_node *)schema;
    if (resolve_applies_when(schema, 0, NULL)) {
        /* this data are considered trusted so if this node exists, it means its when must have been true */
        node->when_status = LYD_WHEN | LYD_WHEN_TRUE;
    }
    node->prev = node;

    return node;
}

static int
lyb_parse_anydata(struct lyd_node *node, const char *data, struct lyb_state *lybs)
{
    int r, ret = 0;
    char *str = NULL;
    struct lyd_node_anydata *any = (struct lyd_node_anydata *)node;

    /* read value type */
    ret += (r = lyb_read(data, (uint8_t *)&any->value_type, sizeof any->value_type, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);

    /* read anydata content */
    if (any->value_type == LYD_ANYDATA_DATATREE) {
        /* invalid situation */
        LOGINT(node->schema->module->ctx);
        return -1;
    } else if (any->value_type == LYD_ANYDATA_LYB) {
        ret += (r = lyb_read_string(data, &any->value.mem, 0, lybs));
        LYB_HAVE_READ_RETURN(r, data, -1);
    } else {
        ret += (r = lyb_read_string(data, &str, 0, lybs));
        LYB_HAVE_READ_RETURN(r, data, -1);

        /* add to dictionary */
        any->value.str = lydict_insert_zc(node->schema->module->ctx, str);
    }

    return ret;
}

/* generally, fill lyd_val value union */
static int
lyb_parse_val_1(struct ly_ctx *ctx, struct lys_type *type, LY_DATA_TYPE value_type, uint8_t value_flags, const char *data,
                const char **value_str, lyd_val *value, struct lyb_state *lybs)
{
    int r, ret;
    size_t i;
    char *str = NULL;
    uint8_t byte;
    uint64_t num;

    if (value_flags & LY_VALUE_USER) {
        /* just read value_str */
        ret = lyb_read_string(data, &str, 0, lybs);
        if (ret > -1) {
            *value_str = lydict_insert_zc(ctx, str);
        }
        return ret;
    }

    /* find the correct structure, go through leafrefs and typedefs */
    switch (value_type) {
    case LY_TYPE_ENUM:
        for (; type->base == LY_TYPE_LEAFREF; type = &type->info.lref.target->type);
        for (; !type->info.enums.count; type = &type->der->type);
        break;
    case LY_TYPE_BITS:
        for (; type->base == LY_TYPE_LEAFREF; type = &type->info.lref.target->type);
        for (; !type->info.bits.count; type = &type->der->type);
        break;
    default:
        break;
    }

    switch (value_type) {
    case LY_TYPE_INST:
    case LY_TYPE_IDENT:
    case LY_TYPE_UNION:
        /* we do not actually fill value now, but value_str */
        ret = lyb_read_string(data, &str, 0, lybs);
        if (ret > -1) {
            *value_str = lydict_insert_zc(ctx, str);
        }
        break;
    case LY_TYPE_BINARY:
    case LY_TYPE_STRING:
    case LY_TYPE_UNKNOWN:
        /* read string */
        ret = lyb_read_string(data, &str, 0, lybs);
        if (ret > -1) {
            value->string = lydict_insert_zc(ctx, str);
        }
        break;
    case LY_TYPE_BITS:
        value->bit = calloc(type->info.bits.count, sizeof *value->bit);
        LY_CHECK_ERR_RETURN(!value->bit, LOGMEM(ctx), -1);

        /* read values */
        ret = 0;
        for (i = 0; i < type->info.bits.count; ++i) {
            if (i % 8 == 0) {
                /* read another byte */
                ret += (r = lyb_read(data + ret, &byte, sizeof byte, lybs));
                if (r < 0) {
                    return -1;
                }
            }

            if (byte & (0x01 << (i % 8))) {
                /* bit is set */
                value->bit[i] = &type->info.bits.bit[i];
            }
        }
        break;
    case LY_TYPE_BOOL:
        /* read byte */
        ret = lyb_read(data, &byte, sizeof byte, lybs);
        if ((ret > 0) && byte) {
            value->bln = 1;
        }
        break;
    case LY_TYPE_EMPTY:
        /* nothing to read */
        ret = 0;
        break;
    case LY_TYPE_ENUM:
        num = 0;
        ret = lyb_read_enum(&num, type->info.enums.count, data, lybs);
        if (ret > 0) {
            assert(num < type->info.enums.count);
            value->enm = &type->info.enums.enm[num];
        }
        break;
    case LY_TYPE_INT8:
    case LY_TYPE_UINT8:
        ret = lyb_read_number((uint64_t *)&value->uint8, 1, data, lybs);
        break;
    case LY_TYPE_INT16:
    case LY_TYPE_UINT16:
        ret = lyb_read_number((uint64_t *)&value->uint16, 2, data, lybs);
        value->uint16 = le16toh(value->uint16);
        break;
    case LY_TYPE_INT32:
    case LY_TYPE_UINT32:
        ret = lyb_read_number((uint64_t *)&value->uint32, 4, data, lybs);
        value->uint32 = le32toh(value->uint32);
        break;
    case LY_TYPE_DEC64:
    case LY_TYPE_INT64:
    case LY_TYPE_UINT64:
        ret = lyb_read_number((uint64_t *)&value->uint64, 8, data, lybs);
        value->uint64 = le64toh(value->uint64);
        break;
    default:
        return -1;
    }

    return ret;
}

/* generally, fill value_str */
static int
lyb_parse_val_2(struct lys_type *type, struct lyd_node_leaf_list *leaf, struct lyd_attr *attr, struct unres_data *unres)
{
    struct ly_ctx *ctx;
    struct lys_module *mod;
    struct lys_type *rtype;
    char num_str[22], *str;
    int64_t frac;
    uint32_t i, str_len;
    uint8_t *value_flags, dig;
    const char **value_str;
    LY_DATA_TYPE value_type;
    lyd_val *value;

    if (leaf) {
        ctx = leaf->schema->module->ctx;
        mod = lys_node_module(leaf->schema);

        value = &leaf->value;
        value_str = &leaf->value_str;
        value_flags = &leaf->value_flags;
        value_type = leaf->value_type;
    } else {
        ctx = attr->annotation->module->ctx;
        mod = lys_main_module(attr->annotation->module);

        value = &attr->value;
        value_str = &attr->value_str;
        value_flags = &attr->value_flags;
        value_type = attr->value_type;
    }

    if (*value_flags & LY_VALUE_USER) {
        /* unfortunately, we need to also fill the value properly, so just parse it again */
        *value_flags &= ~LY_VALUE_USER;
        if (!lyp_parse_value(type, value_str, NULL, leaf, attr, NULL, 1, (leaf ? leaf->dflt : 0), 1)) {
            return -1;
        }

        if (!(*value_flags & LY_VALUE_USER)) {
            LOGWRN(ctx, "Value \"%s\" was stored as a user type, but it is not in the current context.", value_str);
        }
        return 0;
    }

    /* we are parsing leafref/ptr union stored as the target type,
     * so we first parse it into string and then resolve the leafref/ptr union */
    if ((type->base == LY_TYPE_LEAFREF) || (type->base == LY_TYPE_INST)
            || ((type->base == LY_TYPE_UNION) && type->info.uni.has_ptr_type)) {
        if ((value_type == LY_TYPE_INST) || (value_type == LY_TYPE_IDENT) || (value_type == LY_TYPE_UNION)) {
            /* we already have a string */
            goto parse_reference;
        }
    }

    /* find the correct structure, go through leafrefs and typedefs */
    switch (value_type) {
    case LY_TYPE_BITS:
        for (rtype = type; rtype->base == LY_TYPE_LEAFREF; rtype = &rtype->info.lref.target->type);
        for (; !rtype->info.bits.count; rtype = &rtype->der->type);
        break;
    case LY_TYPE_DEC64:
        for (rtype = type; rtype->base == LY_TYPE_LEAFREF; rtype = &type->info.lref.target->type);
        break;
    default:
        break;
    }

    switch (value_type) {
    case LY_TYPE_IDENT:
        /* fill the identity pointer now */
        value->ident = resolve_identref(type, *value_str, (struct lyd_node *)leaf, mod, (leaf ? leaf->dflt : 0));
        if (!value->ident) {
            return -1;
        }
        break;
    case LY_TYPE_INST:
        /* unresolved instance-identifier, keep value NULL */
        value->instance = NULL;
        break;
    case LY_TYPE_BINARY:
    case LY_TYPE_STRING:
    case LY_TYPE_UNKNOWN:
        /* just re-assign it */
        *value_str = value->string;
        break;
    case LY_TYPE_BITS:
        /* print the set bits */
        str = malloc(1);
        LY_CHECK_ERR_RETURN(!str, LOGMEM(ctx), -1);
        str[0] = '\0';
        str_len = 0;
        for (i = 0; i < rtype->info.bits.count; ++i) {
            if (value->bit[i]) {
                str = ly_realloc(str, str_len + strlen(value->bit[i]->name) + (str_len ? 1 : 0) + 1);
                LY_CHECK_ERR_RETURN(!str, LOGMEM(ctx), -1);

                str_len += sprintf(str + str_len, "%s%s", str_len ? " " : "", value->bit[i]->name);
            }
        }

        *value_str = lydict_insert_zc(ctx, str);
        break;
    case LY_TYPE_BOOL:
        *value_str = lydict_insert(ctx, (value->bln ? "true" : "false"), 0);
        break;
    case LY_TYPE_EMPTY:
        *value_str = lydict_insert(ctx, "", 0);
        break;
    case LY_TYPE_UNION:
        if (attr) {
            /* we do not support union type attribute */
            LOGINT(ctx);
            return -1;
        }

        if (resolve_union(leaf, type, 1, 2, NULL)) {
            return -1;
        }
        break;
    case LY_TYPE_ENUM:
        /* print the value */
        *value_str = lydict_insert(ctx, value->enm->name, 0);
        break;
    case LY_TYPE_INT8:
        sprintf(num_str, "%d", value->int8);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_UINT8:
        sprintf(num_str, "%u", value->uint8);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_INT16:
        sprintf(num_str, "%d", value->int16);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_UINT16:
        sprintf(num_str, "%u", value->uint16);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_INT32:
        sprintf(num_str, "%d", value->int32);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_UINT32:
        sprintf(num_str, "%u", value->uint32);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_INT64:
        sprintf(num_str, "%"PRId64, value->int64);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_UINT64:
        sprintf(num_str, "%"PRIu64, value->uint64);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    case LY_TYPE_DEC64:
        frac = value->dec64 % rtype->info.dec64.div;
        dig = rtype->info.dec64.dig;
        /* remove trailing zeros */
        while ((dig > 1) && !(frac % 10)) {
            frac /= 10;
            --dig;
        }

        sprintf(num_str, "%"PRId64".%.*"PRId64, value->dec64 / (int64_t)rtype->info.dec64.div, dig, frac);
        *value_str = lydict_insert(ctx, num_str, 0);
        break;
    default:
        return -1;
    }

    if ((type->base == LY_TYPE_LEAFREF) || (type->base == LY_TYPE_INST)
            || ((type->base == LY_TYPE_UNION) && type->info.uni.has_ptr_type)) {
parse_reference:
        assert(*value_str);

        if (attr) {
            /* we do not support reference types of attributes */
            LOGINT(ctx);
            return -1;
        }

        if (type->base == LY_TYPE_INST) {
            if (unres_data_add(unres, (struct lyd_node *)leaf, UNRES_INSTID)) {
                return -1;
            }
        } else if (type->base == LY_TYPE_LEAFREF) {
            if (unres_data_add(unres, (struct lyd_node *)leaf, UNRES_LEAFREF)) {
                return -1;
            }
        } else {
            if (unres_data_add(unres, (struct lyd_node *)leaf, UNRES_UNION)) {
                return -1;
            }
        }
    }

    return 0;
}

static int
lyb_parse_value(struct lys_type *type, struct lyd_node_leaf_list *leaf, struct lyd_attr *attr, const char *data,
                struct unres_data *unres, struct lyb_state *lybs)
{
    int r, ret = 0;
    uint8_t start_byte;

    struct ly_ctx *ctx;
    const char **value_str;
    lyd_val *value;
    LY_DATA_TYPE *value_type;
    uint8_t *value_flags;

    assert((leaf || attr) && (!leaf || !attr));

    if (leaf) {
        ctx = leaf->schema->module->ctx;
        value_str = &leaf->value_str;
        value = &leaf->value;
        value_type = &leaf->value_type;
        value_flags = &leaf->value_flags;
    } else {
        ctx = attr->annotation->module->ctx;
        value_str = &attr->value_str;
        value = &attr->value;
        value_type = &attr->value_type;
        value_flags = &attr->value_flags;
    }

    /* read value type and flags on the first byte */
    ret += (r = lyb_read(data, &start_byte, sizeof start_byte, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);

    /* fill value type, flags */
    *value_type = start_byte & 0x1F;
    if (start_byte & 0x80) {
        assert(leaf);
        leaf->dflt = 1;
    }
    if (start_byte & 0x40) {
        *value_flags |= LY_VALUE_USER;
    }
    if (start_byte & 0x20) {
        *value_flags |= LY_VALUE_UNRES;
    }

    ret += (r = lyb_parse_val_1(ctx, type, *value_type, *value_flags, data, value_str, value, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);

    /* union is handled specially */
    if ((type->base == LY_TYPE_UNION) && !(*value_flags & LY_VALUE_USER)) {
        assert(*value_type == LY_TYPE_STRING);

        *value_str = value->string;
        value->string = NULL;
        *value_type = LY_TYPE_UNION;
    }

    ret += (r = lyb_parse_val_2(type, leaf, attr, unres));
    LYB_HAVE_READ_RETURN(r, data, -1);

    return ret;
}

static int
lyb_parse_attr_name(const struct lys_module *mod, const char *data, struct lys_ext_instance_complex **ext, int options,
                    struct lyb_state *lybs)
{
    int r, ret = 0, pos, i, j, k;
    const struct lys_submodule *submod = NULL;
    char *attr_name = NULL;

    /* attr name */
    ret += (r = lyb_read_string(data, &attr_name, 1, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);

    /* search module */
    pos = -1;
    for (i = 0, j = 0; i < mod->ext_size; i = i + j + 1) {
        j = lys_ext_instance_presence(&mod->ctx->models.list[0]->extensions[0], &mod->ext[i], mod->ext_size - i);
        if (j == -1) {
            break;
        }
        if (ly_strequal(mod->ext[i + j]->arg_value, attr_name, 0)) {
            pos = i + j;
            break;
        }
    }

    /* try submodules */
    if (pos == -1) {
        for (k = 0; k < mod->inc_size; ++k) {
            submod = mod->inc[k].submodule;
            for (i = 0, j = 0; i < submod->ext_size; i = i + j + 1) {
                j = lys_ext_instance_presence(&mod->ctx->models.list[0]->extensions[0], &submod->ext[i], submod->ext_size - i);
                if (j == -1) {
                    break;
                }
                if (ly_strequal(submod->ext[i + j]->arg_value, attr_name, 0)) {
                    pos = i + j;
                    break;
                }
            }
        }
    }

    if (pos == -1) {
        *ext = NULL;
    } else {
        *ext = submod ? (struct lys_ext_instance_complex *)submod->ext[pos] : (struct lys_ext_instance_complex *)mod->ext[pos];
    }

    if (!*ext && (options & LYD_OPT_STRICT)) {
        LOGVAL(mod->ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Failed to find annotation \"%s\" in \"%s\".", attr_name, mod->name);
        free(attr_name);
        return -1;
    }

    free(attr_name);
    return ret;
}

static int
lyb_parse_attributes(struct lyd_node *node, const char *data, int options, struct unres_data *unres, struct lyb_state *lybs)
{
    int r, ret = 0;
    uint8_t i, count = 0;
    const struct lys_module *mod;
    struct lys_type **type;
    struct lyd_attr *attr = NULL;
    struct lys_ext_instance_complex *ext;
    struct ly_ctx *ctx = node->schema->module->ctx;

    /* read number of attributes stored */
    ret += (r = lyb_read(data, &count, 1, lybs));
    LYB_HAVE_READ_GOTO(r, data, error);

    /* read attributes */
    for (i = 0; i < count; ++i) {
        ret += (r = lyb_read_start_subtree(data, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);

        /* find model */
        ret += (r = lyb_parse_model(ctx, data, &mod, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);

        if (mod) {
            /* annotation name */
            ret += (r = lyb_parse_attr_name(mod, data, &ext, options, lybs));
            LYB_HAVE_READ_GOTO(r, data, error);
        }

        if (!mod || !ext) {
            /* unknown attribute, skip it */
            do {
                ret += (r = lyb_read(data, NULL, lybs->written[lybs->used - 1], lybs));
                LYB_HAVE_READ_GOTO(r, data, error);
            } while (lybs->written[lybs->used - 1]);
            goto stop_subtree;
        }

        /* allocate new attribute */
        if (!attr) {
            assert(!node->attr);

            attr = calloc(1, sizeof *attr);
            LY_CHECK_ERR_GOTO(!attr, LOGMEM(ctx), error);

            node->attr = attr;
        } else {
            attr->next = calloc(1, sizeof *attr);
            LY_CHECK_ERR_GOTO(!attr->next, LOGMEM(ctx), error);

            attr = attr->next;
        }

        /* attribute annotation */
        attr->annotation = ext;

        /* attribute name */
        attr->name = lydict_insert(ctx, attr->annotation->arg_value, 0);

        /* get the type */
        type = (struct lys_type **)lys_ext_complex_get_substmt(LY_STMT_TYPE, attr->annotation, NULL);
        if (!type || !(*type)) {
            goto error;
        }

        /* attribute value */
        ret += (r = lyb_parse_value(*type, NULL, attr, data, unres, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);

stop_subtree:
        lyb_read_stop_subtree(lybs);
    }

    return ret;

error:
    lyd_free_attr(ctx, node, node->attr, 1);
    return -1;
}

static int
lyb_is_schema_hash_match(struct lys_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_hash(sibling, i);
        if (sibling_hash != hash[i]) {
            return 0;
        }
    }

    return 1;
}

static int
lyb_parse_schema_hash(const struct lys_node *sparent, const struct lys_module *mod, const char *data, const char *yang_data_name,
                      int options, struct lys_node **snode, struct lyb_state *lybs)
{
    int r, ret = 0;
    uint8_t i, j;
    struct lys_node *sibling;
    LYB_HASH hash[LYB_HASH_BITS - 1];
    struct ly_ctx *ctx;

    assert((sparent || mod) && (!sparent || !mod));
    ctx = (sparent ? sparent->module->ctx : mod->ctx);

    /* read the first hash */
    ret += (r = lyb_read(data, &hash[0], sizeof *hash, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);

    /* 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) {
            return -1;
        }
    }

    /* move the first hash on its accurate position */
    hash[i] = hash[0];

    /* read the rest of hashes */
    for (j = i; j; --j) {
        ret += (r = lyb_read(data, &hash[j - 1], sizeof *hash, lybs));
        LYB_HAVE_READ_RETURN(r, data, -1);

        /* 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)))));
    }

    /* handle yang data templates */
    if ((options & LYD_OPT_DATA_TEMPLATE) && yang_data_name && mod) {
        sparent = lyp_get_yang_data_template(mod, yang_data_name, strlen(yang_data_name));
        if (!sparent) {
            sibling = NULL;
            goto finish;
        }
    }

    /* handle RPC/action input/output */
    if (sparent && (sparent->nodetype & (LYS_RPC | LYS_ACTION))) {
        sibling = NULL;
        while ((sibling = (struct lys_node *)lys_getnext(sibling, sparent, NULL, LYS_GETNEXT_WITHINOUT))) {
            if ((sibling->nodetype == LYS_INPUT) && (options & LYD_OPT_RPC)) {
                break;
            }
            if ((sibling->nodetype == LYS_OUTPUT) && (options & LYD_OPT_RPCREPLY)) {
                break;
            }
        }
        if (!sibling) {
            /* fail */
            goto finish;
        }

        /* use only input/output children nodes */
        sparent = sibling;
    }

    /* find our node with matching hashes */
    sibling = NULL;
    while ((sibling = (struct lys_node *)lys_getnext(sibling, sparent, mod, 0))) {
        /* skip schema nodes from models not present during printing */
        if (lyb_has_schema_model(sibling, lybs->models, lybs->mod_count) && lyb_is_schema_hash_match(sibling, hash, i + 1)) {
            /* match found */
            break;
        }
    }

finish:
    *snode = sibling;
    if (!sibling && (options & LYD_OPT_STRICT)) {
        if (mod) {
            LOGVAL(ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Failed to find matching hash for a top-level node from \"%s\".", mod->name);
        } else {
            LOGVAL(ctx, LYE_SPEC, LY_VLOG_LYS, sparent, "Failed to find matching hash for a child of \"%s\".", sparent->name);
        }
        return -1;
    }

    return ret;
}

static int
lyb_parse_subtree(struct ly_ctx *ctx, const char *data, struct lyd_node *parent, struct lyd_node **first_sibling,
                  const char *yang_data_name, int options, struct unres_data *unres, struct lyb_state *lybs)
{
    int r, ret = 0;
    struct lyd_node *node = NULL, *iter;
    const struct lys_module *mod;
    struct lys_node *snode;

    assert((parent && !first_sibling) || (!parent && first_sibling));

    /* register a new subtree */
    ret += (r = lyb_read_start_subtree(data, lybs));
    LYB_HAVE_READ_GOTO(r, data, error);

    if (!parent) {
        /* top-level, read module name */
        ret += (r = lyb_parse_model(ctx, data, &mod, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);

        if (mod) {
            /* read hash, find the schema node starting from mod, possibly yang_data_name */
            r = lyb_parse_schema_hash(NULL, mod, data, yang_data_name, options, &snode, lybs);
        }
    } else {
        mod = lyd_node_module(parent);

        /* read hash, find the schema node starting from parent schema */
        r = lyb_parse_schema_hash(parent->schema, NULL, data, NULL, options, &snode, lybs);
    }
    ret += r;
    LYB_HAVE_READ_GOTO(r, data, error);

    if (!mod || !snode) {
        /* unknown data subtree, skip it whole */
        do {
            ret += (r = lyb_read(data, NULL, lybs->written[lybs->used - 1], lybs));
            /* also skip the meta information inside */
            ret += (r = lyb_read(data, NULL, lybs->inner_chunks[lybs->used - 1] * LYB_META_BYTES, lybs));
        } while (lybs->written[lybs->used - 1]);
        goto stop_subtree;
    }

    /*
     * read the node
     */
    node = lyb_new_node(snode);
    if (!node) {
        goto error;
    }

    ret += (r = lyb_parse_attributes(node, data, options, unres, lybs));
    LYB_HAVE_READ_GOTO(r, data, error);

    /* read node content */
    switch (snode->nodetype) {
    case LYS_CONTAINER:
    case LYS_LIST:
    case LYS_NOTIF:
    case LYS_RPC:
    case LYS_ACTION:
        /* nothing to read */
        break;
    case LYS_LEAF:
    case LYS_LEAFLIST:
        ret += (r = lyb_parse_value(&((struct lys_node_leaf *)node->schema)->type, (struct lyd_node_leaf_list *)node,
                                    NULL, data, unres, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);
        break;
    case LYS_ANYXML:
    case LYS_ANYDATA:
        ret += (r = lyb_parse_anydata(node, data, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);
        break;
    default:
        goto error;
    }

    /* insert into data tree, manually */
    if (parent) {
        if (!parent->child) {
            /* only child */
            parent->child = node;
        } else {
            /* last child */
            parent->child->prev->next = node;
            node->prev = parent->child->prev;
            parent->child->prev = node;
        }
        node->parent = parent;
    } else if (*first_sibling) {
        /* last sibling */
        (*first_sibling)->prev->next = node;
        node->prev = (*first_sibling)->prev;
        (*first_sibling)->prev = node;
    } else {
        /* only sibling */
        *first_sibling = node;
    }

    /* read all descendants */
    while (lybs->written[lybs->used - 1]) {
        ret += (r = lyb_parse_subtree(ctx, data, node, NULL, NULL, options, unres, lybs));
        LYB_HAVE_READ_GOTO(r, data, error);
    }

    /* make containers default if should be */
    if ((node->schema->nodetype == LYS_CONTAINER) && !((struct lys_node_container *)node->schema)->presence) {
        LY_TREE_FOR(node->child, iter) {
            if (!iter->dflt) {
                break;
            }
        }

        if (!iter) {
            node->dflt = 1;
        }
    }

#ifdef LY_ENABLED_CACHE
    /* calculate the hash and insert it into parent (list with keys is handled when its keys are inserted) */
    if ((node->schema->nodetype != LYS_LIST) || !((struct lys_node_list *)node->schema)->keys_size) {
        lyd_hash(node);
        lyd_insert_hash(node);
    }
#endif

stop_subtree:
    /* end the subtree */
    lyb_read_stop_subtree(lybs);

    return ret;

error:
    lyd_free(node);
    if (first_sibling && (*first_sibling == node)) {
        *first_sibling = NULL;
    }
    return -1;
}

static int
lyb_parse_data_models(struct ly_ctx *ctx, const char *data, struct lyb_state *lybs)
{
    int i, r, ret = 0;
    uint8_t mod_count_buf[2];

    /* read model count */
    ret += (r = lyb_read(data, mod_count_buf, 2, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);
    lybs->mod_count = mod_count_buf[0] | (mod_count_buf[1] << 8);

    lybs->models = malloc(lybs->mod_count * sizeof *lybs->models);
    LY_CHECK_ERR_RETURN(!lybs->models, LOGMEM(NULL), -1);

    /* read modules */
    for (i = 0; i < lybs->mod_count; ++i) {
        ret += (r = lyb_parse_model(ctx, data, &lybs->models[i], lybs));
        LYB_HAVE_READ_RETURN(r, data, -1);
    }

    return ret;
}

static int
lyb_parse_magic_number(const char *data, struct lyb_state *lybs)
{
    int r, ret = 0;
    char magic_byte = 0;

    ret += (r = lyb_read(data, (uint8_t *)&magic_byte, 1, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);
    if (magic_byte != 'l') {
        LOGERR(NULL, LY_EINVAL, "Invalid first magic number byte \"0x%02x\".", magic_byte);
        return -1;
    }

    ret += (r = lyb_read(data, (uint8_t *)&magic_byte, 1, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);
    if (magic_byte != 'y') {
        LOGERR(NULL, LY_EINVAL, "Invalid second magic number byte \"0x%02x\".", magic_byte);
        return -1;
    }

    ret += (r = lyb_read(data, (uint8_t *)&magic_byte, 1, lybs));
    LYB_HAVE_READ_RETURN(r, data, -1);
    if (magic_byte != 'b') {
        LOGERR(NULL, LY_EINVAL, "Invalid third magic number byte \"0x%02x\".", magic_byte);
        return -1;
    }

    return ret;
}

static int
lyb_parse_header(const char *data, struct lyb_state *lybs)
{
    int ret = 0;
    uint8_t byte = 0;

    /* TODO version, any flags? */
    ret += lyb_read(data, (uint8_t *)&byte, sizeof byte, lybs);

    return ret;
}

struct lyd_node *
lyd_parse_lyb(struct ly_ctx *ctx, const char *data, int options, const struct lyd_node *data_tree,
              const char *yang_data_name, int *parsed)
{
    int r = 0, ret = 0;
    struct lyd_node *node = NULL, *next, *act_notif = NULL;
    struct unres_data *unres = NULL;
    struct lyb_state lybs;

    if (!ctx || !data) {
        LOGARG;
        return NULL;
    }

    lybs.written = malloc(LYB_STATE_STEP * sizeof *lybs.written);
    lybs.position = malloc(LYB_STATE_STEP * sizeof *lybs.position);
    lybs.inner_chunks = malloc(LYB_STATE_STEP * sizeof *lybs.inner_chunks);
    LY_CHECK_ERR_GOTO(!lybs.written || !lybs.position || !lybs.inner_chunks, LOGMEM(ctx), finish);
    lybs.used = 0;
    lybs.size = LYB_STATE_STEP;
    lybs.models = NULL;
    lybs.mod_count = 0;

    unres = calloc(1, sizeof *unres);
    LY_CHECK_ERR_GOTO(!unres, LOGMEM(ctx), finish);

    /* read magic number */
    ret += (r = lyb_parse_magic_number(data, &lybs));
    LYB_HAVE_READ_GOTO(r, data, finish);

    /* read header */
    ret += (r = lyb_parse_header(data, &lybs));
    LYB_HAVE_READ_GOTO(r, data, finish);

    /* read used models */
    ret += (r = lyb_parse_data_models(ctx, data, &lybs));
    LYB_HAVE_READ_GOTO(r, data, finish);

    /* read subtree(s) */
    while (data[0]) {
        ret += (r = lyb_parse_subtree(ctx, data, NULL, &node, yang_data_name, options, unres, &lybs));
        if (r < 0) {
            lyd_free_withsiblings(node);
            node = NULL;
            goto finish;
        }
        data += r;
    }

    /* read the last zero, parsing finished */
    ++ret;
    r = ret;

    if (options & LYD_OPT_DATA_ADD_YANGLIB) {
        if (lyd_merge(node, ly_ctx_info(ctx), LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) {
            LOGERR(ctx, LY_EINT, "Adding ietf-yang-library data failed.");
            lyd_free_withsiblings(node);
            node = NULL;
            goto finish;
        }
    }

    /* resolve any unresolved instance-identifiers */
    if (unres->count) {
        if (options & (LYD_OPT_RPC | LYD_OPT_RPCREPLY | LYD_OPT_NOTIF)) {
            LY_TREE_DFS_BEGIN(node, next, act_notif) {
                if (act_notif->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) {
                    break;
                }
                LY_TREE_DFS_END(node, next, act_notif);
            }
        }
        if (lyd_defaults_add_unres(&node, options, ctx, NULL, 0, data_tree, act_notif, unres, 0)) {
            lyd_free_withsiblings(node);
            node = NULL;
            goto finish;
        }
    }

finish:
    free(lybs.written);
    free(lybs.position);
    free(lybs.inner_chunks);
    free(lybs.models);
    if (unres) {
        free(unres->node);
        free(unres->type);
        free(unres);
    }

    if (parsed) {
        *parsed = r;
    }
    return node;
}
