From 4c95c2286dc0714f5c9bc1d999ae75c9d4c81268 Mon Sep 17 00:00:00 2001 From: Humblesaw Date: Thu, 8 Oct 2026 13:33:22 +0200 Subject: [PATCH] union NEW store value for a specific member type index New API helper which stores a union value member for a given type index and ensures the value type does not change during validation. --- src/plugins_types.h | 37 ++++++ src/plugins_types/union.c | 220 +++++++++++++++++++++------------ tests/utests/types/union.c | 242 +++++++++++++++++++++++++++++++++++++ 3 files changed, 425 insertions(+), 74 deletions(-) diff --git a/src/plugins_types.h b/src/plugins_types.h index 57b2e418e..74093d7bc 100644 --- a/src/plugins_types.h +++ b/src/plugins_types.h @@ -445,6 +445,43 @@ LIBYANG_API_DECL LY_ERR lyplg_type_print_val(const struct lysc_node *node, const */ LIBYANG_API_DECL void lyplg_type_lypath_free(struct ly_path *path); +/** + * @brief Store a union value with a specific member type index, keeping that member resolved (pinned), + * so it is not re-resolved to the first matching member during later validation. + * + * Equivalent to storing the value through the member type plugin and then the union type plugin with + * the value in the LYB format, except the caller does not need to handle the binary LYB format at all. + * Meaning of the parameters follows ::lyplg_type_store_clb unless stated otherwise. + * + * Suitable for datastore plugins that persist the resolved member type index of union values and need + * to restore the values resolved as exactly the same member (e.g. when several member types share the + * same base type). The resulting value is identical to a value stored from the LYB format. + * + * @param[in] ctx libyang context. + * @param[in] type Union schema type, MUST be a union (basetype ::LY_TYPE_UNION). + * @param[in] type_idx Index of the union member type to use (into ::lysc_type_union::types). + * @param[in] value Value to store, is always consumed. + * @param[in] value_size_bits Size of @p value in bits. + * @param[in] options [Type plugin store options](@ref plugintypestoreopts), may be updated by the member store. + * @param[in] format Input format of @p value, even ::LY_VALUE_LYB of the member type value itself (the + * 1-byte member index prefix is handled internally and must not be included). + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). + * @param[in] hints Bitmap of value hints. + * @param[in] ctx_node The @p value schema context node. + * @param[in,out] storage Value storage to fill identically to ::lyplg_type_store_clb, on failure it is + * freeded and MUST NOT be freed again. + * @param[in,out] unres Global unres structure for newly implemented modules. + * @param[out] err Pointer to store the error information provided in case of failure. + * @return LY_SUCCESS on success, + * @return LY_EINCOMPLETE in case the value requires validation of its instance in the data tree, + * @return LY_EVALID in/out-of-range @p type_idx or if the member type does not accept the @p value, + * @return LY_ERR value on other error. + */ +LIBYANG_API_DECL LY_ERR lyplg_type_store_union_idx(const struct ly_ctx *ctx, const struct lysc_type *type, + uint32_t type_idx, const void *value, uint64_t value_size_bits, uint32_t options, LY_VALUE_FORMAT format, + void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, struct lyd_value *storage, + struct lys_glob_unres *unres, struct ly_err_item **err); + /** * @brief Print xpath1.0 value in the specific format. * diff --git a/src/plugins_types/union.c b/src/plugins_types/union.c index c5e03468a..09561cfec 100644 --- a/src/plugins_types/union.c +++ b/src/plugins_types/union.c @@ -153,6 +153,51 @@ lyb_parse_union(const void *lyb_data, uint64_t lyb_data_size_bits, uint32_t *typ } } +/** + * @brief Create LYB binary union data for a value of a given member type: the pinned member type + * index followed by the binary member type value. + * + * @param[in] ctx libyang context. + * @param[in] type_idx Index of the member type of @p member_value, is pinned in the binary data. + * @param[in] member_value Stored member type value, is printed to the LYB format. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). + * @param[out] lyb_size_bits Size of the returned binary data in bits. + * @return Allocated binary data of @p *lyb_size_bits size, NULL on error. The caller is responsible + * for freeing the returned data. + */ +static void * +lyb_union_blob_create(const struct ly_ctx *ctx, uint32_t type_idx, const struct lyd_value *member_value, + void *prefix_data, uint64_t *lyb_size_bits) +{ + void *lyb; + const void *pval; + uint64_t pval_size_bits; + uint32_t num; + ly_bool dynamic; + + /* learn the member value in its binary format */ + pval = LYSC_GET_TYPE_PLG(member_value->realtype->plugin_ref)->print(ctx, member_value, LY_VALUE_LYB, + prefix_data, &dynamic, &pval_size_bits); + if (!pval) { + return NULL; + } + + /* create the binary data of the pinned member type index followed by its binary value + * (the size may not be an exact number of bytes, e.g. for integers) */ + *lyb_size_bits = LYPLG_UNION_TYPE_IDX_SIZE * 8 + pval_size_bits; + lyb = calloc(1, LYPLG_BITS2BYTES(*lyb_size_bits)); + if (lyb) { + num = htole32(type_idx); + memcpy(lyb, &num, LYPLG_UNION_TYPE_IDX_SIZE); + memcpy((char *)lyb + LYPLG_UNION_TYPE_IDX_SIZE, pval, LYPLG_BITS2BYTES(pval_size_bits)); + } + + if (dynamic) { + free((void *)pval); + } + return lyb; +} + /** * @brief For leafref failures, ensure the appropriate error is propagated, not a type validation failure. * @@ -410,54 +455,47 @@ union_find_type(const struct ly_ctx *ctx, struct lysc_type_union *type_u, struct } /** - * @brief Fill union subvalue items: original, origin_len, format prefix_data and call 'store' function for value. + * @brief Prepare a union value storage for storing a value: initialize the storage, assign the caller + * value as the union subvalue original and derive its format-specific data. * * @param[in] ctx libyang context. - * @param[in] type_u Compiled type of union. - * @param[in] lyb_data Input LYB data consisting of index followed by value (lyb_value). - * @param[in] lyb_data_size_bits Size of @p lyb_data in bits. + * @param[in] type The union schema type. + * @param[in] value Value to store, is always consumed. + * @param[in] value_size_bits Size of @p value in bits. + * @param[in,out] options Store options; may be updated by the member store. + * @param[in] format Format of @p value. * @param[in] prefix_data Format-specific data for resolving any prefixes (see ly_resolve_prefix()). - * @param[in,out] subvalue Union subvalue to be filled. - * @param[in,out] options Option containing LYPLG_TYPE_STORE_DYNAMIC. - * @param[in,out] unres Global unres structure for newly implemented modules. - * @param[out] err Error information on error. + * @param[in] hints Bitmap of value hints. + * @param[in] ctx_node Context node for prefix resolution. + * @param[in,out] storage Value storage to fill. + * @param[out] subvalue Prepared union subvalue. * @return LY_ERR value. */ static LY_ERR -lyb_fill_subvalue(const struct ly_ctx *ctx, struct lysc_type_union *type_u, const void *lyb_data, uint64_t lyb_data_size_bits, - void *prefix_data, struct lyd_value_union *subvalue, uint32_t *options, struct lys_glob_unres *unres, - struct ly_err_item **err) +union_prepare_subvalue(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, + uint64_t value_size_bits, uint32_t *options, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + const struct lysc_node *ctx_node, struct lyd_value *storage, struct lyd_value_union **subvalue) { LY_ERR ret; - uint64_t lyb_value_size_bits = 0; - uint32_t type_idx; - const void *lyb_value = NULL; - ret = lyb_union_validate(lyb_data, lyb_data_size_bits, type_u, err); - LY_CHECK_RET(ret); + /* init storage */ + memset(storage, 0, sizeof *storage); + LYPLG_TYPE_VAL_INLINE_PREPARE(storage, *subvalue); + LY_CHECK_ERR_RET(!*subvalue, ret = LY_EMEM, ret); - /* parse lyb_data and set the lyb_value and lyb_value_size_bits */ - lyb_parse_union(lyb_data, lyb_data_size_bits, &type_idx, &lyb_value, &lyb_value_size_bits); + storage->realtype = type; + (*subvalue)->hints = hints; + (*subvalue)->ctx_node = ctx_node; - /* store lyb_data to subvalue */ - ret = union_subvalue_assignment(lyb_data, lyb_data_size_bits, &subvalue->original, &subvalue->orig_size_bits, options); + /* store the caller value as the subvalue original, it is needed for the member type store */ + ret = union_subvalue_assignment(value, value_size_bits, &(*subvalue)->original, &(*subvalue)->orig_size_bits, + options); LY_CHECK_RET(ret); - if (lyb_value) { - /* resolve prefix_data and set format */ - ret = lyplg_type_prefix_data_new(ctx, lyb_value, LYPLG_BITS2BYTES(lyb_value_size_bits), LY_VALUE_LYB, - prefix_data, &subvalue->format, &subvalue->prefix_data); - LY_CHECK_RET(ret); - assert(subvalue->format == LY_VALUE_LYB); - } else { - /* lyb_parse_union() did not find lyb_value, just set format */ - subvalue->format = LY_VALUE_LYB; - } - - /* use the specific type to store the value */ - ret = union_store_type(ctx, type_u, type_idx, subvalue, *options, 0, NULL, NULL, unres, err); - - return ret; + /* derive format-specific data of the value, needed for the member type store; + * the values in the LYB format must have no prefix data stored (see ly_dup_prefix_data()) */ + return lyplg_type_prefix_data_new(ctx, value, LYPLG_BITS2BYTES(value_size_bits), format, + (format == LY_VALUE_LYB) ? NULL : prefix_data, &(*subvalue)->format, &(*subvalue)->prefix_data); } static LY_ERR @@ -468,30 +506,25 @@ lyplg_type_store_union(const struct ly_ctx *ctx, const struct lysc_type *type, c LY_ERR ret = LY_SUCCESS, r; struct lysc_type_union *type_u = (struct lysc_type_union *)type; struct lyd_value_union *subvalue; + uint32_t type_idx = 0; *err = NULL; - /* init storage */ - memset(storage, 0, sizeof *storage); - LYPLG_TYPE_VAL_INLINE_PREPARE(storage, subvalue); - LY_CHECK_ERR_GOTO(!subvalue, ret = LY_EMEM, cleanup); - storage->realtype = type; - subvalue->hints = hints; - subvalue->ctx_node = ctx_node; + /* initialize the storage and assign the caller value to the subvalue */ + ret = union_prepare_subvalue(ctx, type, value, value_size_bits, &options, format, prefix_data, hints, + ctx_node, storage, &subvalue); + LY_CHECK_GOTO(ret, cleanup); if (format == LY_VALUE_LYB) { - ret = lyb_fill_subvalue(ctx, type_u, value, value_size_bits, prefix_data, subvalue, &options, unres, err); - LY_CHECK_GOTO((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE), cleanup); - } else { - /* store value to subvalue */ - ret = union_subvalue_assignment(value, value_size_bits, &subvalue->original, &subvalue->orig_size_bits, &options); - LY_CHECK_GOTO(ret, cleanup); - - /* store format-specific data for later prefix resolution */ - ret = lyplg_type_prefix_data_new(ctx, value, LYPLG_BITS2BYTES(value_size_bits), format, prefix_data, - &subvalue->format, &subvalue->prefix_data); + /* the binary value pins the member type to use, validate it and learn its index */ + ret = lyb_union_validate(value, value_size_bits, type_u, err); LY_CHECK_GOTO(ret, cleanup); + lyb_parse_union(value, 0, &type_idx, NULL, NULL); + /* use the pinned member type to store the value, its acceptance errors are propagated */ + ret = union_store_type(ctx, type_u, type_idx, subvalue, options, 0, NULL, NULL, unres, err); + LY_CHECK_GOTO((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE), cleanup); + } else { /* use the first usable and valid subtype to store the value */ ret = union_find_type(ctx, type_u, subvalue, options & ~LYPLG_TYPE_STORE_ONLY, 0, NULL, NULL, NULL, unres, err); if (ret && (ret != LY_EINCOMPLETE) && (options & LYPLG_TYPE_STORE_ONLY)) { @@ -518,6 +551,63 @@ lyplg_type_store_union(const struct ly_ctx *ctx, const struct lysc_type *type, c return ret; } +LIBYANG_API_DEF LY_ERR +lyplg_type_store_union_idx(const struct ly_ctx *ctx, const struct lysc_type *type, uint32_t type_idx, + const void *value, uint64_t value_size_bits, uint32_t options, LY_VALUE_FORMAT format, void *prefix_data, + uint32_t hints, const struct lysc_node *ctx_node, struct lyd_value *storage, struct lys_glob_unres *unres, + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS, r; + struct lysc_type_union *type_u = (struct lysc_type_union *)type; + struct lyd_value_union *subvalue; + void *lyb; + + *err = NULL; + + assert(type->basetype == LY_TYPE_UNION); + + /* initialize the storage and assign the caller value to the subvalue */ + ret = union_prepare_subvalue(ctx, type, value, value_size_bits, &options, format, prefix_data, hints, + ctx_node, storage, &subvalue); + LY_CHECK_GOTO(ret, cleanup); + + if (type_idx >= LYA_COUNT(type_u->types)) { + /* stale member type index of e.g. an older module revision is a data condition, report it */ + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, + "Union member type index %" PRIu32 " out of range of the union type with %" LYA_PRI_COUNT_T + " member type(s).", type_idx, LYA_COUNT(type_u->types)); + goto cleanup; + } + + /* store the value by the selected member type only, its acceptance errors are propagated */ + ret = union_store_type(ctx, type_u, type_idx, subvalue, options, 0, NULL, NULL, unres, err); + LY_CHECK_GOTO((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE), cleanup); + + /* pin the member type by replacing the stored caller value with its binary value */ + lyb = lyb_union_blob_create(ctx, type_idx, &subvalue->value, NULL, &subvalue->orig_size_bits); + LY_CHECK_ERR_GOTO(!lyb, LOGINT(ctx); ret = LY_EINT, cleanup); + + free(subvalue->original); + subvalue->original = lyb; + lyplg_type_prefix_data_free(subvalue->format, subvalue->prefix_data); + subvalue->prefix_data = NULL; + subvalue->format = LY_VALUE_LYB; + + /* store canonical value, if any (use the specific type value) */ + r = lydict_insert(ctx, subvalue->value._canonical, 0, &storage->_canonical); + LY_CHECK_ERR_GOTO(r, ret = r, cleanup); + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if ((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE)) { + lyplg_type_free_union(ctx, storage); + } + return ret; +} + static LY_ERR lyplg_type_validate_tree_union(const struct ly_ctx *ctx, const struct lysc_type *type, const struct lyd_node *ctx_node, const struct lyd_node *tree, struct lyd_value *storage, struct ly_err_item **err) @@ -635,12 +725,9 @@ lyb_union_print(const struct ly_ctx *ctx, struct lysc_type_union *type_u, struct void *ret = NULL; LY_ERR r; struct ly_err_item *err; - uint64_t pval_size_bits; - uint32_t num = 0, type_idx = 0; + uint32_t type_idx = 0; struct lyd_value orig = {0}; struct lyplg_type *subvalue_type_plg; - ly_bool dynamic; - void *pval; if (!ctx) { assert(subvalue->ctx_node); @@ -664,25 +751,10 @@ lyb_union_print(const struct ly_ctx *ctx, struct lysc_type_union *type_u, struct } LY_CHECK_ERR_RET((r != LY_SUCCESS) && (r != LY_EINCOMPLETE), subvalue->value = orig, NULL); - /* print subvalue in LYB format */ - pval = (void *)LYSC_GET_TYPE_PLG(subvalue->value.realtype->plugin_ref)->print(NULL, &subvalue->value, LY_VALUE_LYB, - prefix_data, &dynamic, &pval_size_bits); - LY_CHECK_ERR_RET(!pval, subvalue->value = orig, NULL); - - /* create LYB data */ - *value_size_bits = LYPLG_UNION_TYPE_IDX_SIZE * 8 + pval_size_bits; - ret = malloc(LYPLG_BITS2BYTES(*value_size_bits)); - LY_CHECK_GOTO(!ret, cleanup); + /* create the LYB data of the resolved member type value */ + ret = lyb_union_blob_create(NULL, type_idx, &subvalue->value, prefix_data, value_size_bits); + LY_CHECK_ERR_RET(!ret, subvalue->value = orig, NULL); - num = htole32(type_idx); - memcpy(ret, &num, LYPLG_UNION_TYPE_IDX_SIZE); - memcpy((char *)ret + LYPLG_UNION_TYPE_IDX_SIZE, pval, LYPLG_BITS2BYTES(pval_size_bits)); - - if (dynamic) { - free(pval); - } - -cleanup: /* restore backup (original) value */ LYSC_GET_TYPE_PLG(subvalue->value.realtype->plugin_ref)->free(ctx, &subvalue->value); subvalue->value = orig; diff --git a/tests/utests/types/union.c b/tests/utests/types/union.c index 2990607e2..c0c148c08 100644 --- a/tests/utests/types/union.c +++ b/tests/utests/types/union.c @@ -376,6 +376,246 @@ test_validation_store_only(void **state) lyd_free_tree(root); } +static void +test_store_lyb(void **state) +{ + const char *schema; + struct lys_module *mod; + struct lyd_value value = {0}; + struct lyplg_type *type = NULL; + struct lysc_type *lysc_type; + struct lysc_type_union *type_u; + struct ly_err_item *err = NULL; + const char *printed; + uint64_t printed_bits; + ly_bool dynamic; + /* LYB-pinned values: member type index followed by the member type value (string "50", uint32 50) */ + uint8_t lyb_str[3] = {1, '5', '0'}; + uint8_t lyb_u32[5] = {0, 0x32, 0, 0, 0}; + /* LYB-pinned values of other member types (uint8 30, leafref value 10) */ + uint8_t lyb_u8r[2] = {0, 30}; + uint8_t lyb_lref[2] = {0, 10}; + + schema = MODULE_CREATE_YANG("lybs", + "leaf l {type union {type uint32; type string; type int8;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data)->type; + type = lysc_get_type_plugin(lysc_type->plugin_ref); + assert_string_equal("ly2 union", type->id); + + /* a value pinned to a member type that the first matching member would accept, too, keeps its member */ + err = NULL; + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, lyb_str, sizeof lyb_str * 8, + 0, LY_VALUE_LYB, NULL, LYD_VALHINT_STRING, NULL, &value, NULL, &err)); + assert_null(err); + assert_ptr_equal(lysc_type, value.realtype); + assert_int_equal(LY_VALUE_LYB, value.subvalue->format); + assert_int_equal(LY_TYPE_STRING, value.subvalue->value.realtype->basetype); + assert_string_equal("50", lyd_value_get_canonical(UTEST_LYCTX, &value)); + + /* the pinned member type is kept through validation (no re-resolution to uint32) */ + err = NULL; + assert_int_equal(LY_SUCCESS, type->validate_tree(UTEST_LYCTX, lysc_type, NULL, NULL, &value, &err)); + assert_null(err); + assert_int_equal(LY_TYPE_STRING, value.subvalue->value.realtype->basetype); + + /* the value is printed by the pinned member type */ + printed = type->print(UTEST_LYCTX, &value, LY_VALUE_JSON, NULL, &dynamic, &printed_bits); + assert_non_null(printed); + assert_string_equal("50", printed); + if (dynamic) { + free((void *)printed); + } + + /* the stored binary value is kept as is, pinning the member type */ + printed = type->print(UTEST_LYCTX, &value, LY_VALUE_LYB, NULL, &dynamic, &printed_bits); + assert_non_null(printed); + assert_int_equal(0, dynamic); + assert_int_equal(sizeof lyb_str * 8, printed_bits); + assert_memory_equal(printed, lyb_str, sizeof lyb_str); + type->free(UTEST_LYCTX, &value); + + /* prefix data are irrelevant for binary values and must not be asserted */ + err = NULL; + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, lyb_str, sizeof lyb_str * 8, + 0, LY_VALUE_LYB, (void *)mod, LYD_VALHINT_STRING, NULL, &value, NULL, &err)); + assert_null(err); + assert_null(value.subvalue->prefix_data); + type->free(UTEST_LYCTX, &value); + + /* two members with the same basetype are still distinguished by the pinned index */ + schema = MODULE_CREATE_YANG("lybs2", + "leaf l {type union {type uint32; type uint32;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data)->type; + type_u = (struct lysc_type_union *)lysc_type; + + err = NULL; + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, lyb_u32, sizeof lyb_u32 * 8, + 0, LY_VALUE_LYB, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_null(err); + assert_ptr_equal(type_u->types[0], value.subvalue->value.realtype); + type->free(UTEST_LYCTX, &value); + + err = NULL; + lyb_u32[0] = 1; + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type, lyb_u32, sizeof lyb_u32 * 8, + 0, LY_VALUE_LYB, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_null(err); + assert_ptr_equal(type_u->types[1], value.subvalue->value.realtype); + type->free(UTEST_LYCTX, &value); + + /* an out-of-bounds pinned member type index is a data error, not a crash */ + err = NULL; + lyb_u32[0] = 2; + assert_int_equal(LY_EVALID, type->store(UTEST_LYCTX, lysc_type, lyb_u32, sizeof lyb_u32 * 8, + 0, LY_VALUE_LYB, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_non_null(err); + CHECK_STRING(err->msg, "Invalid LYB union type index 2 (type count 2)."); + ly_err_free(err); + + /* a too short binary value is a data error, too */ + err = NULL; + assert_int_equal(LY_EVALID, type->store(UTEST_LYCTX, lysc_type, "", 0, + 0, LY_VALUE_LYB, NULL, 0, NULL, &value, NULL, &err)); + assert_non_null(err); + CHECK_STRING(err->msg, "Invalid LYB union value size 0 b (expected at least 8 b)."); + ly_err_free(err); + + /* member type rejection is propagated */ + schema = MODULE_CREATE_YANG("lybs3", + "leaf l {type union {type uint8 {range 10..20;} type string;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data)->type; + + err = NULL; + assert_int_equal(LY_EVALID, type->store(UTEST_LYCTX, lysc_type, lyb_u8r, sizeof lyb_u8r * 8, + 0, LY_VALUE_LYB, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_non_null(err); + ly_err_free(err); + + /* pending value validation is propagated, too */ + schema = MODULE_CREATE_YANG("lybs4", + "leaf target {type int8;}" + "leaf l {type union {type leafref {path ../target; require-instance true;} type string;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data->next)->type; + + err = NULL; + assert_int_equal(LY_EINCOMPLETE, type->store(UTEST_LYCTX, lysc_type, lyb_lref, sizeof lyb_lref * 8, + 0, LY_VALUE_LYB, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_int_equal(LY_TYPE_INT8, value.subvalue->value.realtype->basetype); + type->free(UTEST_LYCTX, &value); + ly_err_free(err); +} + +static void +test_store_union_idx(void **state) +{ + const char *schema; + struct lys_module *mod; + struct lyd_value value = {0}; + struct lyplg_type *type = NULL; + struct lysc_type *lysc_type; + struct lysc_type_union *type_u; + struct ly_err_item *err = NULL; + const char *printed; + uint64_t printed_bits; + ly_bool dynamic; + + schema = MODULE_CREATE_YANG("idx", + "leaf l {type union {type uint32; type string;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data)->type; + type = lysc_get_type_plugin(lysc_type->plugin_ref); + type_u = (struct lysc_type_union *)lysc_type; + assert_string_equal("ly2 union", type->id); + + /* a value stored with a member type index keeps that member resolved, without any LYB data needed */ + err = NULL; + assert_int_equal(LY_SUCCESS, lyplg_type_store_union_idx(UTEST_LYCTX, lysc_type, 1, "50", 2 * 8, + 0, LY_VALUE_CANON, NULL, LYD_VALHINT_STRING, NULL, &value, NULL, &err)); + assert_null(err); + assert_ptr_equal(lysc_type, value.realtype); + assert_int_equal(LY_VALUE_LYB, value.subvalue->format); + assert_ptr_equal(type_u->types[1], value.subvalue->value.realtype); + assert_null(value.subvalue->prefix_data); + assert_string_equal("50", lyd_value_get_canonical(UTEST_LYCTX, &value)); + + /* the pinned member type is kept through validation (no re-resolution to uint32) */ + err = NULL; + assert_int_equal(LY_SUCCESS, type->validate_tree(UTEST_LYCTX, lysc_type, NULL, NULL, &value, &err)); + assert_null(err); + assert_ptr_equal(type_u->types[1], value.subvalue->value.realtype); + + /* the value is printed by the pinned member type */ + printed = type->print(UTEST_LYCTX, &value, LY_VALUE_JSON, NULL, &dynamic, &printed_bits); + assert_non_null(printed); + assert_string_equal("50", printed); + if (dynamic) { + free((void *)printed); + } + + /* the internally created binary value pins the member type */ + printed = type->print(UTEST_LYCTX, &value, LY_VALUE_LYB, NULL, &dynamic, &printed_bits); + assert_non_null(printed); + assert_int_equal(0, dynamic); + assert_int_equal(3 * 8, printed_bits); + assert_memory_equal(printed, "\x01" "50", 3); + type->free(UTEST_LYCTX, &value); + + /* two members with the same basetype are still distinguished by the member type index */ + schema = MODULE_CREATE_YANG("idx2", + "leaf l {type union {type uint32; type uint32;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data)->type; + type_u = (struct lysc_type_union *)lysc_type; + + err = NULL; + assert_int_equal(LY_SUCCESS, lyplg_type_store_union_idx(UTEST_LYCTX, lysc_type, 0, "50", 2 * 8, + 0, LY_VALUE_CANON, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_null(err); + assert_ptr_equal(type_u->types[0], value.subvalue->value.realtype); + type->free(UTEST_LYCTX, &value); + + err = NULL; + assert_int_equal(LY_SUCCESS, lyplg_type_store_union_idx(UTEST_LYCTX, lysc_type, 1, "50", 2 * 8, + 0, LY_VALUE_CANON, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_null(err); + assert_ptr_equal(type_u->types[1], value.subvalue->value.realtype); + type->free(UTEST_LYCTX, &value); + + /* an out-of-range or stale member type index is a data error, not an assert/crash */ + err = NULL; + assert_int_equal(LY_EVALID, lyplg_type_store_union_idx(UTEST_LYCTX, lysc_type, 2, "50", 2 * 8, + 0, LY_VALUE_CANON, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_non_null(err); + CHECK_STRING(err->msg, "Union member type index 2 out of range of the union type with 2 member type(s)."); + ly_err_free(err); + + /* member type rejection is propagated */ + err = NULL; + assert_int_equal(LY_EVALID, lyplg_type_store_union_idx(UTEST_LYCTX, lysc_type, 0, "x", 1 * 8, + 0, LY_VALUE_CANON, NULL, LYD_VALHINT_STRING, NULL, &value, NULL, &err)); + assert_non_null(err); + ly_err_free(err); + + /* pending value validation is propagated, too */ + schema = MODULE_CREATE_YANG("idx3", + "leaf target {type int8;}" + "leaf l {type union {type leafref {path ../target; require-instance true;} type string;}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); + lysc_type = ((struct lysc_node_leaf *)mod->compiled->data->next)->type; + + err = NULL; + assert_int_equal(LY_EINCOMPLETE, lyplg_type_store_union_idx(UTEST_LYCTX, lysc_type, 0, "10", 2 * 8, + 0, LY_VALUE_CANON, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err)); + assert_int_equal(LY_TYPE_INT8, value.subvalue->value.realtype->basetype); + assert_int_equal(LY_VALUE_LYB, value.subvalue->format); + type->free(UTEST_LYCTX, &value); + ly_err_free(err); +} + int main(void) { @@ -386,6 +626,8 @@ main(void) UTEST(test_plugin_sort), UTEST(test_validation), UTEST(test_validation_store_only), + UTEST(test_store_lyb), + UTEST(test_store_union_idx), }; return cmocka_run_group_tests(tests, NULL, NULL);