diff --git a/src/printer_sid.c b/src/printer_sid.c index 384c3ee10..a8eff1db3 100644 --- a/src/printer_sid.c +++ b/src/printer_sid.c @@ -50,6 +50,7 @@ struct sid_collect_data { uint64_t count; /**< Number of items collected so far; exceeding @p max, surplus items are only counted. */ uint64_t max; /**< Maximum number of items (capacity of the array). */ struct ly_ctx *ctx; /**< libyang context used for error logging. */ + const struct lys_module *module; /**< Module whose nodes are collected; used to attribute augmented nodes to their defining module. */ }; /** @@ -178,6 +179,13 @@ collect_data_cb(struct lysc_node *node, void *data, ly_bool *UNUSED(dfs_continue return LY_SUCCESS; /* choice/case get no item, but their subtree is still traversed */ } + /* collect only nodes defined by the module being processed; when another + * module's tree is traversed this keeps exactly the nodes augmented in by + * the processed module and skips the target module's own nodes */ + if (node->module != collect_data->module) { + return LY_SUCCESS; + } + path = sid_node_path(node); LY_CHECK_ERR_RET(!path, LOGMEM(collect_data->ctx), LY_EMEM); @@ -343,12 +351,42 @@ sid_collect_items(struct sid_collect_data *callback_data, const struct lys_modul LY_CHECK_RET((rc = sid_item_add(callback_data, "feature", feature->name)), rc); } + /* collect_data_cb attributes nodes to their defining module, so foreign + nodes are skipped whenever another module's tree is traversed below */ + callback_data->module = module; + /* data namespace: walk the entire compiled schema tree depth-first. lysc_module_dfs_full traverses all nodes including RPCs, actions, notifications, input, output, choice and case nodes. sid_node_path() builds the RFC 9595 schema-node-path identifiers (choice/case names omitted). */ LY_CHECK_RET((rc = lysc_module_dfs_full(module, collect_data_cb, callback_data)), rc); + /* data namespace: nodes that this module augments into other modules live in + the target modules' trees (RFC 9595 still assigns them to this module's .sid). + Traverse every context module that lists this module in its augmented_by and + collect the nodes defined here (collect_data_cb filters by defining module). */ + { + const struct lys_module *aug_target; + uint32_t mod_idx = 0; + + while ((aug_target = ly_ctx_get_module_iter(module->ctx, &mod_idx))) { + ly_bool augmented = 0; + + if ((aug_target == module) || !aug_target->compiled) { + continue; + } + LY_ARRAY_FOR(aug_target->augmented_by, i) { + if (aug_target->augmented_by[i] == module) { + augmented = 1; + break; + } + } + if (augmented) { + LY_CHECK_RET((rc = lysc_module_dfs_full(aug_target, collect_data_cb, callback_data)), rc); + } + } + } + /* data namespace: also traverse the data trees of compiled top-level extension instances that define their own data tree outside the standard module trees (rc:yang-data, sx:structure); the top-level data node is obtained from the diff --git a/tests/utests/schema/test_printer_sid.c b/tests/utests/schema/test_printer_sid.c index f380644bd..729991b27 100644 --- a/tests/utests/schema/test_printer_sid.c +++ b/tests/utests/schema/test_printer_sid.c @@ -453,6 +453,78 @@ test_exts(void **state) lyd_free_all(sid_file); } +/** + * @brief Test case: augment coverage. A module's .sid file must include the data + * nodes it augments into another module (RFC 9595), and a module's own .sid must + * not include nodes augmented into it by other modules. + */ +static void +test_augment(void **state) +{ + struct lys_module *base, *aug; + struct lyd_node *sid_file = NULL; + static const char *mod_base = + "module b1 {\n" + " yang-version 1.1;\n" + " namespace \"urn:b1\";\n" + " prefix b1;\n" + " revision 2024-01-01;\n" + " container cont {\n" + " leaf l { type string; }\n" + " }\n" + "}\n"; + static const char *mod_aug = + "module a1 {\n" + " yang-version 1.1;\n" + " namespace \"urn:a1\";\n" + " prefix a1;\n" + " import b1 { prefix b1; }\n" + " revision 2024-01-01;\n" + " augment \"/b1:cont\" {\n" + " leaf x { type string; }\n" + " }\n" + "}\n"; + + UTEST_ADD_MODULE(mod_base, LYS_IN_YANG, NULL, &base); + UTEST_ADD_MODULE(mod_aug, LYS_IN_YANG, NULL, &aug); + assert_non_null(ly_ctx_load_module(_UC->ctx, "ietf-sid-file", NULL, NULL)); + + /* the augmenting module's .sid contains the node it augments in: /b1:cont/a1:x */ + assert_int_equal(LY_SUCCESS, lys_sid_gen(aug, 100, 2, LYS_SID_FILE_UNPUBLISHED, "d", &sid_file)); + assert_non_null(sid_file); + check_json_tree(sid_file, + "{\"ietf-sid-file:sid-file\":{" + "\"module-name\":\"a1\"," + "\"module-revision\":\"2024-01-01\"," + "\"sid-file-status\":\"unpublished\"," + "\"description\":\"d\"," + "\"dependency-revision\":[{\"module-name\":\"b1\",\"module-revision\":\"2024-01-01\"}]," + "\"assignment-range\":[{\"entry-point\":\"100\",\"size\":\"2\"}]," + "\"item\":[" + "{\"namespace\":\"module\",\"identifier\":\"a1\",\"status\":\"unstable\",\"sid\":\"100\"}," + "{\"namespace\":\"data\",\"identifier\":\"/b1:cont/a1:x\",\"status\":\"unstable\",\"sid\":\"101\"}" + "]}}"); + lyd_free_all(sid_file); + sid_file = NULL; + + /* the base module's own .sid must not contain the node augmented in by a1 */ + assert_int_equal(LY_SUCCESS, lys_sid_gen(base, 100, 3, LYS_SID_FILE_UNPUBLISHED, "d", &sid_file)); + assert_non_null(sid_file); + check_json_tree(sid_file, + "{\"ietf-sid-file:sid-file\":{" + "\"module-name\":\"b1\"," + "\"module-revision\":\"2024-01-01\"," + "\"sid-file-status\":\"unpublished\"," + "\"description\":\"d\"," + "\"assignment-range\":[{\"entry-point\":\"100\",\"size\":\"3\"}]," + "\"item\":[" + "{\"namespace\":\"module\",\"identifier\":\"b1\",\"status\":\"unstable\",\"sid\":\"100\"}," + "{\"namespace\":\"data\",\"identifier\":\"/b1:cont\",\"status\":\"unstable\",\"sid\":\"101\"}," + "{\"namespace\":\"data\",\"identifier\":\"/b1:cont/l\",\"status\":\"unstable\",\"sid\":\"102\"}" + "]}}"); + lyd_free_all(sid_file); +} + int main(void) { @@ -461,6 +533,7 @@ main(void) UTEST(test_gen), UTEST(test_flow), UTEST(test_exts), + UTEST(test_augment), }; return cmocka_run_group_tests(tests, NULL, NULL);