_lyd_validate(struct lyd_node **node, struct lyd_node *data_tree, struct ly_ctx *ctx, const struct lys_module **modules,
              int mod_count, struct lyd_difflist **diff, int options)
{
    struct lyd_node *root, *next1, *next2, *iter, *act_notif = NULL;
    int ret = EXIT_FAILURE;
    unsigned int i;
    struct unres_data *unres = NULL;
    const struct lys_module *yanglib_mod;

    unres = calloc(1, sizeof *unres);
    LY_CHECK_ERR_RETURN(!unres, LOGMEM(NULL), EXIT_FAILURE);

    if (diff) {
        unres->store_diff = 1;
        unres->diff = lyd_diff_init_difflist(ctx, &unres->diff_size);
    }

    if ((options & (LYD_OPT_RPC | LYD_OPT_RPCREPLY)) && *node && ((*node)->schema->nodetype != LYS_RPC)) {
        options |= LYD_OPT_ACT_NOTIF;
    }
    if ((options & (LYD_OPT_NOTIF | LYD_OPT_NOTIF_FILTER)) && *node && ((*node)->schema->nodetype != LYS_NOTIF)) {
        options |= LYD_OPT_ACT_NOTIF;
    }

    LY_TREE_FOR_SAFE(*node, next1, root) {
        if (modules) {
            for (i = 0; i < (unsigned)mod_count; ++i) {
                if (lyd_node_module(root) == modules[i]) {
                    break;
                }
            }
            if (i == (unsigned)mod_count) {
                /* skip data that should not be validated */
                continue;
            }
        }

        LY_TREE_DFS_BEGIN(root, next2, iter) {
            if (iter->parent && (iter->schema->nodetype & (LYS_ACTION | LYS_NOTIF))) {
                if (!(options & LYD_OPT_ACT_NOTIF) || act_notif) {
                    LOGVAL(ctx, LYE_INELEM, LY_VLOG_LYD, iter, iter->schema->name);
                    LOGVAL(ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Unexpected %s node \"%s\".",
                           (options & LYD_OPT_RPC ? "action" : "notification"), iter->schema->name);
                    goto cleanup;
                }
                act_notif = iter;
            }

            if (lyv_data_context(iter, options, unres) || lyv_data_content(iter, options, unres)) {
                goto cleanup;
            }

            /* basic validation successful */
            iter->validity &= ~LYD_VAL_MAND;

            /* empty non-default, non-presence container without attributes, make it default */
            if (!iter->dflt && (iter->schema->nodetype == LYS_CONTAINER) && !iter->child
                        && !((struct lys_node_container *)iter->schema)->presence && !iter->attr) {
                iter->dflt = 1;
            }

            LY_TREE_DFS_END(root, next2, iter);
        }

        if (options & LYD_OPT_NOSIBLINGS) {
            break;
        }

    }

    if (options & LYD_OPT_ACT_NOTIF) {
        if (!act_notif) {
            LOGVAL(ctx, LYE_MISSELEM, LY_VLOG_LYD, *node, (options & LYD_OPT_RPC ? "action" : "notification"), (*node)->schema->name);
            goto cleanup;
        }
        options &= ~LYD_OPT_ACT_NOTIF;
    }

    if (*node) {
        /* check for uniqueness of top-level lists/leaflists because
         * only the inner instances were tested in lyv_data_content() */
        yanglib_mod = ly_ctx_get_module(ctx ? ctx : (*node)->schema->module->ctx, "ietf-yang-library", NULL, 1);
        LY_TREE_FOR(*node, root) {
            if ((options & LYD_OPT_DATA_ADD_YANGLIB) && yanglib_mod && (root->schema->module == yanglib_mod)) {
                /* ietf-yang-library data present, so ignore the option to add them */
                options &= ~LYD_OPT_DATA_ADD_YANGLIB;
            }

            if (!(root->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) || !(root->validity & LYD_VAL_DUP)) {
                continue;
            }

            if (options & LYD_OPT_TRUSTED) {
                /* just clear the flag */
                root->validity &= ~LYD_VAL_DUP;
                continue;
            }

            if (lyv_data_dup(root, *node)) {
                goto cleanup;
            }
        }
    }

    /* add missing ietf-yang-library if requested */
    if (options & LYD_OPT_DATA_ADD_YANGLIB) {
        if (!(*node)) {
            (*node) = ly_ctx_info(ctx);
        } else if (lyd_merge((*node), ly_ctx_info(ctx), LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) {
            LOGERR(ctx, LY_EINT, "Adding ietf-yang-library data failed.");
            goto cleanup;
        }
    }

    /* add default values, resolve unres and check for mandatory nodes in final tree */
    if (lyd_defaults_add_unres(node, options, ctx, modules, mod_count, data_tree, act_notif, unres, 1)) {
        goto cleanup;
    }
    if (act_notif) {
        if (lyd_check_mandatory_tree(act_notif, ctx, modules, mod_count, options)) {
            goto cleanup;
        }
    } else {
        if (lyd_check_mandatory_tree(*node, ctx, modules, mod_count, options)) {
            goto cleanup;
        }
    }

    /* consolidate diff if created */
    if (diff) {
        assert(unres->store_diff);

        for (i = 0; i < unres->diff_idx; ++i) {
            if (unres->diff->type[i] == LYD_DIFF_CREATED) {
                if (unres->diff->second[i]->parent) {
                    unres->diff->first[i] = (struct lyd_node *)lyd_path(unres->diff->second[i]->parent);
                }
                unres->diff->second[i] = lyd_dup(unres->diff->second[i], LYD_DUP_OPT_RECURSIVE);
            }
        }

        *diff = unres->diff;
        unres->diff = 0;
        unres->diff_idx = 0;
    }

    ret = EXIT_SUCCESS;

cleanup:
    if (unres) {
        free(unres->node);
        free(unres->type);
        for (i = 0; i < unres->diff_idx; ++i) {
            if (unres->diff->type[i] == LYD_DIFF_DELETED) {
                lyd_free_withsiblings(unres->diff->first[i]);
                free(unres->diff->second[i]);
            }
        }
        lyd_free_diff(unres->diff);
        free(unres);
    }

    return ret;
}