From 0b29e9eee4fd1a3350e8aa8de4a0b81543cdc302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 30 Apr 2025 09:24:00 +0200 Subject: [PATCH] Bump sysrepo, netopeer2, libnetconf2, libyang, libyang-cpp, sysrepo-cpp, rousette --- buildroot | 2 +- doc/ChangeLog.md | 10 + ...pport-for-RpcYang-in-Context-parseOp.patch | 4 +- ...-when-lyd_validate_all-returns-error.patch | 4 +- .../0003-remove-a-misleading-comment.patch | 4 +- ...mprove-List-keys-not-to-use-std-move.patch | 4 +- ...f-list-s-default-statement-available.patch | 4 +- ...choice-and-case-statements-available.patch | 4 +- ...term-for-changing-the-value-for-a-te.patch | 4 +- ...-Module-childrenDfs-and-Module-immed.patch | 652 ++ ...topped-reporting-schema-mounts-node.patch} | 4 +- ...in-version-due-to-anyxml-behavior-ch.patch | 39 + ...ngle-place-to-define-package-version.patch | 40 + ...12-YANG-flavored-regular-expressions.patch | 173 + ...ream-changes-in-anyxml-JSON-printing.patch | 67 + ...-DataNode-insertSibling-return-value.patch | 57 + ...15-API-ABI-change-opaque-node-naming.patch | 260 + ...dd-a-helper-for-finding-opaque-nodes.patch | 462 + ...17-build-link-to-libpcre2-explicitly.patch | 56 + .../0018-CI-renamed-project-upstream.patch | 66 + ...the-list-key-values-when-they-differ.patch | 4 +- ...ame-url-encoding-to-percent-encoding.patch | 4 +- ...0003-uri-correct-x3-rule-class-names.patch | 4 +- ...should-work-on-raw-percent-encoded-p.patch | 4 +- ...y-values-checking-must-respect-libya.patch | 4 +- ...-test-querying-lists-with-union-keys.patch | 4 +- ...rror-handling-in-sysrepo-has-changed.patch | 4 +- ...tconf-support-fields-query-parameter.patch | 4 +- .../0009-cmake-adhere-to-CMP0167.patch | 4 +- ...compatibility-with-pam_wrapper-1.1.6.patch | 4 +- .../0011-tests-add-missing-pragma-once.patch | 4 +- ...entRequest-wrappers-interface-and-us.patch | 4 +- ...f-from-the-header-file-into-cpp-file.patch | 4 +- ...ame-datastoreUtils-to-event_watchers.patch | 4 +- ...015-tests-rename-NotificationWatcher.patch | 4 +- ...ts-make-NotificationWatcher-reusable.patch | 4 +- ...per-function-to-construct-server-URI.patch | 4 +- .../0018-tests-make-SSEClient-reusable.patch | 4 +- .../0019-tests-fix-deadlock-in-tests.patch | 4 +- ...ke-as_restconf_notification-reusable.patch | 4 +- ...p.patch => 0021-Update-dependencies.patch} | 25 +- ...2-restconf-add-RAII-datastore-switch.patch | 104 + ...023-restconf-add-missing-pragma-once.patch | 30 + ...f-make-fetching-replay-info-reusable.patch | 108 + ...tream-API-change-NULL-output-on-RPCs.patch | 53 + .../rousette/0026-boost-1.87-support.patch | 82 + ...-refactor-uri-parser-for-stream-URIs.patch | 196 + ...hecking-if-yang-features-are-enabled.patch | 54 + ...0029-tests-refactor-default-handling.patch | 54 + ...ty-containers-in-user-defined-conten.patch | 356 + .../0031-fix-a-typo-in-RFC-number.patch | 31 + .../0032-Fix-bug-in-depth-processing.patch | 104 + ...ibyang-cpp-API-break-in-opaque-nodes.patch | 89 + ...-XML-serialization-when-listing-RPCs.patch | 122 + ...-that-edit-tree-is-the-first-sibling.patch | 39 + ...ce-internal-RPC-handling-for-subscri.patch | 5532 ++++++++++++ ...owaway-sysrepo-sessions-during-start.patch | 31 + ...equests-create-sysrepo-session-later.patch | 59 + .../0039-docs-doctest-has-moved.patch | 31 + ...pture-wrongly-formatted-SSE-messages.patch | 35 + ...ctory-reset-between-individual-secti.patch | 32 + ...y-condition-in-NACM-anonymous-access.patch | 70 + ...pecifying-exec-permissions-for-anony.patch | 140 + ...stconf-add-access-log-to-stream-root.patch | 32 + ...handling-changes-in-upstream-sysrepo.patch | 4 +- ...-message-when-sr_session_start-fails.patch | 4 +- ...0003-We-don-t-support-sysrepo-v3-yet.patch | 4 +- ...-don-t-use-module-as-a-variable-name.patch | 4 +- .../0005-DRY-a-dummy-leaf-XPath.patch | 4 +- ...ngle-place-to-define-package-version.patch | 4 +- ...eak-Change-how-pushed-ops-data-work.patch} | 57 +- ...r_rpc_send_tree-s-output-might-be-NU.patch | 131 + ...pstream-breaking-change-in-sr_get_da.patch | 83 + ...rking-with-existing-sysrepo-discard-.patch | 222 + package/sysrepo-cpp/0011-Fix-a-typo.patch | 36 + ...-wrap-dynamic-subscription-functions.patch | 7598 +++++++++++++++++ ...tering-to-notifications-subscription.patch | 414 + ...anges-to-YANG-push-on-change-wrapper.patch | 187 + .../0015-wrap-sr_nacm_get_user.patch | 84 + .../0016-CI-renamed-project-upstream.patch | 69 + .../0017-wrap-sr_nacm_check_operation.patch | 133 + ...018-Fix-return-value-type-in-doxygen.patch | 50 + ...sion-getter-to-dynamic-subscriptions.patch | 137 + .../0020-wrap-sr_nacm_get_recovery_user.patch | 55 + ...ers-backwards-compat-with-older-mode.patch | 50 - ...as-copy-from-only-in-rpc-copy-config.patch | 8 +- ...002-Do-not-generate-data-in-sysrepo.patch} | 8 +- ...-add-NP-containers-to-push-oper-data.patch | 36 - .../0002-sysrepo-BUGFIX-wrong-param.patch | 30 - ...03-SOVERSION-bump-to-version-7.29.11.patch | 30 - .../0004-VERSION-bump-to-version-3.3.11.patch | 30 - ...enerate-notifications-on-module-chan.patch | 963 --- ...06-SOVERSION-bump-to-version-7.29.12.patch | 30 - .../0007-VERSION-bump-to-version-3.4.0.patch | 32 - ...R-conn_remap_unlock-remove-last-hole.patch | 62 - ...ATE-sess-push-oper-data-module-cache.patch | 250 - ...ACTOR-formatting-and-missing-doxygen.patch | 39 - ...UGFIX-remap-READ-lock-is-enough-3496.patch | 50 - ...OPTIMIZE-avoid-long-ext-lock-holding.patch | 162 - ...se-operation-ID-instead-of-request-I.patch | 1501 ---- ...14-SOVERSION-bump-to-version-7.29.13.patch | 30 - .../0015-VERSION-bump-to-version-3.4.1.patch | 30 - ...dd-support-for-running-in-foregroun.patch} | 8 +- ...E-to-return-any-error-to-sysrepocfg.patch} | 36 +- ...-Allow-to-copy-from-factory-default.patch} | 10 +- ...ysrepoctl-to-install-factory-config.patch} | 16 +- ...-new-log-level-SEC-for-audit-trails.patch} | 8 +- ...il-for-high-priority-system-changes.patch} | 18 +- ...r_shmsub_listen_thread-exit-process.patch} | 10 +- src/confd/yang/libnetconf2.inc | 2 +- src/confd/yang/netopeer2.inc | 2 + src/confd/yang/rousette.inc | 4 +- ...f-subscribed-notifications@2019-11-17.yang | 85 + ...f-subscribed-notifications@2019-09-09.yang | 1350 +++ 114 files changed, 20090 insertions(+), 3485 deletions(-) create mode 100644 package/libyang-cpp/0008-Add-Module-child-Module-childrenDfs-and-Module-immed.patch rename package/libyang-cpp/{0008-upstream-stopped-reporting-schema-mounts-node.patch => 0009-upstream-stopped-reporting-schema-mounts-node.patch} (93%) create mode 100644 package/libyang-cpp/0010-CI-temporarily-pin-version-due-to-anyxml-behavior-ch.patch create mode 100644 package/libyang-cpp/0011-build-a-single-place-to-define-package-version.patch create mode 100644 package/libyang-cpp/0012-YANG-flavored-regular-expressions.patch create mode 100644 package/libyang-cpp/0013-Adapt-to-upstream-changes-in-anyxml-JSON-printing.patch create mode 100644 package/libyang-cpp/0014-fix-DataNode-insertSibling-return-value.patch create mode 100644 package/libyang-cpp/0015-API-ABI-change-opaque-node-naming.patch create mode 100644 package/libyang-cpp/0016-Add-a-helper-for-finding-opaque-nodes.patch create mode 100644 package/libyang-cpp/0017-build-link-to-libpcre2-explicitly.patch create mode 100644 package/libyang-cpp/0018-CI-renamed-project-upstream.patch rename package/rousette/{999-GERRIT-step-up-sysrepo-cpp.patch => 0021-Update-dependencies.patch} (82%) create mode 100644 package/rousette/0022-restconf-add-RAII-datastore-switch.patch create mode 100644 package/rousette/0023-restconf-add-missing-pragma-once.patch create mode 100644 package/rousette/0024-restconf-make-fetching-replay-info-reusable.patch create mode 100644 package/rousette/0025-sysrepo-upstream-API-change-NULL-output-on-RPCs.patch create mode 100644 package/rousette/0026-boost-1.87-support.patch create mode 100644 package/rousette/0027-restconf-refactor-uri-parser-for-stream-URIs.patch create mode 100644 package/rousette/0028-server-prepare-checking-if-yang-features-are-enabled.patch create mode 100644 package/rousette/0029-tests-refactor-default-handling.patch create mode 100644 package/rousette/0030-Always-print-empty-containers-in-user-defined-conten.patch create mode 100644 package/rousette/0031-fix-a-typo-in-RFC-number.patch create mode 100644 package/rousette/0032-Fix-bug-in-depth-processing.patch create mode 100644 package/rousette/0033-Adapt-to-libyang-cpp-API-break-in-opaque-nodes.patch create mode 100644 package/rousette/0034-Fix-XML-serialization-when-listing-RPCs.patch create mode 100644 package/rousette/0035-make-sure-that-edit-tree-is-the-first-sibling.patch create mode 100644 package/rousette/0036-restconf-introduce-internal-RPC-handling-for-subscri.patch create mode 100644 package/rousette/0037-don-t-create-throwaway-sysrepo-sessions-during-start.patch create mode 100644 package/rousette/0038-requests-create-sysrepo-session-later.patch create mode 100644 package/rousette/0039-docs-doctest-has-moved.patch create mode 100644 package/rousette/0040-tests-capture-wrongly-formatted-SSE-messages.patch create mode 100644 package/rousette/0041-tests-perform-factory-reset-between-individual-secti.patch create mode 100644 package/rousette/0042-restconf-simplify-condition-in-NACM-anonymous-access.patch create mode 100644 package/rousette/0043-restconf-allow-specifying-exec-permissions-for-anony.patch create mode 100644 package/rousette/0044-restconf-add-access-log-to-stream-root.patch rename package/sysrepo-cpp/{9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch => 0007-API-ABI-break-Change-how-pushed-ops-data-work.patch} (90%) create mode 100644 package/sysrepo-cpp/0008-upstream-break-sr_rpc_send_tree-s-output-might-be-NU.patch create mode 100644 package/sysrepo-cpp/0009-tests-adapt-to-upstream-breaking-change-in-sr_get_da.patch create mode 100644 package/sysrepo-cpp/0010-Utilities-for-working-with-existing-sysrepo-discard-.patch create mode 100644 package/sysrepo-cpp/0011-Fix-a-typo.patch create mode 100644 package/sysrepo-cpp/0012-wrap-dynamic-subscription-functions.patch create mode 100644 package/sysrepo-cpp/0013-add-subtree-filtering-to-notifications-subscription.patch create mode 100644 package/sysrepo-cpp/0014-add-excluded-changes-to-YANG-push-on-change-wrapper.patch create mode 100644 package/sysrepo-cpp/0015-wrap-sr_nacm_get_user.patch create mode 100644 package/sysrepo-cpp/0016-CI-renamed-project-upstream.patch create mode 100644 package/sysrepo-cpp/0017-wrap-sr_nacm_check_operation.patch create mode 100644 package/sysrepo-cpp/0018-Fix-return-value-type-in-doxygen.patch create mode 100644 package/sysrepo-cpp/0019-add-a-session-getter-to-dynamic-subscriptions.patch create mode 100644 package/sysrepo-cpp/0020-wrap-sr_nacm_get_recovery_user.patch delete mode 100644 patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch rename patches/netopeer2/{2.2.35 => 2.4.1}/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch (76%) rename patches/netopeer2/{2.2.35/0003-Do-not-generate-data-in-sysrepo.patch => 2.4.1/0002-Do-not-generate-data-in-sysrepo.patch} (93%) delete mode 100644 patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch delete mode 100644 patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch delete mode 100644 patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch delete mode 100644 patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch delete mode 100644 patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch delete mode 100644 patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch delete mode 100644 patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch delete mode 100644 patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch delete mode 100644 patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch delete mode 100644 patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch delete mode 100644 patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch delete mode 100644 patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch delete mode 100644 patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch delete mode 100644 patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch delete mode 100644 patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch rename patches/sysrepo/{3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch => 3.6.11/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch} (92%) rename patches/sysrepo/{3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch => 3.6.11/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch} (87%) rename patches/sysrepo/{3.3.10/0018-Allow-to-copy-from-factory-default.patch => 3.6.11/0003-Allow-to-copy-from-factory-default.patch} (78%) rename patches/sysrepo/{3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch => 3.6.11/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch} (93%) rename patches/sysrepo/{3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch => 3.6.11/0005-Introduce-new-log-level-SEC-for-audit-trails.patch} (95%) rename patches/sysrepo/{3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch => 3.6.11/0006-Add-audit-trail-for-high-priority-system-changes.patch} (84%) rename patches/sysrepo/{3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch => 3.6.11/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch} (77%) create mode 100644 src/confd/yang/rousette/ietf-restconf-subscribed-notifications@2019-11-17.yang create mode 100644 src/confd/yang/rousette/ietf-subscribed-notifications@2019-09-09.yang diff --git a/buildroot b/buildroot index 74fc86b0..33d2ca71 160000 --- a/buildroot +++ b/buildroot @@ -1 +1 @@ -Subproject commit 74fc86b09970288bf5610831ffb11a1dd2cf446d +Subproject commit 33d2ca716fa9fdc4d4b1a187d68190f1d36ab60d diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 3830f432..00cb1be8 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -3,6 +3,15 @@ Change Log All notable changes to the project are documented in this file. +[v25.05.0][UNRELEASED] +------------------------- +### Changes +- Upgrade libyang to 3.12.2 +- Upgrade sysrepo to 3.6.11 +- Upgrade netopeer2 (NETCONF) to 2.4.1 + +### Fixes + [v25.04.0][] - 2025-04-30 ------------------------- @@ -1547,6 +1556,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer: [buildroot]: https://buildroot.org/ [UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.04.0...HEAD +[v25.05.0]: https://github.com/kernelkit/infix/compare/v25.04.0...v25.05.0 [v25.04.0]: https://github.com/kernelkit/infix/compare/v25.03.0...v25.04.0 [v25.03.0]: https://github.com/kernelkit/infix/compare/v25.02.0...v25.03.0 [v25.02.0]: https://github.com/kernelkit/infix/compare/v25.01.0...v25.02.0 diff --git a/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch b/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch index 67129032..e654e938 100644 --- a/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch +++ b/package/libyang-cpp/0001-added-support-for-RpcYang-in-Context-parseOp.patch @@ -1,11 +1,11 @@ From d0f6422fee7a46fcb7445c88f499f61b3eb0ead0 Mon Sep 17 00:00:00 2001 From: Adam Piecek Date: Wed, 23 Oct 2024 14:37:09 +0200 -Subject: [PATCH 1/8] added support for RpcYang in Context::parseOp +Subject: [PATCH 01/18] added support for RpcYang in Context::parseOp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Change-Id: I25182ea2d042be1e6e4246e18aee260cc032e547 Signed-off-by: Mattias Walström diff --git a/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch b/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch index c676b814..949da45a 100644 --- a/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch +++ b/package/libyang-cpp/0002-throw-when-lyd_validate_all-returns-error.patch @@ -1,11 +1,11 @@ From 7e015f3486bdbb54f1dcc2e2ce51102b1d623081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Wed, 23 Oct 2024 12:52:24 +0200 -Subject: [PATCH 2/8] throw when lyd_validate_all returns error +Subject: [PATCH 02/18] throw when lyd_validate_all returns error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Bug: https://github.com/CESNET/libyang-cpp/issues/20 Change-Id: I005a2f1b057978573a4046e7b4cc31d77e36fde3 diff --git a/package/libyang-cpp/0003-remove-a-misleading-comment.patch b/package/libyang-cpp/0003-remove-a-misleading-comment.patch index d7768a25..668666fa 100644 --- a/package/libyang-cpp/0003-remove-a-misleading-comment.patch +++ b/package/libyang-cpp/0003-remove-a-misleading-comment.patch @@ -1,11 +1,11 @@ From 490d8bb242d33213b948485f5b94c55e22cf86a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 21 Nov 2024 11:32:44 +0100 -Subject: [PATCH 3/8] remove a misleading comment +Subject: [PATCH 03/18] remove a misleading comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires The whole intention within action's input/output handling here was to put some emphasis on the fact that we aren't tracking the input/output diff --git a/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch b/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch index f29d1c07..58e57672 100644 --- a/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch +++ b/package/libyang-cpp/0004-schema-improve-List-keys-not-to-use-std-move.patch @@ -1,11 +1,11 @@ From e1b17386cf61048d2fe27fffb3b763981a225f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= Date: Wed, 27 Nov 2024 09:47:47 +0100 -Subject: [PATCH 4/8] schema: improve `List::keys()` not to use `std::move` +Subject: [PATCH 04/18] schema: improve `List::keys()` not to use `std::move` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires `List::keys()` used `std::move` while iterating over array of leafs. This was solved without using `std::move`. diff --git a/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch b/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch index dd5b0a8c..9839dd9d 100644 --- a/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch +++ b/package/libyang-cpp/0005-schema-make-leaf-list-s-default-statement-available.patch @@ -1,11 +1,11 @@ From 1102ecdcafbc9206f59b383769687e418557838e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= Date: Mon, 25 Nov 2024 15:54:02 +0100 -Subject: [PATCH 5/8] schema: make leaf-list's `default` statement available +Subject: [PATCH 05/18] schema: make leaf-list's `default` statement available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Make leaf-list's `default` statement available so that it can be accessed if end-user requires reading schema nodes. diff --git a/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch b/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch index 797db204..c5c9009f 100644 --- a/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch +++ b/package/libyang-cpp/0006-schema-Make-choice-and-case-statements-available.patch @@ -1,11 +1,11 @@ From 01f2633cef60495d5cafc4b4b1f25273b03ab3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= Date: Tue, 22 Oct 2024 15:11:30 +0200 -Subject: [PATCH 6/8] schema: Make choice and case statements available +Subject: [PATCH 06/18] schema: Make choice and case statements available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Make choice and case statements available so that they can be accessed if end-user requires reading schema nodes. diff --git a/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch b/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch index 20903283..76cb2206 100644 --- a/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch +++ b/package/libyang-cpp/0007-Wrap-lyd_change_term-for-changing-the-value-for-a-te.patch @@ -1,12 +1,12 @@ From a1acdc794facf8cbf113f73274ecebd5898c81a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 17 Dec 2024 15:08:43 +0100 -Subject: [PATCH 7/8] Wrap lyd_change_term for changing the value for a +Subject: [PATCH 07/18] Wrap lyd_change_term for changing the value for a terminal node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Previously, the code would require a newPath(..., libyang::CreationOptions::Update), which is quite a mouthful. diff --git a/package/libyang-cpp/0008-Add-Module-child-Module-childrenDfs-and-Module-immed.patch b/package/libyang-cpp/0008-Add-Module-child-Module-childrenDfs-and-Module-immed.patch new file mode 100644 index 00000000..4ffce00e --- /dev/null +++ b/package/libyang-cpp/0008-Add-Module-child-Module-childrenDfs-and-Module-immed.patch @@ -0,0 +1,652 @@ +From 32b200ed06e9adb44a8d4ce6771f18812a54d06e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= +Date: Wed, 20 Nov 2024 10:20:19 +0100 +Subject: [PATCH 08/18] Add `Module::child()`, `Module::childrenDfs()` and + `Module::immediateChildren()` +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Those functions are implemented in the same manner as in `SchemaNode` +and allows to walk through modules children. This is counterpart to +already implemented `Module::childInstantiables()` that returns +instantiables schema nodes. These return all nodes, including +the schema-only nodes such as choice and case if end-user needs +to read its schema. + +While the implementation is inspired by functions in `SchemaNode`, +imlementation of `Module::parent()` and `Module::siblings()` was omitted +as those do no make sense on `Module`. + +Change-Id: I38c8374304f859d65343d04d08302e07deb05f27 +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/Collection.hpp | 1 + + include/libyang-cpp/Module.hpp | 5 + + src/Module.cpp | 40 +++ + tests/context.cpp | 5 + + tests/example_schema.hpp | 21 ++ + tests/schema_node.cpp | 409 +++++++++++++++++++---------- + 6 files changed, 346 insertions(+), 135 deletions(-) + +diff --git a/include/libyang-cpp/Collection.hpp b/include/libyang-cpp/Collection.hpp +index 557a0a2..4324791 100644 +--- a/include/libyang-cpp/Collection.hpp ++++ b/include/libyang-cpp/Collection.hpp +@@ -98,6 +98,7 @@ class LIBYANG_CPP_EXPORT Collection { + public: + friend DataNode; + friend Iterator; ++ friend Module; + friend SchemaNode; + ~Collection(); + Collection(const Collection&); +diff --git a/include/libyang-cpp/Module.hpp b/include/libyang-cpp/Module.hpp +index f10c36f..ab20d36 100644 +--- a/include/libyang-cpp/Module.hpp ++++ b/include/libyang-cpp/Module.hpp +@@ -34,6 +34,8 @@ class ChildInstanstiables; + class Identity; + class SchemaNode; + class SubmoduleParsed; ++template ++class Collection; + + namespace types { + class IdentityRef; +@@ -86,7 +88,10 @@ public: + + std::vector identities() const; + ++ std::optional child() const; + ChildInstanstiables childInstantiables() const; ++ libyang::Collection childrenDfs() const; ++ Collection immediateChildren() const; + std::vector actionRpcs() const; + + std::string printStr(const SchemaOutputFormat format, const std::optional flags = std::nullopt, std::optional lineLength = std::nullopt) const; +diff --git a/src/Module.cpp b/src/Module.cpp +index 4dc9e3b..d6d4023 100644 +--- a/src/Module.cpp ++++ b/src/Module.cpp +@@ -8,6 +8,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -178,6 +179,23 @@ std::vector Module::identities() const + return res; + } + ++/** ++ * @brief Returns the first child node of this module. ++ * @return The child, or std::nullopt if there are no children. ++ */ ++std::optional Module::child() const ++{ ++ if (!m_module->implemented) { ++ throw Error{"Module::child: module is not implemented"}; ++ } ++ ++ if (!m_module->compiled->data) { ++ return std::nullopt; ++ } ++ ++ return SchemaNode{m_module->compiled->data, m_ctx}; ++} ++ + /** + * @brief Returns a collection of data instantiable top-level nodes of this module. + * +@@ -191,6 +209,28 @@ ChildInstanstiables Module::childInstantiables() const + return ChildInstanstiables{nullptr, m_module->compiled, m_ctx}; + } + ++/** ++ * @brief Returns a collection for iterating depth-first over the subtree this module points to. ++ */ ++Collection Module::childrenDfs() const ++{ ++ if (!m_module->implemented) { ++ throw Error{"Module::childrenDfs: module is not implemented"}; ++ } ++ return Collection{m_module->compiled->data, m_ctx}; ++} ++ ++/** ++ * @brief Returns a collection for iterating over the immediate children of where this module points to. ++ * ++ * This is a convenience function for iterating over this->child().siblings() which does not throw even when module has no children. ++ */ ++Collection Module::immediateChildren() const ++{ ++ auto c = child(); ++ return c ? c->siblings() : Collection{nullptr, nullptr}; ++} ++ + /** + * @brief Returns a collection of RPC nodes (not action nodes) as SchemaNode + * +diff --git a/tests/context.cpp b/tests/context.cpp +index 5929b75..25343db 100644 +--- a/tests/context.cpp ++++ b/tests/context.cpp +@@ -713,6 +713,11 @@ TEST_CASE("context") + +--rw anydataWithMandatoryChild anydata + +--rw anyxmlBasic? anyxml + +--rw anyxmlWithMandatoryChild anyxml ++ +--rw (choiceOnModule)? ++ | +--:(case1) ++ | | +--rw choiceOnModuleLeaf1? string ++ | +--:(case2) ++ | +--rw choiceOnModuleLeaf2? string + +--rw choiceBasicContainer + | +--rw (choiceBasic)? + | +--:(case1) +diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp +index ae3b4de..0d8acb9 100644 +--- a/tests/example_schema.hpp ++++ b/tests/example_schema.hpp +@@ -390,6 +390,19 @@ module type_module { + mandatory true; + } + ++ choice choiceOnModule { ++ case case1 { ++ leaf choiceOnModuleLeaf1 { ++ type string; ++ } ++ } ++ case case2 { ++ leaf choiceOnModuleLeaf2 { ++ type string; ++ } ++ } ++ } ++ + container choiceBasicContainer { + choice choiceBasic { + case case1 { +@@ -787,6 +800,14 @@ module type_module { + } + )"s; + ++const auto empty_module = R"( ++module empty_module { ++ yang-version 1.1; ++ namespace "e"; ++ prefix "e"; ++} ++)"s; ++ + const auto with_inet_types_module = R"( + module with-inet-types { + yang-version 1.1; +diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp +index 8d74bd2..0001377 100644 +--- a/tests/schema_node.cpp ++++ b/tests/schema_node.cpp +@@ -24,8 +24,10 @@ TEST_CASE("SchemaNode") + libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd}; + ctx->parseModule(example_schema, libyang::SchemaFormat::YANG); + ctx->parseModule(type_module, libyang::SchemaFormat::YANG); ++ ctx->parseModule(empty_module, libyang::SchemaFormat::YANG); + ctxWithParsed->parseModule(example_schema, libyang::SchemaFormat::YANG); + ctxWithParsed->parseModule(type_module, libyang::SchemaFormat::YANG); ++ ctxWithParsed->parseModule(empty_module, libyang::SchemaFormat::YANG); + + DOCTEST_SUBCASE("context lifetime") + { +@@ -74,10 +76,34 @@ TEST_CASE("SchemaNode") + REQUIRE(node->schema().path() == "/example-schema:person"); + } + +- DOCTEST_SUBCASE("SchemaNode::child") ++ DOCTEST_SUBCASE("child") + { +- REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first"); +- REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value()); ++ DOCTEST_SUBCASE("implemented module") ++ { ++ DOCTEST_SUBCASE("SchemaNode::child") ++ { ++ REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first"); ++ REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value()); ++ } ++ ++ DOCTEST_SUBCASE("Module::child") ++ { ++ REQUIRE(ctx->getModule("type_module", std::nullopt)->child()->name() == "anydataBasic"); ++ REQUIRE(!ctx->getModule("empty_module", std::nullopt)->child()); ++ } ++ } ++ ++ DOCTEST_SUBCASE("unimplemented module") ++ { ++ DOCTEST_SUBCASE("Module::child") ++ { ++ ctx->setSearchDir(TESTS_DIR / "yang"); ++ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt); ++ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26"); ++ REQUIRE(!modRestconf->implemented()); ++ REQUIRE_THROWS_WITH_AS(modRestconf->child(), "Module::child: module is not implemented", libyang::Error); ++ } ++ } + } + + DOCTEST_SUBCASE("SchemaNode::config") +@@ -160,162 +186,275 @@ TEST_CASE("SchemaNode") + + DOCTEST_SUBCASE("childInstantiables") + { +- std::vector expectedPaths; +- std::optional children; +- +- DOCTEST_SUBCASE("SchemaNode::childInstantiables") ++ DOCTEST_SUBCASE("implemented module") + { +- expectedPaths = { +- "/type_module:listAdvancedWithOneKey/lol", +- "/type_module:listAdvancedWithOneKey/notKey1", +- "/type_module:listAdvancedWithOneKey/notKey2", +- "/type_module:listAdvancedWithOneKey/notKey3", +- "/type_module:listAdvancedWithOneKey/notKey4", +- }; ++ std::vector expectedPaths; ++ std::optional children; ++ ++ DOCTEST_SUBCASE("SchemaNode::childInstantiables") ++ { ++ expectedPaths = { ++ "/type_module:listAdvancedWithOneKey/lol", ++ "/type_module:listAdvancedWithOneKey/notKey1", ++ "/type_module:listAdvancedWithOneKey/notKey2", ++ "/type_module:listAdvancedWithOneKey/notKey3", ++ "/type_module:listAdvancedWithOneKey/notKey4", ++ }; ++ ++ children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables(); ++ } + +- children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables(); +- } ++ DOCTEST_SUBCASE("Module::childInstantiables") ++ { ++ expectedPaths = { ++ "/type_module:anydataBasic", ++ "/type_module:anydataWithMandatoryChild", ++ "/type_module:anyxmlBasic", ++ "/type_module:anyxmlWithMandatoryChild", ++ "/type_module:choiceOnModuleLeaf1", ++ "/type_module:choiceOnModuleLeaf2", ++ "/type_module:choiceBasicContainer", ++ "/type_module:choiceWithMandatoryContainer", ++ "/type_module:choiceWithDefaultContainer", ++ "/type_module:implicitCaseContainer", ++ "/type_module:leafBinary", ++ "/type_module:leafBits", ++ "/type_module:leafEnum", ++ "/type_module:leafEnum2", ++ "/type_module:leafNumber", ++ "/type_module:leafRef", ++ "/type_module:leafRefRelaxed", ++ "/type_module:leafString", ++ "/type_module:leafUnion", ++ "/type_module:meal", ++ "/type_module:leafWithConfigFalse", ++ "/type_module:leafWithDefaultValue", ++ "/type_module:leafWithDescription", ++ "/type_module:leafWithMandatoryTrue", ++ "/type_module:leafWithStatusDeprecated", ++ "/type_module:leafWithStatusObsolete", ++ "/type_module:leafWithUnits", ++ "/type_module:iid-valid", ++ "/type_module:iid-relaxed", ++ "/type_module:leafListBasic", ++ "/type_module:leafListWithDefault", ++ "/type_module:leafListWithMinMaxElements", ++ "/type_module:leafListWithUnits", ++ "/type_module:listBasic", ++ "/type_module:listAdvancedWithOneKey", ++ "/type_module:listAdvancedWithTwoKey", ++ "/type_module:listWithMinMaxElements", ++ "/type_module:numeric", ++ "/type_module:container", ++ "/type_module:containerWithMandatoryChild", ++ }; ++ children = ctx->getModule("type_module", std::nullopt)->childInstantiables(); ++ } + +- DOCTEST_SUBCASE("Module::childInstantiables") +- { +- expectedPaths = { +- "/type_module:anydataBasic", +- "/type_module:anydataWithMandatoryChild", +- "/type_module:anyxmlBasic", +- "/type_module:anyxmlWithMandatoryChild", +- "/type_module:choiceBasicContainer", +- "/type_module:choiceWithMandatoryContainer", +- "/type_module:choiceWithDefaultContainer", +- "/type_module:implicitCaseContainer", +- "/type_module:leafBinary", +- "/type_module:leafBits", +- "/type_module:leafEnum", +- "/type_module:leafEnum2", +- "/type_module:leafNumber", +- "/type_module:leafRef", +- "/type_module:leafRefRelaxed", +- "/type_module:leafString", +- "/type_module:leafUnion", +- "/type_module:meal", +- "/type_module:leafWithConfigFalse", +- "/type_module:leafWithDefaultValue", +- "/type_module:leafWithDescription", +- "/type_module:leafWithMandatoryTrue", +- "/type_module:leafWithStatusDeprecated", +- "/type_module:leafWithStatusObsolete", +- "/type_module:leafWithUnits", +- "/type_module:iid-valid", +- "/type_module:iid-relaxed", +- "/type_module:leafListBasic", +- "/type_module:leafListWithDefault", +- "/type_module:leafListWithMinMaxElements", +- "/type_module:leafListWithUnits", +- "/type_module:listBasic", +- "/type_module:listAdvancedWithOneKey", +- "/type_module:listAdvancedWithTwoKey", +- "/type_module:listWithMinMaxElements", +- "/type_module:numeric", +- "/type_module:container", +- "/type_module:containerWithMandatoryChild", +- }; +- children = ctx->getModule("type_module", std::nullopt)->childInstantiables(); +- } ++ std::vector actualPaths; ++ for (const auto& child : *children) { ++ actualPaths.emplace_back(child.path()); ++ } + +- std::vector actualPaths; +- for (const auto& child : *children) { +- actualPaths.emplace_back(child.path()); ++ REQUIRE(expectedPaths == actualPaths); + } + +- REQUIRE(expectedPaths == actualPaths); ++ DOCTEST_SUBCASE("unimplemented module") ++ { ++ DOCTEST_SUBCASE("Module::childInstantiables") ++ { ++ ctx->setSearchDir(TESTS_DIR / "yang"); ++ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt); ++ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26"); ++ REQUIRE(!modRestconf->implemented()); ++ REQUIRE_THROWS_WITH_AS(modRestconf->childInstantiables(), "Module::childInstantiables: module is not implemented", libyang::Error); ++ } ++ } + } + +- DOCTEST_SUBCASE("SchemaNode::childrenDfs") ++ DOCTEST_SUBCASE("childrenDfs") + { +- std::vector expectedPaths; ++ DOCTEST_SUBCASE("implemented module") ++ { ++ std::vector expectedPaths; ++ std::optional> children; + +- const char* path; ++ DOCTEST_SUBCASE("SchemaNode::childrenDfs") ++ { ++ DOCTEST_SUBCASE("listAdvancedWithTwoKey") ++ { ++ expectedPaths = { ++ "/type_module:listAdvancedWithTwoKey", ++ "/type_module:listAdvancedWithTwoKey/first", ++ "/type_module:listAdvancedWithTwoKey/second", ++ }; ++ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").childrenDfs(); ++ } + +- DOCTEST_SUBCASE("listAdvancedWithTwoKey") +- { +- expectedPaths = { +- "/type_module:listAdvancedWithTwoKey", +- "/type_module:listAdvancedWithTwoKey/first", +- "/type_module:listAdvancedWithTwoKey/second", +- }; ++ DOCTEST_SUBCASE("DFS on a leaf") ++ { ++ expectedPaths = { ++ "/type_module:leafString", ++ }; ++ children = ctx->findPath("/type_module:leafString").childrenDfs(); ++ } ++ } + +- path = "/type_module:listAdvancedWithTwoKey"; +- } ++ DOCTEST_SUBCASE("Module::childrenDfs") ++ { ++ expectedPaths = { ++ "/type_module:anydataBasic", ++ }; ++ children = ctx->getModule("type_module", std::nullopt)->childrenDfs(); ++ } + +- DOCTEST_SUBCASE("DFS on a leaf") +- { +- expectedPaths = { +- "/type_module:leafString", +- }; ++ std::vector actualPaths; ++ for (const auto& it : *children) { ++ actualPaths.emplace_back(it.path()); ++ } + +- path = "/type_module:leafString"; ++ REQUIRE(actualPaths == expectedPaths); + } + +- std::vector actualPaths; +- for (const auto& it : ctx->findPath(path).childrenDfs()) { +- actualPaths.emplace_back(it.path()); ++ DOCTEST_SUBCASE("unimplemented module") ++ { ++ DOCTEST_SUBCASE("Module::childrenDfs") ++ { ++ ctx->setSearchDir(TESTS_DIR / "yang"); ++ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt); ++ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26"); ++ REQUIRE(!modRestconf->implemented()); ++ REQUIRE_THROWS_WITH_AS(modRestconf->childrenDfs(), "Module::childrenDfs: module is not implemented", libyang::Error); ++ } + } +- +- REQUIRE(actualPaths == expectedPaths); + } + +- DOCTEST_SUBCASE("SchemaNode::immediateChildren") ++ DOCTEST_SUBCASE("immediateChildren") + { +- std::vector expectedPaths; +- const char* path; +- DOCTEST_SUBCASE("listAdvancedWithTwoKey") +- { +- expectedPaths = { +- "/type_module:listAdvancedWithTwoKey/first", +- "/type_module:listAdvancedWithTwoKey/second", +- }; +- path = "/type_module:listAdvancedWithTwoKey"; +- } +- DOCTEST_SUBCASE("leaf") ++ DOCTEST_SUBCASE("implemented module") + { +- expectedPaths = { +- }; +- path = "/type_module:leafString"; +- } +- DOCTEST_SUBCASE("no recursion") +- { +- expectedPaths = { +- "/type_module:container/x", +- "/type_module:container/y", +- "/type_module:container/z", +- }; +- path = "/type_module:container"; +- } +- DOCTEST_SUBCASE("empty container") +- { +- expectedPaths = { +- }; +- path = "/type_module:container/y"; +- } +- DOCTEST_SUBCASE("one item") +- { +- expectedPaths = { +- "/type_module:container/z/z1", +- }; +- path = "/type_module:container/z"; ++ std::vector expectedPaths; ++ std::optional> children; ++ ++ DOCTEST_SUBCASE("SchemaNode::immediateChildren") ++ { ++ DOCTEST_SUBCASE("listAdvancedWithTwoKey") ++ { ++ expectedPaths = { ++ "/type_module:listAdvancedWithTwoKey/first", ++ "/type_module:listAdvancedWithTwoKey/second", ++ }; ++ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").immediateChildren(); ++ } ++ DOCTEST_SUBCASE("leaf") ++ { ++ expectedPaths = {}; ++ children = ctx->findPath("/type_module:leafString").immediateChildren(); ++ } ++ DOCTEST_SUBCASE("no recursion") ++ { ++ expectedPaths = { ++ "/type_module:container/x", ++ "/type_module:container/y", ++ "/type_module:container/z", ++ }; ++ children = ctx->findPath("/type_module:container").immediateChildren(); ++ } ++ DOCTEST_SUBCASE("empty container") ++ { ++ expectedPaths = {}; ++ children = ctx->findPath("/type_module:container/y").immediateChildren(); ++ } ++ DOCTEST_SUBCASE("one item") ++ { ++ expectedPaths = { ++ "/type_module:container/z/z1", ++ }; ++ children = ctx->findPath("/type_module:container/z").immediateChildren(); ++ } ++ DOCTEST_SUBCASE("two items") ++ { ++ expectedPaths = { ++ "/type_module:container/x/x1", ++ "/type_module:container/x/x2", ++ }; ++ children = ctx->findPath("/type_module:container/x").immediateChildren(); ++ } ++ } ++ ++ DOCTEST_SUBCASE("Module::immediateChildren") ++ { ++ expectedPaths = { ++ "/type_module:anydataBasic", ++ "/type_module:anydataWithMandatoryChild", ++ "/type_module:anyxmlBasic", ++ "/type_module:anyxmlWithMandatoryChild", ++ // choiceOnModule is a choice, so it doesn't have path "/type_module:choiceOnModule". ++ // This node is tested at the end of the test subcase. ++ "/", ++ "/type_module:choiceBasicContainer", ++ "/type_module:choiceWithMandatoryContainer", ++ "/type_module:choiceWithDefaultContainer", ++ "/type_module:implicitCaseContainer", ++ "/type_module:leafBinary", ++ "/type_module:leafBits", ++ "/type_module:leafEnum", ++ "/type_module:leafEnum2", ++ "/type_module:leafNumber", ++ "/type_module:leafRef", ++ "/type_module:leafRefRelaxed", ++ "/type_module:leafString", ++ "/type_module:leafUnion", ++ "/type_module:meal", ++ "/type_module:leafWithConfigFalse", ++ "/type_module:leafWithDefaultValue", ++ "/type_module:leafWithDescription", ++ "/type_module:leafWithMandatoryTrue", ++ "/type_module:leafWithStatusDeprecated", ++ "/type_module:leafWithStatusObsolete", ++ "/type_module:leafWithUnits", ++ "/type_module:iid-valid", ++ "/type_module:iid-relaxed", ++ "/type_module:leafListBasic", ++ "/type_module:leafListWithDefault", ++ "/type_module:leafListWithMinMaxElements", ++ "/type_module:leafListWithUnits", ++ "/type_module:listBasic", ++ "/type_module:listAdvancedWithOneKey", ++ "/type_module:listAdvancedWithTwoKey", ++ "/type_module:listWithMinMaxElements", ++ "/type_module:numeric", ++ "/type_module:container", ++ "/type_module:containerWithMandatoryChild", ++ }; ++ children = ctx->getModule("type_module", std::nullopt)->immediateChildren(); ++ ++ std::vector actualNames; ++ for (auto it : children.value()) { ++ actualNames.emplace_back(it.name()); ++ } ++ // choiceOnModule is a choice, so it doesn't have path, just name. ++ REQUIRE(actualNames[4] == "choiceOnModule"); ++ } ++ ++ std::vector actualPaths; ++ for (const auto& it : *children) { ++ actualPaths.emplace_back(it.path()); ++ } ++ REQUIRE(actualPaths == expectedPaths); + } +- DOCTEST_SUBCASE("two items") ++ ++ DOCTEST_SUBCASE("unimplemented module") + { +- expectedPaths = { +- "/type_module:container/x/x1", +- "/type_module:container/x/x2", +- }; +- path = "/type_module:container/x"; +- } +- std::vector actualPaths; +- for (const auto& it : ctx->findPath(path).immediateChildren()) { +- actualPaths.emplace_back(it.path()); ++ DOCTEST_SUBCASE("Module::immediateChildren") ++ { ++ ctx->setSearchDir(TESTS_DIR / "yang"); ++ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt); ++ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26"); ++ REQUIRE(!modRestconf->implemented()); ++ REQUIRE_THROWS_WITH_AS(modRestconf->immediateChildren(), "Module::child: module is not implemented", libyang::Error); ++ } + } +- REQUIRE(actualPaths == expectedPaths); + } + + DOCTEST_SUBCASE("SchemaNode::siblings") +-- +2.43.0 + diff --git a/package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch b/package/libyang-cpp/0009-upstream-stopped-reporting-schema-mounts-node.patch similarity index 93% rename from package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch rename to package/libyang-cpp/0009-upstream-stopped-reporting-schema-mounts-node.patch index 2219a568..8689db95 100644 --- a/package/libyang-cpp/0008-upstream-stopped-reporting-schema-mounts-node.patch +++ b/package/libyang-cpp/0009-upstream-stopped-reporting-schema-mounts-node.patch @@ -1,11 +1,11 @@ From 39c7530caa510144c17521278b721ba1e6d8ff40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Thu, 9 Jan 2025 15:31:37 +0100 -Subject: [PATCH 8/8] upstream stopped reporting schema-mounts node +Subject: [PATCH 09/18] upstream stopped reporting schema-mounts node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9 Signed-off-by: Mattias Walström diff --git a/package/libyang-cpp/0010-CI-temporarily-pin-version-due-to-anyxml-behavior-ch.patch b/package/libyang-cpp/0010-CI-temporarily-pin-version-due-to-anyxml-behavior-ch.patch new file mode 100644 index 00000000..b17e4dd7 --- /dev/null +++ b/package/libyang-cpp/0010-CI-temporarily-pin-version-due-to-anyxml-behavior-ch.patch @@ -0,0 +1,39 @@ +From 82c963766ad4e4a802db7be656acbedb640745e9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 30 Jan 2025 16:34:12 +0100 +Subject: [PATCH 10/18] CI: temporarily pin version due to anyxml behavior + changes upstream +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: Id977f4d045098c1b93656c0efb871c9a1b650e2d +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index b41c490..19f0def 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -4,13 +4,13 @@ + - f38-gcc-cover: + required-projects: + - name: github/CESNET/libyang +- override-checkout: devel ++ override-checkout: cesnet/2025-01-29 + - name: github/onqtam/doctest + override-checkout: v2.3.6 + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang +- override-checkout: devel ++ override-checkout: cesnet/2025-01-29 + - name: github/onqtam/doctest + override-checkout: v2.4.11 + - f38-clang-tsan: +-- +2.43.0 + diff --git a/package/libyang-cpp/0011-build-a-single-place-to-define-package-version.patch b/package/libyang-cpp/0011-build-a-single-place-to-define-package-version.patch new file mode 100644 index 00000000..f1761702 --- /dev/null +++ b/package/libyang-cpp/0011-build-a-single-place-to-define-package-version.patch @@ -0,0 +1,40 @@ +From 50a216e35a555961f94a32a71bb2d45ac611d0aa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 29 Jan 2025 22:49:08 +0100 +Subject: [PATCH 11/18] build: a single place to define package version +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I2cd7397895ed4852f852e99b97543dde76eaff8f +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 732f52b..c518ca8 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -21,7 +21,8 @@ add_custom_target(libyang-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-prepare_git_version(LIBYANG_CPP_VERSION "3") ++set(LIBYANG_CPP_PKG_VERSION "3") ++prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION}) + + find_package(Doxygen) + option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${DOXYGEN_FOUND}) +@@ -29,7 +30,6 @@ option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a st + + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET) +-set(LIBYANG_CPP_PKG_VERSION "3") + + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency + find_package(date) +-- +2.43.0 + diff --git a/package/libyang-cpp/0012-YANG-flavored-regular-expressions.patch b/package/libyang-cpp/0012-YANG-flavored-regular-expressions.patch new file mode 100644 index 00000000..75cf753f --- /dev/null +++ b/package/libyang-cpp/0012-YANG-flavored-regular-expressions.patch @@ -0,0 +1,173 @@ +From 1533458346b4f395b1187c646b61bbcb1fddc615 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Mon, 4 Nov 2024 14:52:12 +0100 +Subject: [PATCH 12/18] YANG-flavored regular expressions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I93b2756d0f470585280c076308df3f384bd7765d +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 3 +++ + include/libyang-cpp/Regex.hpp | 28 ++++++++++++++++++++++ + src/Regex.cpp | 45 +++++++++++++++++++++++++++++++++++ + tests/regex.cpp | 30 +++++++++++++++++++++++ + 4 files changed, 106 insertions(+) + create mode 100644 include/libyang-cpp/Regex.hpp + create mode 100644 src/Regex.cpp + create mode 100644 tests/regex.cpp + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c518ca8..512af8c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -54,6 +54,7 @@ add_library(yang-cpp + src/Enum.cpp + src/Collection.cpp + src/Module.cpp ++ src/Regex.cpp + src/SchemaNode.cpp + src/Set.cpp + src/Type.cpp +@@ -83,6 +84,7 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24") + libyang-cpp/Enum.hpp + libyang-cpp/ChildInstantiables.hpp + libyang-cpp/Module.hpp ++ libyang-cpp/Regex.hpp + libyang-cpp/Set.hpp + libyang-cpp/SchemaNode.hpp + libyang-cpp/Time.hpp +@@ -119,6 +121,7 @@ if(BUILD_TESTING) + libyang_cpp_test(schema_node) + libyang_cpp_test(unsafe) + target_link_libraries(test_unsafe PkgConfig::LIBYANG) ++ libyang_cpp_test(regex) + + if(date_FOUND) + add_executable(test_time-stl-hhdate tests/time.cpp) +diff --git a/include/libyang-cpp/Regex.hpp b/include/libyang-cpp/Regex.hpp +new file mode 100644 +index 0000000..31935f2 +--- /dev/null ++++ b/include/libyang-cpp/Regex.hpp +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/ ++ * ++ * Written by Jan Kundrát ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++#pragma once ++#include ++#include ++ ++namespace libyang { ++class Context; ++ ++/** ++ * @brief A regular expression pattern which uses the YANG-flavored regex engine ++ */ ++class LIBYANG_CPP_EXPORT Regex { ++public: ++ Regex(const std::string& pattern); ++ ~Regex(); ++ bool matches(const std::string& input); ++ ++private: ++ void* code; ++}; ++ ++} +diff --git a/src/Regex.cpp b/src/Regex.cpp +new file mode 100644 +index 0000000..a34fcd5 +--- /dev/null ++++ b/src/Regex.cpp +@@ -0,0 +1,45 @@ ++/* ++ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/ ++ * ++ * Written by Jan Kundrát ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++// The following header MUST be included before anything else which "might" use PCRE2 ++// because that library uses the preprocessor to prepare the "correct" versions of symbols. ++#include ++ ++#include ++#include ++#include "utils/exception.hpp" ++ ++#define THE_PCRE2_CODE_P reinterpret_cast(this->code) ++#define THE_PCRE2_CODE_P_P reinterpret_cast(&this->code) ++ ++namespace libyang { ++ ++Regex::Regex(const std::string& pattern) ++ : code(nullptr) ++{ ++ auto res = ly_pattern_compile(nullptr, pattern.c_str(), THE_PCRE2_CODE_P_P); ++ throwIfError(res, ly_last_logmsg()); ++} ++ ++Regex::~Regex() ++{ ++ pcre2_code_free(THE_PCRE2_CODE_P); ++} ++ ++bool Regex::matches(const std::string& input) ++{ ++ auto res = ly_pattern_match(nullptr, nullptr /* we have a precompiled pattern */, input.c_str(), input.size(), THE_PCRE2_CODE_P_P); ++ if (res == LY_SUCCESS) { ++ return true; ++ } else if (res == LY_ENOT) { ++ return false; ++ } else { ++ throwError(res, ly_last_logmsg()); ++ } ++} ++} +diff --git a/tests/regex.cpp b/tests/regex.cpp +new file mode 100644 +index 0000000..7594f43 +--- /dev/null ++++ b/tests/regex.cpp +@@ -0,0 +1,30 @@ ++/* ++ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/ ++ * ++ * Written by Jan Kundrát ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++#include ++#include ++ ++TEST_CASE("regex") ++{ ++ using libyang::Regex; ++ using namespace std::string_literals; ++ ++ REQUIRE_THROWS_WITH_AS(Regex{"\\"}, R"(Regular expression "\" is not valid ("": \ at end of pattern).: LY_EVALID)", libyang::ErrorWithCode); ++ ++ Regex re{"ahoj"}; ++ REQUIRE(re.matches("ahoj")); ++ REQUIRE(!re.matches("cau")); ++ REQUIRE(re.matches("ahoj")); // test repeated calls as well ++ REQUIRE(!re.matches("oj")); ++ REQUIRE(!re.matches("aho")); ++ ++ // Testing runtime errors during pattern *matching* is tricky. There's a limit on backtracking, ++ // so testing a pattern like x+x+y on an obscenely long string of "x" characters *will* do the trick, eventually, ++ // but the PCRE2 library has a default limit of 10M attempts. That's a VERY big number to hit during a test :(. ++} +-- +2.43.0 + diff --git a/package/libyang-cpp/0013-Adapt-to-upstream-changes-in-anyxml-JSON-printing.patch b/package/libyang-cpp/0013-Adapt-to-upstream-changes-in-anyxml-JSON-printing.patch new file mode 100644 index 00000000..2feb3b62 --- /dev/null +++ b/package/libyang-cpp/0013-Adapt-to-upstream-changes-in-anyxml-JSON-printing.patch @@ -0,0 +1,67 @@ +From 8d406728a53c2e77a4fe7393b7e30d42b8f9b9bb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 30 Jan 2025 15:40:22 +0100 +Subject: [PATCH 13/18] Adapt to upstream changes in anyxml JSON printing +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I5f6de28cebc95a446549017c2768b450f4fd6526 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + CMakeLists.txt | 3 ++- + tests/data_node.cpp | 2 +- + 3 files changed, 5 insertions(+), 4 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index 19f0def..b41c490 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -4,13 +4,13 @@ + - f38-gcc-cover: + required-projects: + - name: github/CESNET/libyang +- override-checkout: cesnet/2025-01-29 ++ override-checkout: devel + - name: github/onqtam/doctest + override-checkout: v2.3.6 + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang +- override-checkout: cesnet/2025-01-29 ++ override-checkout: devel + - name: github/onqtam/doctest + override-checkout: v2.4.11 + - f38-clang-tsan: +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 512af8c..a40fd52 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -29,7 +29,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D + option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON) + + find_package(PkgConfig REQUIRED) +-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET) ++# FIXME: it's actually 3.7.12, but that hasn't been released yet ++pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET) + + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency + find_package(date) +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index 45fd6c1..14470dd 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -1568,7 +1568,7 @@ TEST_CASE("Data Node manipulation") + REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) + == R"|({"example-schema:ax":[1,2,3]})|"s); + REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings) +- == R"|()|"s); ++ == R"|()|"s + origJSON + ""); + } + + REQUIRE(!!val); +-- +2.43.0 + diff --git a/package/libyang-cpp/0014-fix-DataNode-insertSibling-return-value.patch b/package/libyang-cpp/0014-fix-DataNode-insertSibling-return-value.patch new file mode 100644 index 00000000..0808ed38 --- /dev/null +++ b/package/libyang-cpp/0014-fix-DataNode-insertSibling-return-value.patch @@ -0,0 +1,57 @@ +From f050e7e4a17ef2e221ca000a544042c33c9541fc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 13 Mar 2025 19:21:26 +0100 +Subject: [PATCH 14/18] fix DataNode::insertSibling() return value +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +When such an insert happens, the C library returns the node which is now +the first sibling among all of the siblings of the node which was used +as a reference during the insert. Our API was also documented this way, +but we were not doing that. + +Reported-by: Irfan +Bug: https://github.com/CESNET/libyang-cpp/issues/29 +Change-Id: Id7f84a31e50212d6e2cbce9ab03a351a3721f767 +Signed-off-by: Mattias Walström +--- + src/DataNode.cpp | 2 +- + tests/data_node.cpp | 7 +++++++ + 2 files changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index 84591e5..b899b18 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -711,7 +711,7 @@ DataNode DataNode::insertSibling(DataNode toInsert) + lyd_insert_sibling(this->m_node, toInsert.m_node, &firstSibling); + }, toInsert.parent() ? OperationScope::JustThisNode : OperationScope::AffectsFollowingSiblings, m_refs); + +- return DataNode{m_node, m_refs}; ++ return DataNode{firstSibling, m_refs}; + } + + /** +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index 14470dd..b6ee455 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -973,6 +973,13 @@ TEST_CASE("Data Node manipulation") + REQUIRE(getNumberOrder() == expected); + } + ++ DOCTEST_SUBCASE("DataNode::insertSibling") ++ { ++ auto node = ctx.newPath("/example-schema:leafUInt8", "10"); ++ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:leafUInt16", "10")).path() == "/example-schema:leafUInt8"); ++ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:dummy", "10")).path() == "/example-schema:dummy"); ++ } ++ + DOCTEST_SUBCASE("DataNode::duplicate") + { + auto root = ctx.parseData(data2, libyang::DataFormat::JSON); +-- +2.43.0 + diff --git a/package/libyang-cpp/0015-API-ABI-change-opaque-node-naming.patch b/package/libyang-cpp/0015-API-ABI-change-opaque-node-naming.patch new file mode 100644 index 00000000..2111e7b5 --- /dev/null +++ b/package/libyang-cpp/0015-API-ABI-change-opaque-node-naming.patch @@ -0,0 +1,260 @@ +From f958af42bf5d9fbd901ed59ebc1359ac0ddcc00f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 14 Mar 2025 11:36:50 +0100 +Subject: [PATCH 15/18] API/ABI change: opaque node naming +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Our C++ API would ignore the "module name or XML prefix", which turns +out to be *the* relevant part when it comes to opaque node naming. The +prefix is, instead, just that string that might have been inherited from +the parent node when parsing the serialized data; it's an optional +thingy which, if not set explicitly, is implicitly inherited. + +Adapt the API for this, and since this *will* break the build, let's +bump the package version. + +Change-Id: I199afe5fa7a571034b744531c63b93b9c656563a +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 5 ++--- + include/libyang-cpp/Context.hpp | 4 ++-- + include/libyang-cpp/DataNode.hpp | 11 ++++++++-- + src/Context.cpp | 32 ++++++++++++++++++++-------- + src/DataNode.cpp | 19 ++++++++++++++--- + tests/data_node.cpp | 36 ++++++++++++++++++++++++++------ + 6 files changed, 82 insertions(+), 25 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index a40fd52..c5fec45 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -21,7 +21,7 @@ add_custom_target(libyang-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-set(LIBYANG_CPP_PKG_VERSION "3") ++set(LIBYANG_CPP_PKG_VERSION "4") + prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION}) + + find_package(Doxygen) +@@ -29,8 +29,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D + option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON) + + find_package(PkgConfig REQUIRED) +-# FIXME: it's actually 3.7.12, but that hasn't been released yet +-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET) ++pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET) + + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency + find_package(date) +diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp +index baa47b3..ca89063 100644 +--- a/include/libyang-cpp/Context.hpp ++++ b/include/libyang-cpp/Context.hpp +@@ -115,8 +115,8 @@ public: + CreatedNodes newPath2(const std::string& path, libyang::JSON json, const std::optional options = std::nullopt) const; + CreatedNodes newPath2(const std::string& path, libyang::XML xml, const std::optional options = std::nullopt) const; + std::optional newExtPath(const ExtensionInstance& ext, const std::string& path, const std::optional& value, const std::optional options = std::nullopt) const; +- std::optional newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional& value) const; +- std::optional newOpaqueXML(const std::string& moduleName, const std::string& name, const std::optional& value) const; ++ std::optional newOpaqueJSON(const OpaqueName& name, const std::optional& value) const; ++ std::optional newOpaqueXML(const OpaqueName& name, const std::optional& value) const; + SchemaNode findPath(const std::string& dataPath, const InputOutputNodes inputOutputNodes = InputOutputNodes::Input) const; + Set findXPath(const std::string& path) const; + +diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp +index 851681b..310b5e3 100644 +--- a/include/libyang-cpp/DataNode.hpp ++++ b/include/libyang-cpp/DataNode.hpp +@@ -225,15 +225,22 @@ private: + }; + + /** +- * @brief Contains a (possibly module-qualified) name of an opaque node. ++ * @brief Contains a name of an opaque node. + * +- * This is generic container of a prefix/module string and a name string. ++ * An opaque node always has a name, and a module (or XML namespace) to which this node belongs. ++ * Sometimes, it also has a prefix. ++ * ++ * If the prefix is set *and* the underlying node is an opaque JSON one, then the prefix must be the same as the "module or namespace" name. ++ * If the underlying node is an opaque XML one, then the XML prefix might be something completely different, and in that case the real fun begins. ++ * Review the libayng C manual, this is something that the C++ wrapper doesn't really have under control. + * + * Wraps `ly_opaq_name`. + */ + struct LIBYANG_CPP_EXPORT OpaqueName { ++ std::string moduleOrNamespace; + std::optional prefix; + std::string name; ++ std::string pretty() const; + }; + + /** +diff --git a/src/Context.cpp b/src/Context.cpp +index 287f8c8..fec2f27 100644 +--- a/src/Context.cpp ++++ b/src/Context.cpp +@@ -378,17 +378,25 @@ std::optional Context::newExtPath(const ExtensionInstance& ext, const + * + * Wraps `lyd_new_opaq`. + * +- * @param moduleName Node module name, used as a prefix as well + * @param name Name of the created node + * @param value JSON data blob, if any + * @return Returns the newly created node (if created) + */ +-std::optional Context::newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional& value) const ++std::optional Context::newOpaqueJSON(const OpaqueName& name, const std::optional& value) const + { ++ if (name.prefix && *name.prefix != name.moduleOrNamespace) { ++ throw Error{"invalid opaque JSON node: prefix \"" + *name.prefix + "\" doesn't match module name \"" + name.moduleOrNamespace + "\""}; ++ } + lyd_node* out; +- auto err = lyd_new_opaq(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, moduleName.c_str(), &out); ++ auto err = lyd_new_opaq(nullptr, ++ m_ctx.get(), ++ name.name.c_str(), ++ value ? value->content.c_str() : nullptr, ++ name.prefix ? name.prefix->c_str() : nullptr, ++ name.moduleOrNamespace.c_str(), ++ &out); + +- throwIfError(err, "Couldn't create an opaque JSON node '"s + moduleName + ':' + name + "'"); ++ throwIfError(err, "Couldn't create an opaque JSON node " + name.pretty()); + + if (out) { + return DataNode{out, std::make_shared(m_ctx)}; +@@ -403,17 +411,23 @@ std::optional Context::newOpaqueJSON(const std::string& moduleName, co + * + * Wraps `lyd_new_opaq2`. + * +- * @param xmlNamespace Node module namespace + * @param name Name of the created node + * @param value XML data blob, if any + * @return Returns the newly created node (if created) + */ +-std::optional Context::newOpaqueXML(const std::string& xmlNamespace, const std::string& name, const std::optional& value) const ++std::optional Context::newOpaqueXML(const OpaqueName& name, const std::optional& value) const + { ++ // the XML node naming is "complex", we cannot really check the XML namespace for sanity here + lyd_node* out; +- auto err = lyd_new_opaq2(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, xmlNamespace.c_str(), &out); +- +- throwIfError(err, "Couldn't create an opaque XML node '"s + name +"' from namespace '" + xmlNamespace + "'"); ++ auto err = lyd_new_opaq2(nullptr, ++ m_ctx.get(), ++ name.name.c_str(), ++ value ? value->content.c_str() : nullptr, ++ name.prefix ? name.prefix->c_str() : nullptr, ++ name.moduleOrNamespace.c_str(), ++ &out); ++ ++ throwIfError(err, "Couldn't create an opaque XML node " + name.pretty()); + + if (out) { + return DataNode{out, std::make_shared(m_ctx)}; +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index b899b18..344f1b6 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -1112,9 +1112,9 @@ OpaqueName DataNodeOpaque::name() const + { + auto opaq = reinterpret_cast(m_node); + return OpaqueName{ +- .prefix = opaq->name.prefix ? std::optional(opaq->name.prefix) : std::nullopt, +- .name = opaq->name.name +- }; ++ .moduleOrNamespace = opaq->name.module_name, ++ .prefix = opaq->name.prefix ? std::optional{opaq->name.prefix} : std::nullopt, ++ .name = opaq->name.name}; + } + + std::string DataNodeOpaque::value() const +@@ -1122,6 +1122,19 @@ std::string DataNodeOpaque::value() const + return reinterpret_cast(m_node)->value; + } + ++std::string OpaqueName::pretty() const ++{ ++ if (prefix) { ++ if (*prefix == moduleOrNamespace) { ++ return *prefix + ':' + name; ++ } else { ++ return "{" + moduleOrNamespace + "}, " + *prefix + ':' + name; ++ } ++ } else { ++ return "{" + moduleOrNamespace + "}, " + name; ++ } ++} ++ + /** + * Wraps a raw non-null lyd_node pointer. + * @param node The pointer to be wrapped. Must not be null. +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index b6ee455..a1096a6 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -1969,9 +1969,11 @@ TEST_CASE("Data Node manipulation") + + DOCTEST_SUBCASE("opaque nodes for sysrepo ops data discard") + { +- auto discard1 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:a"}); ++ // the "short" form with no prefix ++ auto discard1 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", std::nullopt, "discard-items"}, libyang::JSON{"/example-schema:a"}); + REQUIRE(!!discard1); +- auto discard2 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:b"}); ++ // let's use a prefix form here ++ auto discard2 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", "sysrepo", "discard-items"}, libyang::JSON{"/example-schema:b"}); + REQUIRE(!!discard2); + discard1->insertSibling(*discard2); + REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) +@@ -2001,16 +2003,38 @@ TEST_CASE("Data Node manipulation") + auto data = ctx.newPath2("/example-schema:myRpc/outputLeaf", "AHOJ", libyang::CreationOptions::Output).createdNode; + REQUIRE(data); + data->newPath("/example-schema:myRpc/another", "yay", libyang::CreationOptions::Output); ++ std::string prettyName; + +- DOCTEST_SUBCASE("JSON") { +- out = ctx.newOpaqueJSON(data->schema().module().name(), "output", std::nullopt); ++ DOCTEST_SUBCASE("JSON no prefix") { ++ out = ctx.newOpaqueJSON({data->schema().module().name(), std::nullopt, "output"}, std::nullopt); ++ prettyName = "{example-schema}, output"; + } + +- DOCTEST_SUBCASE("XML") { +- out = ctx.newOpaqueXML(data->schema().module().ns(), "output", std::nullopt); ++ DOCTEST_SUBCASE("JSON with prefix") { ++ out = ctx.newOpaqueJSON({data->schema().module().name(), data->schema().module().name(), "output"}, std::nullopt); ++ prettyName = "example-schema:output"; ++ ++ // wrong prefix is detected ++ REQUIRE_THROWS_WITH_AS(ctx.newOpaqueJSON({data->schema().module().name(), "xxx", "output"}, std::nullopt), ++ R"(invalid opaque JSON node: prefix "xxx" doesn't match module name "example-schema")", ++ libyang::Error); ++ } ++ ++ DOCTEST_SUBCASE("XML no prefix") { ++ out = ctx.newOpaqueXML({data->schema().module().ns(), std::nullopt, "output"}, std::nullopt); ++ prettyName = "{http://example.com/coze}, output"; ++ } ++ ++ DOCTEST_SUBCASE("XML with prefix") { ++ out = ctx.newOpaqueXML({data->schema().module().ns(), ++ data->schema().module().name() /* prefix is a module name, not the XML NS*/, ++ "output"}, ++ std::nullopt); ++ prettyName = "{http://example.com/coze}, example-schema:output"; + } + + REQUIRE(out); ++ REQUIRE(prettyName == out->asOpaque().name().pretty()); + data->unlinkWithSiblings(); + out->insertChild(*data); + +-- +2.43.0 + diff --git a/package/libyang-cpp/0016-Add-a-helper-for-finding-opaque-nodes.patch b/package/libyang-cpp/0016-Add-a-helper-for-finding-opaque-nodes.patch new file mode 100644 index 00000000..2ea22505 --- /dev/null +++ b/package/libyang-cpp/0016-Add-a-helper-for-finding-opaque-nodes.patch @@ -0,0 +1,462 @@ +From 8c59ecc5c687f8ee2ce62825835a378b422185f2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 14 Mar 2025 13:41:01 +0100 +Subject: [PATCH 16/18] Add a helper for finding opaque nodes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This is needed for sysrepo where we want to search through the +sysrepo:discard-items top-level opaque nodes. + +At first I wanted to simply wrap lyd_find_sibling_opaq_next(), but its +semantics is quite different to what is needed in the code which uses +sysrepo, so here's my attempt at a nice-ish API. + +Change-Id: I2571961e42f6d7a121e27c881cacdcfec0e87762 +Signed-off-by: Mattias Walström +--- + include/libyang-cpp/DataNode.hpp | 2 + + src/DataNode.cpp | 49 ++++++ + tests/data_node.cpp | 74 +++++++++ + tests/yang/sysrepo@2024-10-25.yang | 257 +++++++++++++++++++++++++++++ + 4 files changed, 382 insertions(+) + create mode 100644 tests/yang/sysrepo@2024-10-25.yang + +diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp +index 310b5e3..50d6c0e 100644 +--- a/include/libyang-cpp/DataNode.hpp ++++ b/include/libyang-cpp/DataNode.hpp +@@ -102,6 +102,7 @@ public: + + bool isOpaque() const; + DataNodeOpaque asOpaque() const; ++ std::optional firstOpaqueSibling() const; + + // TODO: allow setting the `parent` argument + DataNode duplicate(const std::optional opts = std::nullopt) const; +@@ -241,6 +242,7 @@ struct LIBYANG_CPP_EXPORT OpaqueName { + std::optional prefix; + std::string name; + std::string pretty() const; ++ bool matches(const std::string& prefixIsh, const std::string& name) const; + }; + + /** +diff --git a/src/DataNode.cpp b/src/DataNode.cpp +index 344f1b6..7e87917 100644 +--- a/src/DataNode.cpp ++++ b/src/DataNode.cpp +@@ -1135,6 +1135,20 @@ std::string OpaqueName::pretty() const + } + } + ++/** ++ * @short Fuzzy-match a real-world name against a combination of "something like a prefix" and "unqualified name" ++ * ++ * Because libyang doesn't propagate inherited prefixes, and because opaque nodes are magic, we seem to require ++ * this "fuzzy matching". It won't properly report a match on opaque nodes with a prefix that's inherited when ++ * using XML namespaces, though. ++ * */ ++bool OpaqueName::matches(const std::string& prefixIsh, const std::string& name) const ++{ ++ return name == this->name ++ && (prefixIsh == moduleOrNamespace ++ || (!!prefix && prefixIsh == *prefix)); ++} ++ + /** + * Wraps a raw non-null lyd_node pointer. + * @param node The pointer to be wrapped. Must not be null. +@@ -1305,4 +1319,39 @@ bool DataNode::siblingsEqual(const libyang::DataNode& other, const DataCompare f + } + } + ++/** ++ * @short Find the first opaque node among the siblings ++ * ++ * This function was inspired by `lyd_find_sibling_opaq_next()`. ++ * */ ++std::optional DataNode::firstOpaqueSibling() const ++{ ++ struct lyd_node *candidate = m_node; ++ ++ // Skip all non-opaque nodes; libyang guarantees to have them first, followed by a (possibly empty) set ++ // of opaque nodes. This is not documented anywhere, but it was explicitly confirmed by the maintainer: ++ // ++ // JK: can I rely on all non-opaque nodes being listed first among the siblings, and then all opaque nodes ++ // in one continuous sequence (but with an unspecified order among the opaque nodes themselves)? ++ // ++ // MV: yep ++ while (candidate && candidate->schema) { ++ candidate = candidate->next; ++ } ++ ++ // walk back through the opaque nodes; however, libyang lists are not your regular linked lists ++ while (candidate ++ && !candidate->prev->schema // don't go from the first opaque node through the non-opaque ones ++ && candidate->prev->next // don't wrap from the first node to the last one in case all of them are opaque ++ ) { ++ candidate = candidate->prev; ++ } ++ ++ if (candidate) { ++ return DataNode{candidate, m_refs}.asOpaque(); ++ } ++ ++ return std::nullopt; ++} ++ + } +diff --git a/tests/data_node.cpp b/tests/data_node.cpp +index a1096a6..db5a28e 100644 +--- a/tests/data_node.cpp ++++ b/tests/data_node.cpp +@@ -1982,6 +1982,80 @@ TEST_CASE("Data Node manipulation") + "sysrepo:discard-items": "/example-schema:b" + } + )"); ++ ++ // check that a list which only consists of opaque nodes doesn't break our iteration ++ REQUIRE(!!discard1->firstOpaqueSibling()); ++ REQUIRE(*discard1->firstOpaqueSibling() == *discard1); ++ REQUIRE(!!discard2->firstOpaqueSibling()); ++ REQUIRE(*discard2->firstOpaqueSibling() == *discard1); ++ ++ auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666"); ++ leafInt16.insertSibling(*discard1); ++ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) ++ == R"({ ++ "example-schema:leafInt16": 666, ++ "sysrepo:discard-items": "/example-schema:a", ++ "sysrepo:discard-items": "/example-schema:b" ++} ++)"); ++ REQUIRE(leafInt16.firstSibling().path() == "/example-schema:leafInt16"); ++ REQUIRE(discard1->firstSibling().path() == "/example-schema:leafInt16"); ++ REQUIRE(discard2->firstSibling().path() == "/example-schema:leafInt16"); ++ ++ auto dummy = ctx.newPath("/example-schema:dummy", "blah"); ++ auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque); ++ opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete"); ++ dummy.insertSibling(opaqueLeaf); ++ ++ // FIXME reword this: this one might not be handled by sysrepo, but we want it for our fuzzy matcher testing anyway ++ auto discard3 = ctx.newOpaqueXML(libyang::OpaqueName{"http://www.sysrepo.org/yang/sysrepo", "sysrepo", "discard-items"}, libyang::XML{"/example-schema:c"}); ++ REQUIRE(!!discard3); ++ // notice that it's printed without a proper prefix at first... ++ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink) ++ == R"({"discard-items":"/example-schema:c"})"); ++ // ...but after loading the module, the proper module is added back ++ ctx.parseModule(TESTS_DIR / "yang" / "sysrepo@2024-10-25.yang", libyang::SchemaFormat::YANG); ++ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink) ++ == R"({"sysrepo:discard-items":"/example-schema:c"})"); ++ ++ dummy.insertSibling(*discard3); ++ leafInt16.insertSibling(dummy); ++ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) ++ == R"({ ++ "example-schema:dummy": "blah", ++ "example-schema:leafInt16": 666, ++ "sysrepo:discard-items": "/example-schema:a", ++ "sysrepo:discard-items": "/example-schema:b", ++ "example-schema:leafInt32": "", ++ "@example-schema:leafInt32": { ++ "ietf-netconf:operation": "delete" ++ }, ++ "sysrepo:discard-items": "/example-schema:c" ++} ++)"); ++ ++ REQUIRE(dummy.firstOpaqueSibling() == discard1); ++ REQUIRE(dummy.firstOpaqueSibling() != discard2); ++ REQUIRE(leafInt16.firstOpaqueSibling() == discard1); ++ REQUIRE(opaqueLeaf.firstOpaqueSibling() == discard1); ++ REQUIRE(discard1->firstOpaqueSibling() == discard1); ++ REQUIRE(discard2->firstOpaqueSibling() == discard1); ++ REQUIRE(discard3->firstOpaqueSibling() == discard1); ++ REQUIRE(discard1->asOpaque().name().matches("sysrepo", "discard-items")); ++ REQUIRE(!discard1->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items")); ++ REQUIRE(discard2->asOpaque().name().matches("sysrepo", "discard-items")); ++ REQUIRE(!discard2->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items")); ++ REQUIRE(discard3->asOpaque().name().matches("sysrepo", "discard-items")); ++ REQUIRE(discard3->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items")); ++ REQUIRE(!opaqueLeaf.asOpaque().name().matches("sysrepo", "discard-items")); ++ ++ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()); ++ REQUIRE(dummy.firstOpaqueSibling()->nextSibling() == discard2); ++ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()); ++ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling() == opaqueLeaf); ++ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling()); ++ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling() == discard3); ++ REQUIRE(!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling()->nextSibling()); + } + + DOCTEST_SUBCASE("RESTCONF RPC output") +diff --git a/tests/yang/sysrepo@2024-10-25.yang b/tests/yang/sysrepo@2024-10-25.yang +new file mode 100644 +index 0000000..f5bc32c +--- /dev/null ++++ b/tests/yang/sysrepo@2024-10-25.yang +@@ -0,0 +1,257 @@ ++module sysrepo { ++ namespace "http://www.sysrepo.org/yang/sysrepo"; ++ prefix sr; ++ ++ yang-version 1.1; ++ ++ import ietf-yang-types { ++ prefix yang; ++ } ++ ++ import ietf-datastores { ++ prefix ds; ++ } ++ ++ import ietf-yang-metadata { ++ prefix md; ++ revision-date 2016-08-05; ++ } ++ ++ organization ++ "CESNET"; ++ ++ contact ++ "Author: Michal Vasko ++ "; ++ ++ description ++ "Sysrepo YANG datastore internal attributes and information."; ++ ++ revision "2024-10-25" { ++ description ++ "Removed redundant metadata used for push operational data."; ++ } ++ ++ revision "2019-07-10" { ++ description ++ "Initial revision."; ++ } ++ ++ typedef module-ref { ++ description ++ "Reference to a module."; ++ type leafref { ++ path "/sysrepo-modules/module/name"; ++ } ++ } ++ ++ md:annotation operation { ++ type enumeration { ++ enum none { ++ description ++ "Node with this operation must exist but does not affect the datastore in any way."; ++ reference ++ "RFC 6241 section 7.2.: default-operation"; ++ } ++ enum ether { ++ description ++ "Node with this operation does not have to exist and does not affect the datastore in any way."; ++ } ++ enum purge { ++ description ++ "Node with this operation represents an arbitrary generic node instance and all ++ the instances will be deleted."; ++ } ++ } ++ description ++ "Additional proprietary operations used internally."; ++ reference ++ "RFC 6241 section 7.2."; ++ } ++ ++ identity notification { ++ base ds:datastore; ++ description ++ "Special datastore for storing notifications for replay."; ++ } ++ ++ grouping module-info-grp { ++ leaf name { ++ type string; ++ description ++ "Module name."; ++ } ++ ++ leaf revision { ++ type string; ++ description ++ "Module revision."; ++ } ++ ++ leaf-list enabled-feature { ++ type string; ++ description ++ "List of all the enabled features."; ++ } ++ ++ list plugin { ++ key "datastore"; ++ description ++ "Module datastore plugin handling specific datastore data."; ++ ++ leaf datastore { ++ type identityref { ++ base ds:datastore; ++ } ++ description ++ "Datastore of this plugin."; ++ } ++ ++ leaf name { ++ type string; ++ mandatory true; ++ description ++ "Specific plugin name as present in the plugin structures."; ++ } ++ } ++ } ++ ++ grouping deps-grp { ++ list lref { ++ description ++ "Dependency of a leafref node."; ++ ++ leaf target-path { ++ type yang:xpath1.0; ++ mandatory true; ++ description ++ "Path identifying the leafref target node."; ++ } ++ ++ leaf target-module { ++ type module-ref; ++ mandatory true; ++ description ++ "Foreign target module of the leafref."; ++ } ++ } ++ ++ list inst-id { ++ description ++ "Dependency of an instance-identifier node."; ++ ++ leaf source-path { ++ type yang:xpath1.0; ++ mandatory true; ++ description ++ "Path identifying the instance-identifier node."; ++ } ++ ++ leaf default-target-path { ++ type yang:xpath1.0; ++ description ++ "Default instance-identifier value."; ++ } ++ } ++ ++ list xpath { ++ description ++ "Dependency of an XPath expression - must or when statement."; ++ ++ leaf expression { ++ type yang:xpath1.0; ++ mandatory true; ++ description ++ "XPath expression of the dependency - must or when statement argument."; ++ } ++ ++ leaf-list target-module { ++ type module-ref; ++ description ++ "Foreign modules with the data needed for evaluation of the XPath."; ++ } ++ } ++ } ++ ++ container sysrepo-modules { ++ config false; ++ description ++ "All installed Sysrepo modules."; ++ ++ leaf content-id { ++ type uint32; ++ mandatory true; ++ description ++ "Sysrepo module-set content-id to be used for its generated yang-library data."; ++ } ++ ++ list module { ++ key "name"; ++ description ++ "Sysrepo module."; ++ ++ uses module-info-grp; ++ ++ leaf replay-support { ++ type yang:date-and-time; ++ description ++ "Present only if the module supports replay. Means the earliest stored notification if any present. ++ Otherwise the time the replay support was switched on."; ++ } ++ ++ container deps { ++ description ++ "Module data dependencies on other modules."; ++ uses deps-grp; ++ } ++ ++ leaf-list inverse-deps { ++ type module-ref; ++ description ++ "List of modules that depend on this module."; ++ } ++ ++ list rpc { ++ key "path"; ++ description ++ "Module RPC/actions."; ++ ++ leaf path { ++ type yang:xpath1.0; ++ description ++ "Path identifying the operation."; ++ } ++ ++ container in { ++ description ++ "Operation input dependencies."; ++ uses deps-grp; ++ } ++ ++ container out { ++ description ++ "Operation output dependencies."; ++ uses deps-grp; ++ } ++ } ++ ++ list notification { ++ key "path"; ++ description ++ "Module notifications."; ++ ++ leaf path { ++ type yang:xpath1.0; ++ description ++ "Path identifying the notification."; ++ } ++ ++ container deps { ++ description ++ "Notification dependencies."; ++ uses deps-grp; ++ } ++ } ++ } ++ } ++} +-- +2.43.0 + diff --git a/package/libyang-cpp/0017-build-link-to-libpcre2-explicitly.patch b/package/libyang-cpp/0017-build-link-to-libpcre2-explicitly.patch new file mode 100644 index 00000000..350fae95 --- /dev/null +++ b/package/libyang-cpp/0017-build-link-to-libpcre2-explicitly.patch @@ -0,0 +1,56 @@ +From dab8c9aac96063ccb8541d5d1795ee89b75faeee Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 2 Apr 2025 11:50:24 -0700 +Subject: [PATCH 17/18] build: link to libpcre2 explicitly +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This is apparently a problem on Mac OS X builds where the transitive +dependency via libyang doesn't take effect: + + Undefined symbols for architecture arm64: + "_pcre2_code_free_8", referenced from: + libyang::Regex::~Regex() in Regex.cpp.o + libyang::Regex::~Regex() in Regex.cpp.o + ld: symbol(s) not found for architecture arm64 + +Let's fix this by linking with libpcre2-8 directly. + +We're using a different approach than libyang; they have their own CMake +wrapper. I find it easier to work with pkg-config modules, so let's try +it that way. + +Change-Id: I1a64407d852161595b7dca654433190002cc3600 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c5fec45..4e009aa 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -34,6 +34,9 @@ pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET) + # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency + find_package(date) + ++# libyang::Regex::~Regex() bypasses libyang and calls out to libpcre directly ++pkg_check_modules(LIBPCRE2 REQUIRED libpcre2-8 IMPORTED_TARGET) ++ + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + + include(CheckIncludeFileCXX) +@@ -64,7 +67,7 @@ add_library(yang-cpp + src/utils/newPath.cpp + ) + +-target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG) ++target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG PkgConfig::LIBPCRE2) + # We do not offer any long-term API/ABI guarantees. To make stuff easier for downstream consumers, + # we will be bumping both API and ABI versions very deliberately. + # There will be no attempts at semver tracking, for example. +-- +2.43.0 + diff --git a/package/libyang-cpp/0018-CI-renamed-project-upstream.patch b/package/libyang-cpp/0018-CI-renamed-project-upstream.patch new file mode 100644 index 00000000..f8f39669 --- /dev/null +++ b/package/libyang-cpp/0018-CI-renamed-project-upstream.patch @@ -0,0 +1,66 @@ +From 7519fcd35216072a6b1eebe2a79e19789be345a9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 9 Apr 2025 15:35:37 +0200 +Subject: [PATCH 18/18] CI: renamed project upstream +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + README.md | 2 +- + ci/build.sh | 2 +- + 3 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index b41c490..7b92766 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -5,13 +5,13 @@ + required-projects: + - name: github/CESNET/libyang + override-checkout: devel +- - name: github/onqtam/doctest ++ - name: github/doctest/doctest + override-checkout: v2.3.6 + - f38-clang-asan-ubsan: + required-projects: &projects + - name: github/CESNET/libyang + override-checkout: devel +- - name: github/onqtam/doctest ++ - name: github/doctest/doctest + override-checkout: v2.4.11 + - f38-clang-tsan: + required-projects: *projects +diff --git a/README.md b/README.md +index d76975b..8291e00 100644 +--- a/README.md ++++ b/README.md +@@ -11,7 +11,7 @@ Object lifetimes are managed automatically via RAII. + - [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ +-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework ++- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework + - optionally for the docs, Doxygen + + ## Building +diff --git a/ci/build.sh b/ci/build.sh +index d06b646..0867f07 100755 +--- a/ci/build.sh ++++ b/ci/build.sh +@@ -73,7 +73,7 @@ build_n_test() { + } + + build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF +-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF ++build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF + build_n_test ${ZUUL_PROJECT_NAME} -DBUILD_TESTING=ON + + pushd ${BUILD_DIR}/${ZUUL_PROJECT_NAME} +-- +2.43.0 + diff --git a/package/rousette/0001-restconf-report-the-list-key-values-when-they-differ.patch b/package/rousette/0001-restconf-report-the-list-key-values-when-they-differ.patch index adea05e2..67469b1b 100644 --- a/package/rousette/0001-restconf-report-the-list-key-values-when-they-differ.patch +++ b/package/rousette/0001-restconf-report-the-list-key-values-when-they-differ.patch @@ -1,12 +1,12 @@ From 9622a68eba4aeaa60619b4c33d050ce91b27653d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Tue, 8 Oct 2024 12:22:49 +0200 -Subject: [PATCH 01/20] restconf: report the list key values when they differ +Subject: [PATCH 01/44] restconf: report the list key values when they differ between URI and data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires For creating (leaf-)list instances with PUT and PATCH methods one has to specify the list key values in the URI and in the data as well. diff --git a/package/rousette/0002-uri-rename-url-encoding-to-percent-encoding.patch b/package/rousette/0002-uri-rename-url-encoding-to-percent-encoding.patch index 2964b199..e0dc5581 100644 --- a/package/rousette/0002-uri-rename-url-encoding-to-percent-encoding.patch +++ b/package/rousette/0002-uri-rename-url-encoding-to-percent-encoding.patch @@ -1,11 +1,11 @@ From 070cffb48fda789910581930265d4624a7213e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Wed, 16 Oct 2024 11:02:45 +0200 -Subject: [PATCH 02/20] uri: rename url-encoding to percent-encoding +Subject: [PATCH 02/44] uri: rename url-encoding to percent-encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires RFC 3986 [1] calls it the percent-encoding, let's be consistent. diff --git a/package/rousette/0003-uri-correct-x3-rule-class-names.patch b/package/rousette/0003-uri-correct-x3-rule-class-names.patch index 06ad7bac..c49caea6 100644 --- a/package/rousette/0003-uri-correct-x3-rule-class-names.patch +++ b/package/rousette/0003-uri-correct-x3-rule-class-names.patch @@ -1,11 +1,11 @@ From 8a233202647f24538f2bbb8fff1e38d52e3599a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Wed, 16 Oct 2024 11:05:09 +0200 -Subject: [PATCH 03/20] uri: correct x3 rule class names +Subject: [PATCH 03/44] uri: correct x3 rule class names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Fixes: e06d5bf ("server: parser for data resources in URI paths") Change-Id: Ic953e568d841032113ede1c0e896574361c0ebe2 diff --git a/package/rousette/0004-restconf-parser-should-work-on-raw-percent-encoded-p.patch b/package/rousette/0004-restconf-parser-should-work-on-raw-percent-encoded-p.patch index 60b6d5b9..d5558e61 100644 --- a/package/rousette/0004-restconf-parser-should-work-on-raw-percent-encoded-p.patch +++ b/package/rousette/0004-restconf-parser-should-work-on-raw-percent-encoded-p.patch @@ -1,12 +1,12 @@ From 96cbf730010ee9539d05d0d72697dc960b3a938c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 7 Oct 2024 20:46:24 +0200 -Subject: [PATCH 04/20] restconf: parser should work on raw percent-encoded +Subject: [PATCH 04/44] restconf: parser should work on raw percent-encoded paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires The difference between nghttp2's uri_ref::path and raw_path is that the raw_path keeps the percent encoding, while the path converts the percent diff --git a/package/rousette/0005-restconf-list-key-values-checking-must-respect-libya.patch b/package/rousette/0005-restconf-list-key-values-checking-must-respect-libya.patch index 7b6b0f8a..8d111ac7 100644 --- a/package/rousette/0005-restconf-list-key-values-checking-must-respect-libya.patch +++ b/package/rousette/0005-restconf-list-key-values-checking-must-respect-libya.patch @@ -1,12 +1,12 @@ From 8b13c1e4ccaa61a241674c27063439e257fa88de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Wed, 2 Oct 2024 20:21:28 +0200 -Subject: [PATCH 05/20] restconf: list key values checking must respect +Subject: [PATCH 05/44] restconf: list key values checking must respect libyang's canonicalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires While dealing with the issue that was fixed in the previous commit we realized that the issue is deeper. Not only that our parser rejected diff --git a/package/rousette/0006-tests-test-querying-lists-with-union-keys.patch b/package/rousette/0006-tests-test-querying-lists-with-union-keys.patch index 53d0b243..7990f1f1 100644 --- a/package/rousette/0006-tests-test-querying-lists-with-union-keys.patch +++ b/package/rousette/0006-tests-test-querying-lists-with-union-keys.patch @@ -1,11 +1,11 @@ From fda47b6a6cfdaecc24e96c4d6138c6de3ef116e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 7 Oct 2024 21:21:22 +0200 -Subject: [PATCH 06/20] tests: test querying lists with union keys +Subject: [PATCH 06/44] tests: test querying lists with union keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Test that we correctly work with keys that are unions of something that can have a module namespace and that must not have it. diff --git a/package/rousette/0007-error-handling-in-sysrepo-has-changed.patch b/package/rousette/0007-error-handling-in-sysrepo-has-changed.patch index a82b7dde..6b6cb921 100644 --- a/package/rousette/0007-error-handling-in-sysrepo-has-changed.patch +++ b/package/rousette/0007-error-handling-in-sysrepo-has-changed.patch @@ -1,11 +1,11 @@ From 27ef5bc87fdeb70a77609da6ec18ee5c28656bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 29 Oct 2024 18:54:55 +0100 -Subject: [PATCH 07/20] error handling in sysrepo has changed +Subject: [PATCH 07/44] error handling in sysrepo has changed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/7969 Change-Id: Id028806ed49114cba4c55e2874bcf3fc98308bdc diff --git a/package/rousette/0008-restconf-support-fields-query-parameter.patch b/package/rousette/0008-restconf-support-fields-query-parameter.patch index f2cc8cfc..f468f422 100644 --- a/package/rousette/0008-restconf-support-fields-query-parameter.patch +++ b/package/rousette/0008-restconf-support-fields-query-parameter.patch @@ -1,11 +1,11 @@ From 6819561d97e38569c319e36ca2e99768036b4032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Wed, 21 Aug 2024 19:18:08 +0200 -Subject: [PATCH 08/20] restconf: support fields query parameter +Subject: [PATCH 08/44] restconf: support fields query parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires This patch adds support for fields query parameter [1]. diff --git a/package/rousette/0009-cmake-adhere-to-CMP0167.patch b/package/rousette/0009-cmake-adhere-to-CMP0167.patch index 44f3538d..ef9f3b96 100644 --- a/package/rousette/0009-cmake-adhere-to-CMP0167.patch +++ b/package/rousette/0009-cmake-adhere-to-CMP0167.patch @@ -1,11 +1,11 @@ From 48d9b6ba3f3f892b9060b76b505d2f9a3aeb9e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 25 Nov 2024 09:15:55 +0100 -Subject: [PATCH 09/20] cmake: adhere to CMP0167 +Subject: [PATCH 09/44] cmake: adhere to CMP0167 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires After locally updating to cmake 3.30 I have seen a warning that our way of finding boost library is deprecated [1]. diff --git a/package/rousette/0010-Fix-compatibility-with-pam_wrapper-1.1.6.patch b/package/rousette/0010-Fix-compatibility-with-pam_wrapper-1.1.6.patch index 1bf2560c..96de31d1 100644 --- a/package/rousette/0010-Fix-compatibility-with-pam_wrapper-1.1.6.patch +++ b/package/rousette/0010-Fix-compatibility-with-pam_wrapper-1.1.6.patch @@ -1,11 +1,11 @@ From 64997543d48236cd2aae417568bc54d32c54df21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Mon, 2 Dec 2024 14:43:36 +0100 -Subject: [PATCH 10/20] Fix compatibility with pam_wrapper 1.1.6+ +Subject: [PATCH 10/44] Fix compatibility with pam_wrapper 1.1.6+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires An year ago I reported a bug that the pam_wrapper project says that they use a variable called `PAM_WRAPPER_DISABLE_DEEPBIND`, but in fact they diff --git a/package/rousette/0011-tests-add-missing-pragma-once.patch b/package/rousette/0011-tests-add-missing-pragma-once.patch index f31e61e8..baff1334 100644 --- a/package/rousette/0011-tests-add-missing-pragma-once.patch +++ b/package/rousette/0011-tests-add-missing-pragma-once.patch @@ -1,11 +1,11 @@ From 849aa35274d5e07726cb849fe724962754b3fa29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 2 Dec 2024 20:20:48 +0100 -Subject: [PATCH 11/20] tests: add missing pragma once +Subject: [PATCH 11/44] tests: add missing pragma once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Change-Id: I269c20e5a914aa8c7bc9431147dd9785ea1aedda Signed-off-by: Mattias Walström diff --git a/package/rousette/0012-tests-extend-clientRequest-wrappers-interface-and-us.patch b/package/rousette/0012-tests-extend-clientRequest-wrappers-interface-and-us.patch index 1c22d65a..1dcb9a3b 100644 --- a/package/rousette/0012-tests-extend-clientRequest-wrappers-interface-and-us.patch +++ b/package/rousette/0012-tests-extend-clientRequest-wrappers-interface-and-us.patch @@ -1,12 +1,12 @@ From 60eac2b2d60f8f1918a0914272975dd53f527c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 2 Dec 2024 19:36:10 +0100 -Subject: [PATCH 12/20] tests: extend clientRequest wrappers interface and use +Subject: [PATCH 12/44] tests: extend clientRequest wrappers interface and use it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires This is a preparation for refactoring in the next few commits. I will generalize the clientRequest interface to accept server diff --git a/package/rousette/0013-tests-move-stuff-from-the-header-file-into-cpp-file.patch b/package/rousette/0013-tests-move-stuff-from-the-header-file-into-cpp-file.patch index 00f86527..31716521 100644 --- a/package/rousette/0013-tests-move-stuff-from-the-header-file-into-cpp-file.patch +++ b/package/rousette/0013-tests-move-stuff-from-the-header-file-into-cpp-file.patch @@ -1,11 +1,11 @@ From 0beaee041fd4fcfbebcbb2912eb6b26ec71f50c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 2 Dec 2024 20:01:54 +0100 -Subject: [PATCH 13/20] tests: move stuff from the header file into cpp file +Subject: [PATCH 13/44] tests: move stuff from the header file into cpp file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Allright, I have had enough. I am no longer waiting for a minute for a recompilation of one test. diff --git a/package/rousette/0014-tests-rename-datastoreUtils-to-event_watchers.patch b/package/rousette/0014-tests-rename-datastoreUtils-to-event_watchers.patch index e45351ea..9c7010c2 100644 --- a/package/rousette/0014-tests-rename-datastoreUtils-to-event_watchers.patch +++ b/package/rousette/0014-tests-rename-datastoreUtils-to-event_watchers.patch @@ -1,11 +1,11 @@ From 53aa2e23ee8acc1881487f3969c2c58fb29437be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 2 Dec 2024 20:08:53 +0100 -Subject: [PATCH 14/20] tests: rename datastoreUtils to event_watchers +Subject: [PATCH 14/44] tests: rename datastoreUtils to event_watchers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires The next commit will move RESTCONF notification watchers there, I think the new name is more appropriate. diff --git a/package/rousette/0015-tests-rename-NotificationWatcher.patch b/package/rousette/0015-tests-rename-NotificationWatcher.patch index 76587638..7fd57165 100644 --- a/package/rousette/0015-tests-rename-NotificationWatcher.patch +++ b/package/rousette/0015-tests-rename-NotificationWatcher.patch @@ -1,11 +1,11 @@ From f86df829979a26d5192a86c3b43c1393dcba7140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Tue, 3 Dec 2024 11:45:41 +0100 -Subject: [PATCH 15/20] tests: rename NotificationWatcher +Subject: [PATCH 15/44] tests: rename NotificationWatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires This watches notifications going through RESTCONF, so I think RestconfNotificatonWatcher is better name for this. diff --git a/package/rousette/0016-tests-make-NotificationWatcher-reusable.patch b/package/rousette/0016-tests-make-NotificationWatcher-reusable.patch index 9660f8a7..bd2d7dd7 100644 --- a/package/rousette/0016-tests-make-NotificationWatcher-reusable.patch +++ b/package/rousette/0016-tests-make-NotificationWatcher-reusable.patch @@ -1,11 +1,11 @@ From 30d704588fa7eb9d32f66296ec5f6784f082869e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 2 Dec 2024 20:11:32 +0100 -Subject: [PATCH 16/20] tests: make NotificationWatcher reusable +Subject: [PATCH 16/44] tests: make NotificationWatcher reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires We will need some parts of this in yang push tests diff --git a/package/rousette/0017-tests-helper-function-to-construct-server-URI.patch b/package/rousette/0017-tests-helper-function-to-construct-server-URI.patch index a8bb359f..90eecac8 100644 --- a/package/rousette/0017-tests-helper-function-to-construct-server-URI.patch +++ b/package/rousette/0017-tests-helper-function-to-construct-server-URI.patch @@ -1,11 +1,11 @@ From 36943051d8563bee0c9cf89eec28acf5d3617272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Tue, 3 Dec 2024 12:02:24 +0100 -Subject: [PATCH 17/20] tests: helper function to construct server URI +Subject: [PATCH 17/44] tests: helper function to construct server URI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Change-Id: Ic7c03b32a29b07464291f99027530010a7902f77 Signed-off-by: Mattias Walström diff --git a/package/rousette/0018-tests-make-SSEClient-reusable.patch b/package/rousette/0018-tests-make-SSEClient-reusable.patch index c0906455..9c311289 100644 --- a/package/rousette/0018-tests-make-SSEClient-reusable.patch +++ b/package/rousette/0018-tests-make-SSEClient-reusable.patch @@ -1,11 +1,11 @@ From 7d15f59d20079ba94224e0bc308682aa5a004483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Mon, 2 Dec 2024 20:15:05 +0100 -Subject: [PATCH 18/20] tests: make SSEClient reusable +Subject: [PATCH 18/44] tests: make SSEClient reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires We will need it in yang push tests diff --git a/package/rousette/0019-tests-fix-deadlock-in-tests.patch b/package/rousette/0019-tests-fix-deadlock-in-tests.patch index 446c59de..9ad46eea 100644 --- a/package/rousette/0019-tests-fix-deadlock-in-tests.patch +++ b/package/rousette/0019-tests-fix-deadlock-in-tests.patch @@ -1,11 +1,11 @@ From 3e3e492f4801df56226a5aff5d82bfa86c9d3812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Tue, 3 Dec 2024 13:59:52 +0100 -Subject: [PATCH 19/20] tests: fix deadlock in tests +Subject: [PATCH 19/44] tests: fix deadlock in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires In the case the client never requests (because of misconfiguration or something else) the test would still be waiting for somebody to diff --git a/package/rousette/0020-restconf-make-as_restconf_notification-reusable.patch b/package/rousette/0020-restconf-make-as_restconf_notification-reusable.patch index 6df9d2bb..6b0608c3 100644 --- a/package/rousette/0020-restconf-make-as_restconf_notification-reusable.patch +++ b/package/rousette/0020-restconf-make-as_restconf_notification-reusable.patch @@ -1,11 +1,11 @@ From ed0ff23f7ad341d663484f0b2a617cd3bc4923c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= Date: Tue, 3 Dec 2024 19:27:49 +0100 -Subject: [PATCH 20/20] restconf: make as_restconf_notification reusable +Subject: [PATCH 20/44] restconf: make as_restconf_notification reusable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires The YANG-PUSH notifications are supposed to be wrapped in this too, so this needs to be accessible from multiple places. diff --git a/package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch b/package/rousette/0021-Update-dependencies.patch similarity index 82% rename from package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch rename to package/rousette/0021-Update-dependencies.patch index 7e260ad6..95ea530b 100644 --- a/package/rousette/999-GERRIT-step-up-sysrepo-cpp.patch +++ b/package/rousette/0021-Update-dependencies.patch @@ -1,17 +1,25 @@ From f7307481fc4ca167592acf925136e5f8a51e2150 Mon Sep 17 00:00:00 2001 -From: Jan Kundrát +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 18 Dec 2024 17:26:15 +0100 -Subject: [PATCH] Update dependencies +Subject: [PATCH 21/44] Update dependencies +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8134 Change-Id: Ib170aa61600ffb9089460247f762ff5a947dd6c6 +Signed-off-by: Mattias Walström --- + CMakeLists.txt | 2 +- + tests/restconf-writing.cpp | 8 ++++---- + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22bce32..5ae8062 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -74,7 +74,7 @@ +@@ -74,7 +74,7 @@ find_package(PkgConfig) pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) find_package(Boost REQUIRED CONFIG COMPONENTS system thread) @@ -24,7 +32,7 @@ diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp index 0932984..d37ab03 100644 --- a/tests/restconf-writing.cpp +++ b/tests/restconf-writing.cpp -@@ -772,7 +772,7 @@ +@@ -772,7 +772,7 @@ TEST_CASE("writing data") { "error-type": "protocol", "error-tag": "invalid-value", @@ -33,7 +41,7 @@ index 0932984..d37ab03 100644 } ] } -@@ -855,7 +855,7 @@ +@@ -855,7 +855,7 @@ TEST_CASE("writing data") { "error-type": "protocol", "error-tag": "invalid-value", @@ -42,7 +50,7 @@ index 0932984..d37ab03 100644 } ] } -@@ -1560,7 +1560,7 @@ +@@ -1560,7 +1560,7 @@ TEST_CASE("writing data") { "error-type": "protocol", "error-tag": "invalid-value", @@ -51,7 +59,7 @@ index 0932984..d37ab03 100644 } ] } -@@ -1626,7 +1626,7 @@ +@@ -1626,7 +1626,7 @@ TEST_CASE("writing data") { "error-type": "protocol", "error-tag": "invalid-value", @@ -60,3 +68,6 @@ index 0932984..d37ab03 100644 } ] } +-- +2.43.0 + diff --git a/package/rousette/0022-restconf-add-RAII-datastore-switch.patch b/package/rousette/0022-restconf-add-RAII-datastore-switch.patch new file mode 100644 index 00000000..93c54ba1 --- /dev/null +++ b/package/rousette/0022-restconf-add-RAII-datastore-switch.patch @@ -0,0 +1,104 @@ +From c967e688c53000519ceb6030e8282296e4846d3c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 13 Jan 2025 18:29:29 +0100 +Subject: [PATCH 22/44] restconf: add RAII datastore switch +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Copied from sysrepo-ietf-alarms[1]. + +[1] https://github.com/CESNET/sysrepo-ietf-alarms/blob/4870dbee30a5ec56135d4f4cb5818f191c089c34/src/utils/sysrepo.cpp + +Change-Id: I51738d06187e141ebd662e7b9421ca995380cafc +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 1 + + src/restconf/utils/sysrepo.cpp | 25 +++++++++++++++++++++++++ + src/restconf/utils/sysrepo.h | 29 +++++++++++++++++++++++++++++ + 3 files changed, 55 insertions(+) + create mode 100644 src/restconf/utils/sysrepo.cpp + create mode 100644 src/restconf/utils/sysrepo.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 5ae8062..7f3a9c0 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -114,6 +114,7 @@ add_library(rousette-restconf STATIC + src/restconf/YangSchemaLocations.cpp + src/restconf/uri.cpp + src/restconf/utils/dataformat.cpp ++ src/restconf/utils/sysrepo.cpp + src/restconf/utils/yang.cpp + ) + target_link_libraries(rousette-restconf PUBLIC rousette-http rousette-sysrepo rousette-auth Boost::system Threads::Threads PRIVATE date::date-tz) +diff --git a/src/restconf/utils/sysrepo.cpp b/src/restconf/utils/sysrepo.cpp +new file mode 100644 +index 0000000..622ab27 +--- /dev/null ++++ b/src/restconf/utils/sysrepo.cpp +@@ -0,0 +1,25 @@ ++/* ++ * Copyright (C) 2020, 2022 CESNET, https://photonics.cesnet.cz/ ++ * ++ * Written by Jan Kundrát ++ * Written by Tomáš Pecka ++ */ ++ ++#include ++#include "sysrepo.h" ++ ++namespace rousette::restconf { ++ ++ScopedDatastoreSwitch::ScopedDatastoreSwitch(sysrepo::Session session, sysrepo::Datastore ds) ++ : m_session(std::move(session)) ++ , m_oldDatastore(m_session.activeDatastore()) ++{ ++ m_session.switchDatastore(ds); ++} ++ ++ScopedDatastoreSwitch::~ScopedDatastoreSwitch() ++{ ++ m_session.switchDatastore(m_oldDatastore); ++} ++ ++} +diff --git a/src/restconf/utils/sysrepo.h b/src/restconf/utils/sysrepo.h +new file mode 100644 +index 0000000..da6c359 +--- /dev/null ++++ b/src/restconf/utils/sysrepo.h +@@ -0,0 +1,29 @@ ++/* ++ * Copyright (C) 2020, 2022 CESNET, https://photonics.cesnet.cz/ ++ * ++ * Written by Jan Kundrát ++ * Written by Tomáš Pecka ++ */ ++ ++#pragma once ++ ++#include ++#include ++ ++namespace rousette::restconf { ++ ++/** @brief Ensures that session switches to provided datastore and when the object gets destroyed the session switches back to the original datastore. */ ++class ScopedDatastoreSwitch { ++ sysrepo::Session m_session; ++ sysrepo::Datastore m_oldDatastore; ++ ++public: ++ ScopedDatastoreSwitch(sysrepo::Session session, sysrepo::Datastore ds); ++ ~ScopedDatastoreSwitch(); ++ ScopedDatastoreSwitch(const ScopedDatastoreSwitch&) = delete; ++ ScopedDatastoreSwitch(ScopedDatastoreSwitch&&) = delete; ++ ScopedDatastoreSwitch& operator=(const ScopedDatastoreSwitch&) = delete; ++ ScopedDatastoreSwitch& operator=(ScopedDatastoreSwitch&&) = delete; ++}; ++ ++} +-- +2.43.0 + diff --git a/package/rousette/0023-restconf-add-missing-pragma-once.patch b/package/rousette/0023-restconf-add-missing-pragma-once.patch new file mode 100644 index 00000000..f46a0a6a --- /dev/null +++ b/package/rousette/0023-restconf-add-missing-pragma-once.patch @@ -0,0 +1,30 @@ +From 05274bd27b886e67accc86be7153ed76465b156e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 15 Jan 2025 17:56:26 +0100 +Subject: [PATCH 23/44] restconf: add missing pragma once +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I6d337c99351e24226fb322a40ba2e96655104860 +Signed-off-by: Mattias Walström +--- + src/restconf/NotificationStream.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/restconf/NotificationStream.h b/src/restconf/NotificationStream.h +index 6840f11..3029a0d 100644 +--- a/src/restconf/NotificationStream.h ++++ b/src/restconf/NotificationStream.h +@@ -5,6 +5,7 @@ + * + */ + ++#pragma once + #include + #include + #include +-- +2.43.0 + diff --git a/package/rousette/0024-restconf-make-fetching-replay-info-reusable.patch b/package/rousette/0024-restconf-make-fetching-replay-info-reusable.patch new file mode 100644 index 00000000..836b7958 --- /dev/null +++ b/package/rousette/0024-restconf-make-fetching-replay-info-reusable.patch @@ -0,0 +1,108 @@ +From bd7def1768e360cc491d56805f7c16784c2f4fbe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 15 Jan 2025 18:27:25 +0100 +Subject: [PATCH 24/44] restconf: make fetching replay info reusable +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +We are querying sysrepo for replay support in our modules for NETCONF +stream. This part of code will be reused for dynamic subscriptions to +NETCONF stream as well, so let's make it reusable. + +Change-Id: Id5bc656619acb8e56dfafde23b03107139d34e94 +Signed-off-by: Mattias Walström +--- + src/restconf/NotificationStream.cpp | 57 +++++++++++++++++------------ + 1 file changed, 34 insertions(+), 23 deletions(-) + +diff --git a/src/restconf/NotificationStream.cpp b/src/restconf/NotificationStream.cpp +index eeddc04..e00c39d 100644 +--- a/src/restconf/NotificationStream.cpp ++++ b/src/restconf/NotificationStream.cpp +@@ -50,6 +50,36 @@ bool canBeSubscribed(const libyang::Module& mod) + { + return mod.implemented() && mod.name() != "sysrepo"; + } ++ ++struct SysrepoReplayInfo { ++ bool enabled; ++ std::optional earliestNotification; ++}; ++ ++SysrepoReplayInfo sysrepoReplayInfo(sysrepo::Session& session) ++{ ++ decltype(sysrepo::ModuleReplaySupport::earliestNotification) globalEarliestNotification; ++ bool replayEnabled = false; ++ ++ for (const auto& mod : session.getContext().modules()) { ++ if (!canBeSubscribed(mod)) { ++ continue; ++ } ++ ++ auto replay = session.getConnection().getModuleReplaySupport(mod.name()); ++ replayEnabled |= replay.enabled; ++ ++ if (replay.earliestNotification) { ++ if (!globalEarliestNotification) { ++ globalEarliestNotification = replay.earliestNotification; ++ } else { ++ globalEarliestNotification = std::min(*replay.earliestNotification, *globalEarliestNotification); ++ } ++ } ++ } ++ ++ return {replayEnabled, globalEarliestNotification}; ++} + } + + namespace rousette::restconf { +@@ -114,28 +144,9 @@ void NotificationStream::activate() + /** @brief Creates and fills ietf-restconf-monitoring:restconf-state/stream. To be called in oper callback. */ + void notificationStreamList(sysrepo::Session& session, std::optional& parent, const std::string& streamsPrefix) + { ++ const auto replayInfo = sysrepoReplayInfo(session); + static const auto prefix = "/ietf-restconf-monitoring:restconf-state/streams/stream[name='NETCONF']"s; + +- decltype(sysrepo::ModuleReplaySupport::earliestNotification) globalEarliestNotification; +- bool replayEnabled = false; +- +- for (const auto& mod : session.getContext().modules()) { +- if (!canBeSubscribed(mod)) { +- continue; +- } +- +- auto replay = session.getConnection().getModuleReplaySupport(mod.name()); +- replayEnabled |= replay.enabled; +- +- if (replay.earliestNotification) { +- if (!globalEarliestNotification) { +- globalEarliestNotification = replay.earliestNotification; +- } else { +- globalEarliestNotification = std::min(*replay.earliestNotification, *globalEarliestNotification); +- } +- } +- } +- + if (!parent) { + parent = session.getContext().newPath(prefix + "/description", "Default NETCONF notification stream"); + } else { +@@ -144,11 +155,11 @@ void notificationStreamList(sysrepo::Session& session, std::optionalnewPath(prefix + "/access[encoding='xml']/location", streamsPrefix + "NETCONF/XML"); + parent->newPath(prefix + "/access[encoding='json']/location", streamsPrefix + "NETCONF/JSON"); + +- if (replayEnabled) { ++ if (replayInfo.enabled) { + parent->newPath(prefix + "/replay-support", "true"); + +- if (globalEarliestNotification) { +- parent->newPath(prefix + "/replay-log-creation-time", libyang::yangTimeFormat(*globalEarliestNotification, libyang::TimezoneInterpretation::Local)); ++ if (replayInfo.earliestNotification) { ++ parent->newPath(prefix + "/replay-log-creation-time", libyang::yangTimeFormat(*replayInfo.earliestNotification, libyang::TimezoneInterpretation::Local)); + } + } + } +-- +2.43.0 + diff --git a/package/rousette/0025-sysrepo-upstream-API-change-NULL-output-on-RPCs.patch b/package/rousette/0025-sysrepo-upstream-API-change-NULL-output-on-RPCs.patch new file mode 100644 index 00000000..45aa2b95 --- /dev/null +++ b/package/rousette/0025-sysrepo-upstream-API-change-NULL-output-on-RPCs.patch @@ -0,0 +1,53 @@ +From 08458725211e707dec7bfebf7c5188e766136677 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 7 Feb 2025 17:34:04 +0100 +Subject: [PATCH 25/44] sysrepo upstream API change: NULL output on RPCs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8289 +Change-Id: I8db21637246492b0f79d1adf7df1704df5173e39 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + src/restconf/Server.cpp | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 7f3a9c0..cdd0eb4 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -74,7 +74,7 @@ find_package(PkgConfig) + pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) + find_package(Boost REQUIRED CONFIG COMPONENTS system thread) + +-pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=4) ++pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5) + pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3) + pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd) + pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam) +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index 7c66ea4..daf23ed 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -462,13 +462,13 @@ void processActionOrRPC(std::shared_ptr requestCtx, const std::c + + auto rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout); + +- if (rpcReply.immediateChildren().empty()) { ++ if (!rpcReply || rpcReply->immediateChildren().empty()) { + requestCtx->res.write_head(204, {CORS}); + requestCtx->res.end(); + return; + } + +- auto responseNode = rpcReply.child(); ++ auto responseNode = rpcReply->child(); + responseNode->unlinkWithSiblings(); + + auto envelope = ctx.newOpaqueJSON(rpcNode->schema().module().name(), "output", std::nullopt); +-- +2.43.0 + diff --git a/package/rousette/0026-boost-1.87-support.patch b/package/rousette/0026-boost-1.87-support.patch new file mode 100644 index 00000000..6c98b3ec --- /dev/null +++ b/package/rousette/0026-boost-1.87-support.patch @@ -0,0 +1,82 @@ +From d69697a719781ef06ecb0545a58c2d055ca53c6d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 3 Mar 2025 13:32:51 +0100 +Subject: [PATCH 26/44] boost 1.87 support +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +In Asio 1.33 (boost 1.87) some of the deprecated functionalities were +removed [1]. For rousette there are two changes that needed to be +addressed: + + * A removed include in clock.cpp. But actually, we do not need that + header at all. + * boost::asio::io_service is no longer around, it was actually renamed + to boost::asio::io_context a while ago. Its post() method is no + longer around as well, it was deprecated in favour of post() + function [2]. + This function is there since boost 1.66, so let's set minimal boost + version. + +A bigger problem is that our dependency nghttp2-asio seems to be a dead +project. At the time of writing this, there are no patches for more than +3 years. However, we are not the only consumers of that project and +somebody else already took care of that before us [3,4]. + +[1] https://www.boost.org/doc/libs/1_87_0/doc/html/boost_asio/history.html +[2] https://live.boost.org/doc/libs/1_86_0/doc/html/boost_asio/reference/io_context/post.html +[3] https://github.com/nghttp2/nghttp2-asio/issues/23 +[3] https://github.com/microsoft/vcpkg/commit/8eecddf7f18792d41c58a404604eed7f87b4b462 + +Change-Id: Ia59bb06e5f8ed222aa6e9f1c9b3947b05afeb9ec +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 4 ++-- + src/clock.cpp | 1 - + src/http/EventStream.cpp | 2 +- + 3 files changed, 3 insertions(+), 4 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index cdd0eb4..ca41ef3 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -71,8 +71,8 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) + find_package(spdlog REQUIRED) + find_package(date REQUIRED) # FIXME: Remove when we have STL with __cpp_lib_chrono >= 201907 (gcc 14) + find_package(PkgConfig) +-pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) +-find_package(Boost REQUIRED CONFIG COMPONENTS system thread) ++pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23 ++find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread) + + pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5) + pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3) +diff --git a/src/clock.cpp b/src/clock.cpp +index 16a49b2..7ac6c3e 100644 +--- a/src/clock.cpp ++++ b/src/clock.cpp +@@ -5,7 +5,6 @@ + * + */ + +-#include + #include + #include + #include +diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp +index 34038f4..6c765c5 100644 +--- a/src/http/EventStream.cpp ++++ b/src/http/EventStream.cpp +@@ -117,6 +117,6 @@ void EventStream::enqueue(const std::string& what) + spdlog::trace("{}: new event, ∑ queue size = {}", peer, len); + queue.push_back(buf); + state = HasEvents; +- res.io_service().post([&res = this->res]() { res.resume(); }); ++ boost::asio::post(res.io_service(), [&res = this->res]() { res.resume(); }); + } + } +-- +2.43.0 + diff --git a/package/rousette/0027-restconf-refactor-uri-parser-for-stream-URIs.patch b/package/rousette/0027-restconf-refactor-uri-parser-for-stream-URIs.patch new file mode 100644 index 00000000..ae690ffd --- /dev/null +++ b/package/rousette/0027-restconf-refactor-uri-parser-for-stream-URIs.patch @@ -0,0 +1,196 @@ +From 97ceef119c900c37bbaa27860c3b43cfa6d69f95 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 13 Jan 2025 13:01:07 +0100 +Subject: [PATCH 27/44] restconf: refactor uri parser for stream URIs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This is a preparation for the upcoming patch that will add support for +subscribed streams. I will use boost spirit x3 parser to parse such +URIs so I need to refactor the existing parser, which is not really a +parser, but we only match for the two known URIs. + +Change-Id: Ib76973cd967fc3e9558ceb6be8f51239ce4c5ef3 +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 15 +-------------- + src/restconf/uri.cpp | 36 +++++++++++++++++++++++++++--------- + src/restconf/uri.h | 11 ++++++----- + src/restconf/uri_impl.h | 2 ++ + tests/uri-parser.cpp | 4 ++-- + 5 files changed, 38 insertions(+), 30 deletions(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index daf23ed..d356f7e 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -874,7 +874,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + + server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable { + auto sess = conn.sessionStart(); +- libyang::DataFormat dataFormat; + std::optional xpathFilter; + std::optional startTime; + std::optional stopTime; +@@ -890,18 +889,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + + auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query); + +- switch(streamRequest.type) { +- case RestconfStreamRequest::Type::NetconfNotificationJSON: +- dataFormat = libyang::DataFormat::JSON; +- break; +- case RestconfStreamRequest::Type::NetconfNotificationXML: +- dataFormat = libyang::DataFormat::XML; +- break; +- default: +- // GCC 14 complains about uninitialized variable, but asRestconfStreamRequest() would have thrown +- __builtin_unreachable(); +- } +- + if (auto it = streamRequest.queryParams.find("filter"); it != streamRequest.queryParams.end()) { + xpathFilter = std::get(it->second); + } +@@ -916,7 +903,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + // The signal is constructed outside NotificationStream class because it is required to be passed to + // NotificationStream's parent (EventStream) constructor where it already must be constructed + // Yes, this is a hack. +- auto client = std::make_shared(req, res, std::make_shared(), sess, dataFormat, xpathFilter, startTime, stopTime); ++ auto client = std::make_shared(req, res, std::make_shared(), sess, streamRequest.type.encoding, xpathFilter, startTime, stopTime); + client->activate(); + } catch (const auth::Error& e) { + processAuthError(req, res, e, [&res]() { +diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp +index da1e3a5..e95d70e 100644 +--- a/src/restconf/uri.cpp ++++ b/src/restconf/uri.cpp +@@ -57,6 +57,13 @@ const auto resources = x3::rule{"resources"} = + ((x3::lit("/") | x3::eps) /* /restconf/ and /restconf */ >> x3::attr(URIPrefix{URIPrefix::Type::RestconfRoot}) >> x3::attr(std::vector{})); + const auto uriGrammar = x3::rule{"grammar"} = x3::lit("/restconf") >> resources; + ++ ++const auto netconfStream = x3::rule{"netconfStream"} = ++ x3::lit("/NETCONF") >> ++ ((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) | ++ (x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON))); ++const auto streamUriGrammar = x3::rule{"streamsGrammar"} = x3::lit("/streams") >> netconfStream; ++ + // clang-format on + } + +@@ -197,6 +204,17 @@ std::optional parseQueryParams(const std::string& quer + return ret; + } + ++std::optional parseStreamUri(const std::string& uriPath) ++{ ++ std::optional ret; ++ ++ if (!x3::parse(std::begin(uriPath), std::end(uriPath), streamUriGrammar >> x3::eoi, ret)) { ++ return std::nullopt; ++ } ++ ++ return ret; ++} ++ + URIPrefix::URIPrefix() + : resourceType(URIPrefix::Type::BasicRestconfData) + { +@@ -637,20 +655,20 @@ std::optional> asYangMod + return std::nullopt; + } + +-RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString) ++RestconfStreamRequest::NetconfStream::NetconfStream() = default; ++RestconfStreamRequest::NetconfStream::NetconfStream(const libyang::DataFormat& encoding) ++ : encoding(encoding) + { +- static const auto netconfStreamRoot = "/streams/NETCONF/"; +- RestconfStreamRequest::Type type; ++} + ++RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString) ++{ + if (httpMethod != "GET" && httpMethod != "HEAD") { + throw ErrorResponse(405, "application", "operation-not-supported", "Method not allowed."); + } + +- if (uriPath == netconfStreamRoot + "XML"s) { +- type = RestconfStreamRequest::Type::NetconfNotificationXML; +- } else if (uriPath == netconfStreamRoot + "JSON"s) { +- type = RestconfStreamRequest::Type::NetconfNotificationJSON; +- } else { ++ auto type = impl::parseStreamUri(uriPath); ++ if (!type) { + throw ErrorResponse(404, "application", "invalid-value", "Invalid stream"); + } + +@@ -661,7 +679,7 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con + + validateQueryParametersForStream(*queryParameters); + +- return {type, *queryParameters}; ++ return {*type, *queryParameters}; + } + + /** @brief Returns a set of allowed HTTP methods for given URI. Usable for the 'allow' header */ +diff --git a/src/restconf/uri.h b/src/restconf/uri.h +index f6df724..6f1f69f 100644 +--- a/src/restconf/uri.h ++++ b/src/restconf/uri.h +@@ -197,11 +197,12 @@ struct RestconfRequest { + }; + + struct RestconfStreamRequest { +- enum class Type { +- NetconfNotificationJSON, +- NetconfNotificationXML, +- }; +- Type type; ++ struct NetconfStream { ++ libyang::DataFormat encoding; ++ ++ NetconfStream(); ++ NetconfStream(const libyang::DataFormat& encoding); ++ } type; + queryParams::QueryParams queryParams; + }; + +diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h +index 2bcdb3f..00b22f6 100644 +--- a/src/restconf/uri_impl.h ++++ b/src/restconf/uri_impl.h +@@ -66,6 +66,8 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::YangModule, name, revision); + BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys); + BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier); + ++BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::RestconfStreamRequest::NetconfStream, encoding); ++ + BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs); + BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs); + BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SemiExpr, lhs, rhs); +diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp +index fd5dd8b..53d1fbb 100644 +--- a/tests/uri-parser.cpp ++++ b/tests/uri-parser.cpp +@@ -1072,13 +1072,13 @@ TEST_CASE("URI path parser") + + { + auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", ""); +- REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationXML); ++ REQUIRE(type.encoding == libyang::DataFormat::XML); + REQUIRE(queryParams.empty()); + } + + { + auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", ""); +- REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationJSON); ++ REQUIRE(type.encoding == libyang::DataFormat::JSON); + REQUIRE(queryParams.empty()); + } + +-- +2.43.0 + diff --git a/package/rousette/0028-server-prepare-checking-if-yang-features-are-enabled.patch b/package/rousette/0028-server-prepare-checking-if-yang-features-are-enabled.patch new file mode 100644 index 00000000..86f6dd8c --- /dev/null +++ b/package/rousette/0028-server-prepare-checking-if-yang-features-are-enabled.patch @@ -0,0 +1,54 @@ +From cc815dac4ea17ccb09a0481ad82745a194efe95f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 27 Jan 2025 19:17:07 +0100 +Subject: [PATCH 28/44] server: prepare checking if yang features are enabled +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +We are not requiring any now, but next commits will need it. I am +splitting this into a separate commit for clarity. + +Change-Id: I0a8874b55b21d5ed6c7222f0c36a36c3c5ff52c5 +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 22 ++++++++++++++-------- + 1 file changed, 14 insertions(+), 8 deletions(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index d356f7e..55e504a 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -817,14 +817,20 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + , server{std::make_unique()} + , dwdmEvents{std::make_unique(conn.sessionStart())} + { +- for (const auto& [module, version] : { +- std::pair{"ietf-restconf", "2017-01-26"}, +- {"ietf-restconf-monitoring", "2017-01-26"}, +- {"ietf-netconf", ""}, +- {"ietf-yang-library", "2019-01-04"}, +- {"ietf-yang-patch", "2017-02-22"}, +- }) { +- if (!conn.sessionStart().getContext().getModuleImplemented(module)) { ++ for (const auto& [module, version, features] : { ++ std::tuple>{"ietf-restconf", "2017-01-26", {}}, ++ {"ietf-restconf-monitoring", "2017-01-26", {}}, ++ {"ietf-netconf", "", {}}, ++ {"ietf-yang-library", "2019-01-04", {}}, ++ {"ietf-yang-patch", "2017-02-22", {}}, ++ }) { ++ if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) { ++ for (const auto& feature : features) { ++ if (!mod->featureEnabled(feature)) { ++ throw std::runtime_error("Module "s + module + "@" + version + " does not implement feature " + feature); ++ } ++ } ++ } else { + throw std::runtime_error("Module "s + module + "@" + version + " is not implemented in sysrepo"); + } + } +-- +2.43.0 + diff --git a/package/rousette/0029-tests-refactor-default-handling.patch b/package/rousette/0029-tests-refactor-default-handling.patch new file mode 100644 index 00000000..f0c207ea --- /dev/null +++ b/package/rousette/0029-tests-refactor-default-handling.patch @@ -0,0 +1,54 @@ +From f45d7cab63431c94a4db859b07ea6f503214ebaa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 13 Mar 2025 17:10:17 +0100 +Subject: [PATCH 29/44] tests: refactor default handling +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +...so that a followup commit which uses +libyang::PrintFlags::KeepEmptyCont introduces less noise. + +Change-Id: I587cf8a5fc0e87b37f4bb91a74d848f58b61e336 +Signed-off-by: Mattias Walström +--- + tests/restconf-defaults.cpp | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp +index dd8b4da..d5c6004 100644 +--- a/tests/restconf-defaults.cpp ++++ b/tests/restconf-defaults.cpp +@@ -32,7 +32,7 @@ TEST_CASE("default handling") + auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT}; + + // default value of /example:a/b/c/enabled is implicitly set so it should not be printed +- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({ ++ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({ + + } + )"}); +@@ -72,7 +72,7 @@ TEST_CASE("default handling") + )"}); + + // default value is explicitly set so it should be printed +- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({ ++ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({ + "example:a": { + "b": { + "c": { +@@ -86,8 +86,8 @@ TEST_CASE("default handling") + // RFC 6243, sec. 2.3.3: A valid 'delete' operation attribute for a data node that has been set by a client to its schema default value MUST succeed. + REQUIRE(httpDelete(RESTCONF_DATA_ROOT "/example:a/b/c/enabled", {AUTH_ROOT}) == Response{204, noContentTypeHeaders, ""}); + +- // default value is implicitly set so it should be printed +- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({ ++ // default value is only there implicitly, so it should *not* be printed ++ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({ + + } + )"}); +-- +2.43.0 + diff --git a/package/rousette/0030-Always-print-empty-containers-in-user-defined-conten.patch b/package/rousette/0030-Always-print-empty-containers-in-user-defined-conten.patch new file mode 100644 index 00000000..4fbcbe27 --- /dev/null +++ b/package/rousette/0030-Always-print-empty-containers-in-user-defined-conten.patch @@ -0,0 +1,356 @@ +From 22442d322bda3d83457dd0f1da45d95a10e93081 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 13 Mar 2025 17:32:59 +0100 +Subject: [PATCH 30/44] Always print empty containers in user-defined content +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +There's some subtle interaction between depth-limited printing, default +values, and empty non-presence containers. In order to prepare for an +upstream change in sysrepo which fixed depth-limited printing, let's +stick to "always" printing the empty non-presence containers. + +(It actually isn't "always" because the data such as error reports, +patches, etc., are still printed using the default settings. But the +data which is subject to user-defined constraints is now consistent.) + +Change-Id: Ie2d6ad2e851d667e7e7582fcd0ac75dabce2c05e +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 4 +- + src/restconf/utils/yang.cpp | 2 +- + src/sr/AllEvents.cpp | 2 +- + src/sr/OpticalEvents.cpp | 2 +- + tests/restconf-defaults.cpp | 12 +++++- + tests/restconf-nacm.cpp | 12 +++++- + tests/restconf-reading.cpp | 79 ++++++++++++++++++++++++++++++++++--- + 7 files changed, 98 insertions(+), 15 deletions(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index 55e504a..bae3992 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -478,7 +478,7 @@ void processActionOrRPC(std::shared_ptr requestCtx, const std::c + contentType(requestCtx->dataFormat.response), + CORS, + }); +- requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings)); ++ requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont)); + } + + void processPost(std::shared_ptr requestCtx, const std::chrono::milliseconds timeout) +@@ -768,7 +768,7 @@ libyang::PrintFlags libyangPrintFlags(const libyang::DataNode& dataNode, const s + } catch(const libyang::Error& e) { + } + +- libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings; ++ libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont; + + if (!withDefaults && node && (node->schema().nodeType() == libyang::NodeType::Leaf || node->schema().nodeType() == libyang::NodeType::Leaflist) && node->asTerm().isImplicitDefault()) { + return ret | libyang::PrintFlags::WithDefaultsAll; +diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp +index 30661fc..8acb160 100644 +--- a/src/restconf/utils/yang.cpp ++++ b/src/restconf/utils/yang.cpp +@@ -111,7 +111,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF + envelope->insertChild(*eventTime); + envelope->insertChild(notification); + +- auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings); ++ auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont); + + // notification node comes from sysrepo and sysrepo will free this; if not unlinked then envelope destructor would try to free this as well + notification.unlink(); +diff --git a/src/sr/AllEvents.cpp b/src/sr/AllEvents.cpp +index 6e2a21c..2aca4c7 100644 +--- a/src/sr/AllEvents.cpp ++++ b/src/sr/AllEvents.cpp +@@ -109,7 +109,7 @@ sysrepo::ErrorCode AllEvents::onChange(sysrepo::Session session, const std::stri + break; + } + }; +- auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings); ++ auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont); + spdlog::info("JSON: {}", json); + spdlog::warn("FULL JSON: {}", + *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)); +diff --git a/src/sr/OpticalEvents.cpp b/src/sr/OpticalEvents.cpp +index 8c5feff..25906a8 100644 +--- a/src/sr/OpticalEvents.cpp ++++ b/src/sr/OpticalEvents.cpp +@@ -15,7 +15,7 @@ + namespace { + std::string dumpDataFrom(sysrepo::Session session, const std::string& module) + { +- return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings); ++ return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont); + } + } + +diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp +index d5c6004..89083f1 100644 +--- a/tests/restconf-defaults.cpp ++++ b/tests/restconf-defaults.cpp +@@ -33,7 +33,11 @@ TEST_CASE("default handling") + + // default value of /example:a/b/c/enabled is implicitly set so it should not be printed + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({ +- ++ "example:a": { ++ "b": { ++ "c": {} ++ } ++ } + } + )"}); + +@@ -88,7 +92,11 @@ TEST_CASE("default handling") + + // default value is only there implicitly, so it should *not* be printed + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({ +- ++ "example:a": { ++ "b": { ++ "c": {} ++ } ++ } + } + )"}); + } +diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp +index 29d7723..51328f3 100644 +--- a/tests/restconf-nacm.cpp ++++ b/tests/restconf-nacm.cpp +@@ -130,6 +130,9 @@ TEST_CASE("NACM") + "clock": { + "timezone-utc-offset": 2 + }, ++ "dns-resolver": { ++ "options": {} ++ }, + "radius": { + "server": [ + { +@@ -139,7 +142,8 @@ TEST_CASE("NACM") + "shared-secret": "shared-secret" + } + } +- ] ++ ], ++ "options": {} + } + } + } +@@ -207,6 +211,9 @@ TEST_CASE("NACM") + "clock": { + "timezone-utc-offset": 2 + }, ++ "dns-resolver": { ++ "options": {} ++ }, + "radius": { + "server": [ + { +@@ -216,7 +223,8 @@ TEST_CASE("NACM") + "shared-secret": "shared-secret" + } + } +- ] ++ ], ++ "options": {} + } + } + } +diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp +index e709486..ffdb047 100644 +--- a/tests/restconf-reading.cpp ++++ b/tests/restconf-reading.cpp +@@ -62,10 +62,22 @@ TEST_CASE("reading data") + // this relies on a NACM rule for anonymous access that filters out "a lot of stuff" + REQUIRE(get(RESTCONF_DATA_ROOT, {}) == Response{200, jsonHeaders, R"({ + "example:top-level-leaf": "moo", ++ "example:tlc": {}, ++ "example:a": { ++ "b": { ++ "c": {} ++ }, ++ "b1": {}, ++ "example-augment:b": { ++ "c": {} ++ } ++ }, ++ "example:two-leafs": {}, + "example:config-nonconfig": { + "config-node": "foo-config-true", + "nonconfig-node": "foo-config-false" + }, ++ "example:ordered-lists": {}, + "ietf-restconf-monitoring:restconf-state": { + "capabilities": { + "capability": [ +@@ -107,10 +119,22 @@ TEST_CASE("reading data") + + REQUIRE(get(RESTCONF_ROOT_DS("operational"), {}) == Response{200, jsonHeaders, R"({ + "example:top-level-leaf": "moo", ++ "example:tlc": {}, ++ "example:a": { ++ "b": { ++ "c": {} ++ }, ++ "b1": {}, ++ "example-augment:b": { ++ "c": {} ++ } ++ }, ++ "example:two-leafs": {}, + "example:config-nonconfig": { + "config-node": "foo-config-true", + "nonconfig-node": "foo-config-false" + }, ++ "example:ordered-lists": {}, + "ietf-restconf-monitoring:restconf-state": { + "capabilities": { + "capability": [ +@@ -152,9 +176,21 @@ TEST_CASE("reading data") + + REQUIRE(get(RESTCONF_ROOT_DS("running"), {}) == Response{200, jsonHeaders, R"({ + "example:top-level-leaf": "moo", ++ "example:tlc": {}, ++ "example:a": { ++ "b": { ++ "c": {} ++ }, ++ "b1": {}, ++ "example-augment:b": { ++ "c": {} ++ } ++ }, ++ "example:two-leafs": {}, + "example:config-nonconfig": { + "config-node": "foo-config-true" +- } ++ }, ++ "example:ordered-lists": {} + } + )"}); + } +@@ -213,7 +249,8 @@ TEST_CASE("reading data") + { + "name": "a" + } +- ] ++ ], ++ "options": {} + } + } + } +@@ -244,7 +281,8 @@ TEST_CASE("reading data") + "shared-secret": "shared-secret" + } + } +- ] ++ ], ++ "options": {} + } + } + } +@@ -714,6 +752,7 @@ TEST_CASE("reading data") + "enabled": true + } + }, ++ "b1": {}, + "example-augment:b": { + "c": { + "enabled": true +@@ -723,9 +762,18 @@ TEST_CASE("reading data") + } + )"}); + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=explicit", {}) == Response{200, jsonHeaders, R"({ +- ++ "example:a": { ++ "b": { ++ "c": {} ++ }, ++ "b1": {}, ++ "example-augment:b": { ++ "c": {} ++ } ++ } + } + )"}); ++ // FIXME: libyang is not really consistent in printing of NP-containers when trimming away the defaults... + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({ + + } +@@ -740,6 +788,7 @@ TEST_CASE("reading data") + } + } + }, ++ "b1": {}, + "example-augment:b": { + "c": { + "enabled": true, +@@ -766,6 +815,7 @@ TEST_CASE("reading data") + "enabled": true + } + }, ++ "b1": {}, + "example-augment:b": { + "c": { + "enabled": true +@@ -781,13 +831,24 @@ TEST_CASE("reading data") + "c": { + "enabled": true + } ++ }, ++ "b1": {}, ++ "example-augment:b": { ++ "c": {} + } + } + } + )"}); + ++ // FIXME: libyang is not consistent here: ++ // - /example:a/b/c NP-container *is* printed, ++ // - /example:a/example-augment:b/c NP-container is *not* printed + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({ +- ++ "example:a": { ++ "b": { ++ "c": {} ++ } ++ } + } + )"}); + +@@ -801,6 +862,7 @@ TEST_CASE("reading data") + } + } + }, ++ "b1": {}, + "example-augment:b": { + "c": { + "enabled": true, +@@ -830,10 +892,15 @@ TEST_CASE("reading data") + )"}); + + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled?with-defaults=explicit", {}) == Response{200, jsonHeaders, R"({ +- ++ "example:a": { ++ "b": { ++ "c": {} ++ } ++ } + } + )"}); + ++ // again, libyang is not 100% consistent in the `trim` mode + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({ + + } +-- +2.43.0 + diff --git a/package/rousette/0031-fix-a-typo-in-RFC-number.patch b/package/rousette/0031-fix-a-typo-in-RFC-number.patch new file mode 100644 index 00000000..55f4ae9d --- /dev/null +++ b/package/rousette/0031-fix-a-typo-in-RFC-number.patch @@ -0,0 +1,31 @@ +From 818af7ec1dc890774603dbf11ed81cf64d89a628 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 13 Mar 2025 17:56:50 +0100 +Subject: [PATCH 31/44] fix a typo in RFC number +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I81b68b3920c41129cb98728481580699e44e8c20 +Signed-off-by: Mattias Walström +--- + tests/restconf-reading.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp +index ffdb047..38f5496 100644 +--- a/tests/restconf-reading.cpp ++++ b/tests/restconf-reading.cpp +@@ -879,7 +879,7 @@ TEST_CASE("reading data") + + SECTION("Implicit node with default value") + { +- // RFC 4080, 3.5.4: If target of the query is implicitly created node with default value, ignore basic mode ++ // RFC 8040, 3.5.4: If target of the query is implicitly created node with default value, ignore basic mode + REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled", {}) == Response{200, jsonHeaders, R"({ + "example:a": { + "b": { +-- +2.43.0 + diff --git a/package/rousette/0032-Fix-bug-in-depth-processing.patch b/package/rousette/0032-Fix-bug-in-depth-processing.patch new file mode 100644 index 00000000..e82d50ef --- /dev/null +++ b/package/rousette/0032-Fix-bug-in-depth-processing.patch @@ -0,0 +1,104 @@ +From 9997d61dc43775444e4d78c37054a30b602c2603 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Thu, 13 Mar 2025 18:11:39 +0100 +Subject: [PATCH 32/44] Fix bug in depth= processing +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +...which got fixed upstream in sysrepo. + +Change-Id: Ida37c48058488be32230165b8b4f45c6bd5a4d3a +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8429 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 1 + + tests/restconf-reading.cpp | 45 ++++++++++++++++++++++++++++++++++++++ + 2 files changed, 46 insertions(+) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index ca41ef3..272caad 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -74,6 +74,7 @@ find_package(PkgConfig) + pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23 + find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread) + ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET) + pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5) + pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3) + pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd) +diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp +index 38f5496..f87c4f5 100644 +--- a/tests/restconf-reading.cpp ++++ b/tests/restconf-reading.cpp +@@ -243,6 +243,13 @@ TEST_CASE("reading data") + )"}); + + REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius?depth=1", {AUTH_DWDM}) == Response{200, jsonHeaders, R"({ ++ "ietf-system:system": { ++ "radius": {} ++ } ++} ++)"}); ++ ++ REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius?depth=2", {AUTH_DWDM}) == Response{200, jsonHeaders, R"({ + "ietf-system:system": { + "radius": { + "server": [ +@@ -1026,6 +1033,25 @@ TEST_CASE("reading data") + } + )"}); + REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=nested/data&depth=1", {}) == Response{200, jsonHeaders, R"({ ++ "example:tlc": { ++ "list": [ ++ { ++ "name": "blabla", ++ "nested": [ ++ { ++ "first": "1", ++ "second": 2, ++ "third": "3", ++ "data": {} ++ } ++ ] ++ } ++ ] ++ } ++} ++)"}); ++ ++ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=nested/data&depth=2", {}) == Response{200, jsonHeaders, R"({ + "example:tlc": { + "list": [ + { +@@ -1049,6 +1075,25 @@ TEST_CASE("reading data") + + // whole datastore with fields filtering + REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/list/nested/data&depth=1", {}) == Response{200, jsonHeaders, R"({ ++ "example:tlc": { ++ "list": [ ++ { ++ "name": "blabla", ++ "nested": [ ++ { ++ "first": "1", ++ "second": 2, ++ "third": "3", ++ "data": {} ++ } ++ ] ++ } ++ ] ++ } ++} ++)"}); ++ ++ REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/list/nested/data&depth=2", {}) == Response{200, jsonHeaders, R"({ + "example:tlc": { + "list": [ + { +-- +2.43.0 + diff --git a/package/rousette/0033-Adapt-to-libyang-cpp-API-break-in-opaque-nodes.patch b/package/rousette/0033-Adapt-to-libyang-cpp-API-break-in-opaque-nodes.patch new file mode 100644 index 00000000..b3988f96 --- /dev/null +++ b/package/rousette/0033-Adapt-to-libyang-cpp-API-break-in-opaque-nodes.patch @@ -0,0 +1,89 @@ +From 009b2b01fb4a1ca854402debd66d899a264db8f2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 14 Mar 2025 13:57:26 +0100 +Subject: [PATCH 33/44] Adapt to libyang-cpp API break in opaque nodes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +I was wondering for a while whether libyang will do the right thing when +printing the RPC's output nodes in XML, but apparently it does (and we +have a test for that). + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8438 +Change-Id: I82425743fd2613fcbe2b696848e2feb1c47fc658 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + src/restconf/Server.cpp | 5 +++-- + src/restconf/utils/yang.cpp | 9 +++++---- + 3 files changed, 9 insertions(+), 7 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 272caad..bcabe84 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -76,7 +76,7 @@ find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread) + + pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET) + pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5) +-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3) ++pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=4) + pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd) + pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam) + pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt) +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index bae3992..28e2e4c 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -471,7 +471,8 @@ void processActionOrRPC(std::shared_ptr requestCtx, const std::c + auto responseNode = rpcReply->child(); + responseNode->unlinkWithSiblings(); + +- auto envelope = ctx.newOpaqueJSON(rpcNode->schema().module().name(), "output", std::nullopt); ++ // libyang auto-resolves the XML namespace when the result is printed into XML ++ auto envelope = ctx.newOpaqueJSON({rpcNode->schema().module().name(), rpcNode->schema().module().name(), "output"}, std::nullopt); + envelope->insertChild(*responseNode); + + requestCtx->res.write_head(200, { +@@ -735,7 +736,7 @@ libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest + } + + for (const auto& rpc : mod.actionRpcs()) { +- operations.insertChild(*ctx.newOpaqueJSON(rpc.module().name(), rpc.name(), libyang::JSON{"[null]"})); ++ operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"})); + } + } + } else { +diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp +index 8acb160..9358840 100644 +--- a/src/restconf/utils/yang.cpp ++++ b/src/restconf/utils/yang.cpp +@@ -89,6 +89,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF + { + static const auto jsonNamespace = "ietf-restconf"; + static const auto xmlNamespace = "urn:ietf:params:xml:ns:netconf:notification:1.0"; ++ static const auto xmlPrefix = "ietf-netconf-notifications"; + + std::optional envelope; + std::optional eventTime; +@@ -96,11 +97,11 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF + + /* The namespaces for XML and JSON envelopes are different. See https://datatracker.ietf.org/doc/html/rfc8040#section-6.4 */ + if (dataFormat == libyang::DataFormat::JSON) { +- envelope = ctx.newOpaqueJSON(jsonNamespace, "notification", std::nullopt); +- eventTime = ctx.newOpaqueJSON(jsonNamespace, "eventTime", libyang::JSON{timeStr}); ++ envelope = ctx.newOpaqueJSON({jsonNamespace, jsonNamespace, "notification"}, std::nullopt); ++ eventTime = ctx.newOpaqueJSON({jsonNamespace, jsonNamespace, "eventTime"}, libyang::JSON{timeStr}); + } else { +- envelope = ctx.newOpaqueXML(xmlNamespace, "notification", std::nullopt); +- eventTime = ctx.newOpaqueXML(xmlNamespace, "eventTime", libyang::XML{timeStr}); ++ envelope = ctx.newOpaqueXML({xmlNamespace, xmlPrefix, "notification"}, std::nullopt); ++ eventTime = ctx.newOpaqueXML({xmlNamespace, xmlPrefix, "eventTime"}, libyang::XML{timeStr}); + } + + // the notification data node holds only the notification data tree but for nested notification we should print the whole YANG data tree +-- +2.43.0 + diff --git a/package/rousette/0034-Fix-XML-serialization-when-listing-RPCs.patch b/package/rousette/0034-Fix-XML-serialization-when-listing-RPCs.patch new file mode 100644 index 00000000..4a853b50 --- /dev/null +++ b/package/rousette/0034-Fix-XML-serialization-when-listing-RPCs.patch @@ -0,0 +1,122 @@ +From 061d960d9435018aaba3b2d1b4d857ddb8836d1b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 14 Mar 2025 15:38:44 +0100 +Subject: [PATCH 34/44] Fix XML serialization when listing RPCs +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +The RFC says [1] that each RPC entry that's listed in the reply should +look like this when serialized into the XML format: + + + +Our old code would, due to the way how libyang deals with opaque nodes, +just try to dump the JSON-ish `[null]` bits in there, which is wrong. + +[1] https://datatracker.ietf.org/doc/html/rfc8040#page-84 + +Change-Id: I6ea433dbd3fcf3ca2883d3fa00c9d215c0e1bb4f +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/br2-external/+/8443 +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 16 +++++++++++++--- + tests/restconf-rpc.cpp | 33 ++++++++++++++++++++++++++++++++- + 2 files changed, 45 insertions(+), 4 deletions(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index 28e2e4c..05239e9 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -712,7 +712,7 @@ void processPutOrPlainPatch(std::shared_ptr requestCtx, const st + } + + /** @brief Build data trees for endpoints returning ietf-restconf:restconf data */ +-libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest::Type& type) ++libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest::Type& type, libyang::DataFormat dataFormat) + { + const auto yangLib = *ctx.getModuleLatest("ietf-yang-library"); + const auto yangApiExt = ctx.getModuleImplemented("ietf-restconf")->extensionInstance("yang-api"); +@@ -736,7 +736,16 @@ libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest + } + + for (const auto& rpc : mod.actionRpcs()) { +- operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"})); ++ switch (dataFormat) { ++ case libyang::DataFormat::JSON: ++ operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"})); ++ break; ++ case libyang::DataFormat::XML: ++ operations.insertChild(*ctx.newOpaqueXML({rpc.module().ns(), rpc.module().name(), rpc.name()}, std::nullopt)); ++ break; ++ default: ++ throw std::logic_error{"Invalid data format for apiResource()"}; ++ } + } + } + } else { +@@ -987,7 +996,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + case RestconfRequest::Type::YangLibraryVersion: + case RestconfRequest::Type::ListRPC: + res.write_head(200, {contentType(dataFormat.response), CORS}); +- res.end(*apiResource(sess.getContext(), restconfRequest.type).printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont)); ++ res.end(*apiResource(sess.getContext(), restconfRequest.type, dataFormat.response) ++ .printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont)); + break; + + case RestconfRequest::Type::GetData: { +diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp +index c4229a0..d49b121 100644 +--- a/tests/restconf-rpc.cpp ++++ b/tests/restconf-rpc.cpp +@@ -79,7 +79,9 @@ TEST_CASE("invoking actions and rpcs") + + SECTION("List RPCs") + { +- REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT}) == Response{200, jsonHeaders, R"({ ++ SECTION("JSON") ++ { ++ REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT}) == Response{200, jsonHeaders, R"({ + "ietf-restconf:restconf": { + "operations": { + "ietf-factory-default:factory-reset": [null], +@@ -103,6 +105,35 @@ TEST_CASE("invoking actions and rpcs") + } + } + )"}); ++ } ++ ++ SECTION("XML") ++ { ++ REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT, CONTENT_TYPE_XML}) ++ == Response{200, xmlHeaders, ++ R"( ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++)"}); ++ } + } + + SECTION("RPC") +-- +2.43.0 + diff --git a/package/rousette/0035-make-sure-that-edit-tree-is-the-first-sibling.patch b/package/rousette/0035-make-sure-that-edit-tree-is-the-first-sibling.patch new file mode 100644 index 00000000..8166d457 --- /dev/null +++ b/package/rousette/0035-make-sure-that-edit-tree-is-the-first-sibling.patch @@ -0,0 +1,39 @@ +From 6ad606cb9ad49ca7bfb8056b9695ab271a33e790 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 26 Mar 2025 18:56:06 +0100 +Subject: [PATCH 35/44] make sure that edit tree is the first sibling +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +After a bug in velia [1], where we passed to Session::editBatch a tree +which was not the first sibling (and this caused that all the left +siblings of the node were not considered in the edit), I am making sure +that we do not lose any data in this project due to this too. + +[1] https://github.com/CESNET/velia/commit/2b7187405740fe61e8b37e0efab8c3173d398a26 + +Fixes: 25f3e3b ("restconf: support for YANG patch") +Change-Id: I7211168325fbfddd75dc1520c9dd83e31138d3bc +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index 05239e9..c454541 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -600,7 +600,7 @@ void processYangPatchEdit(const std::shared_ptr& requestCtx, con + if (!mergedEdits) { + mergedEdits = *singleEdit; + } else { +- mergedEdits->insertSibling(*singleEdit); ++ mergedEdits = mergedEdits->insertSibling(*singleEdit); // make sure we point to the first sibling, sysrepo::editBatch requires that + } + } + +-- +2.43.0 + diff --git a/package/rousette/0036-restconf-introduce-internal-RPC-handling-for-subscri.patch b/package/rousette/0036-restconf-introduce-internal-RPC-handling-for-subscri.patch new file mode 100644 index 00000000..b1e6d309 --- /dev/null +++ b/package/rousette/0036-restconf-introduce-internal-RPC-handling-for-subscri.patch @@ -0,0 +1,5532 @@ +From 854e20a328270ff980f0a864f72def1cc84e716f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 27 Jan 2025 16:53:33 +0100 +Subject: [PATCH 36/44] restconf: introduce internal RPC handling for + subscribed notifications +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This commit adds support for establishing subscribed notifications [1,2] +in the URI parser and server processing. + +We are going to implement the required RPC handlers for creating +subscribed notifications directly in rousette on the HTTP-level, +i.e., we are not going to pass those RPC calls to sysrepo. + +The Netopeer2 project already implements the RPC callbacks for subscribed +notifications RPCs. Sysrepo can handle multiple RPC callbacks, but only +one of them can return data. The other will be executed but the data will +be ignored [4]. + +It makes no sense to establish subscriptions through NETCONF and then +request the stream via RESTCONF and AFAIK it is even not allowed by RFCs +which states that NETCONF requests are handled by the same NETCONF +session and also, there is no way how to get to those subscriptions via +RESTCONF. RESTCONF provides its own way how to read the streamed +notifications [3,4]. + +And yes, we want both Netopeer2 and Rousette to be able to run at the +same time on the same machine. + +[1] https://datatracker.ietf.org/doc/html/rfc8639 +[2] https://datatracker.ietf.org/doc/html/rfc8650 +[3] https://datatracker.ietf.org/doc/html/rfc8640 +[4] https://github.com/sysrepo/sysrepo/blob/ef93a1253cc97f13671759f6e7790cbf729a5ae9/doc/subs.dox#L78 + +Change-Id: I207e4d5b0d34eea4477c50019e9cab4f8e81e1a6 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 8 + + src/restconf/Server.cpp | 18 +- + src/restconf/uri.cpp | 15 +- + src/restconf/uri.h | 1 + + tests/restconf-rpc.cpp | 8 + + tests/uri-parser.cpp | 13 +- + yang/iana-if-type@2014-05-08.yang | 1523 +++++++++++++++++ + yang/ietf-interfaces@2018-02-20.yang | 1123 ++++++++++++ + yang/ietf-ip@2018-02-22.yang | 876 ++++++++++ + yang/ietf-network-instance@2019-01-21.yang | 282 +++ + ...f-subscribed-notifications@2019-11-17.yang | 85 + + ...f-subscribed-notifications@2019-09-09.yang | 1350 +++++++++++++++ + 12 files changed, 5298 insertions(+), 4 deletions(-) + create mode 100644 yang/iana-if-type@2014-05-08.yang + create mode 100644 yang/ietf-interfaces@2018-02-20.yang + create mode 100644 yang/ietf-ip@2018-02-22.yang + create mode 100644 yang/ietf-network-instance@2019-01-21.yang + create mode 100644 yang/ietf-restconf-subscribed-notifications@2019-11-17.yang + create mode 100644 yang/ietf-subscribed-notifications@2019-09-09.yang + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index bcabe84..2287447 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -146,6 +146,12 @@ install(FILES + ${CMAKE_SOURCE_DIR}/yang/ietf-restconf@2017-01-26.yang + ${CMAKE_SOURCE_DIR}/yang/ietf-restconf-monitoring@2017-01-26.yang + ${CMAKE_SOURCE_DIR}/yang/ietf-yang-patch@2017-02-22.yang ++ ${CMAKE_SOURCE_DIR}/yang/iana-if-type@2014-05-08.yang ++ ${CMAKE_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang ++ ${CMAKE_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang ++ ${CMAKE_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang ++ ${CMAKE_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang ++ ${CMAKE_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/yang/modules/rousette) + + include(CTest) +@@ -205,6 +211,8 @@ if(BUILD_TESTING) + --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf@2017-01-26.yang + --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-monitoring@2017-01-26.yang + --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-yang-patch@2017-02-22.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1 + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-augment.yang +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index c454541..9fd3fa9 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -430,6 +430,12 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std: + return {editNode, replacementNode}; + } + ++std::optional processInternalRPC(sysrepo::Session&, const libyang::DataNode&) ++{ ++ // TODO: Implement internal RPCs ++ throw ErrorResponse(501, "application", "operation-not-supported", "Internal RPCs are not yet supported."); ++} ++ + void processActionOrRPC(std::shared_ptr requestCtx, const std::chrono::milliseconds timeout) + { + requestCtx->sess.switchDatastore(sysrepo::Datastore::Operational); +@@ -460,7 +466,12 @@ void processActionOrRPC(std::shared_ptr requestCtx, const std::c + rpcNode->parseOp(requestCtx->payload, *requestCtx->dataFormat.request, libyang::OperationType::RpcRestconf); + } + +- auto rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout); ++ std::optional rpcReply; ++ if (requestCtx->restconfRequest.type == RestconfRequest::Type::Execute) { ++ rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout); ++ } else if (requestCtx->restconfRequest.type == RestconfRequest::Type::ExecuteInternal) { ++ rpcReply = processInternalRPC(requestCtx->sess, *rpcNode); ++ } + + if (!rpcReply || rpcReply->immediateChildren().empty()) { + requestCtx->res.write_head(204, {CORS}); +@@ -833,6 +844,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + {"ietf-netconf", "", {}}, + {"ietf-yang-library", "2019-01-04", {}}, + {"ietf-yang-patch", "2017-02-22", {}}, ++ {"ietf-subscribed-notifications", "2019-09-09", {}}, ++ {"ietf-restconf-subscribed-notifications", "2019-11-17", {}}, + }) { + if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) { + for (const auto& feature : features) { +@@ -1120,7 +1133,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + res.end(); + break; + +- case RestconfRequest::Type::Execute: { ++ case RestconfRequest::Type::Execute: ++ case RestconfRequest::Type::ExecuteInternal: { + auto requestCtx = std::make_shared(req, res, dataFormat, sess, restconfRequest); + + req.on_data([requestCtx, timeout](const uint8_t* data, std::size_t length) { +diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp +index e95d70e..956c155 100644 +--- a/src/restconf/uri.cpp ++++ b/src/restconf/uri.cpp +@@ -555,6 +555,19 @@ std::vector asPathSegments(const std::string& uriPath) + return uri->segments; + } + ++/** @brief Checks if the RPC with this schema path should be handled internally by the RESTCONF server */ ++constexpr bool isInternalRPCPath(const std::string& schemaPath) ++{ ++ std::vector arr{ ++ "/ietf-subscribed-notifications:establish-subscription", ++ "/ietf-subscribed-notifications:modify-subscription", ++ "/ietf-subscribed-notifications:delete-subscription", ++ "/ietf-subscribed-notifications:kill-subscription", ++ }; ++ ++ return std::find(arr.begin(), arr.end(), schemaPath) != arr.end(); ++} ++ + /** @brief Parse requested URL as a RESTCONF requested + * + * The URI path (i.e., a resource identifier) will be parsed into an action that is supposed to be performed, +@@ -601,7 +614,7 @@ RestconfRequest asRestconfRequest(const libyang::Context& ctx, const std::string + } else if (httpMethod == "DELETE" && schemaNode) { + type = RestconfRequest::Type::DeleteNode; + } else if (httpMethod == "POST" && schemaNode && (schemaNode->nodeType() == libyang::NodeType::Action || schemaNode->nodeType() == libyang::NodeType::RPC)) { +- type = RestconfRequest::Type::Execute; ++ type = isInternalRPCPath(schemaNode->path()) ? RestconfRequest::Type::ExecuteInternal : RestconfRequest::Type::Execute; + } else if (httpMethod == "POST" && (uri->prefix.resourceType == impl::URIPrefix::Type::BasicRestconfData || uri->prefix.resourceType == impl::URIPrefix::Type::NMDADatastore)) { + type = RestconfRequest::Type::CreateChildren; + } else if (httpMethod == "PATCH") { +diff --git a/src/restconf/uri.h b/src/restconf/uri.h +index 6f1f69f..d9dd29f 100644 +--- a/src/restconf/uri.h ++++ b/src/restconf/uri.h +@@ -180,6 +180,7 @@ struct RestconfRequest { + CreateOrReplaceThisNode, ///< PUT on a data resource + DeleteNode, ///< DELETE on a data resource + Execute, ///< POST on a operation resource (Execute an RPC or an action) ++ ExecuteInternal, ///< POST on a operation resource (RPC or action) that is not sent to sysrepo but handled in roustte directly + CreateChildren, ///< POST on a data resource + YangLibraryVersion, ///< Report ietf-yang-library version + OptionsQuery, ///< Request for allowed HTTP methods for a path +diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp +index d49b121..a3e9309 100644 +--- a/tests/restconf-rpc.cpp ++++ b/tests/restconf-rpc.cpp +@@ -97,6 +97,10 @@ TEST_CASE("invoking actions and rpcs") + "ietf-system:set-current-datetime": [null], + "ietf-system:system-restart": [null], + "ietf-system:system-shutdown": [null], ++ "ietf-subscribed-notifications:establish-subscription": [null], ++ "ietf-subscribed-notifications:modify-subscription": [null], ++ "ietf-subscribed-notifications:delete-subscription": [null], ++ "ietf-subscribed-notifications:kill-subscription": [null], + "example:test-rpc": [null], + "example:test-rpc-no-output": [null], + "example:test-rpc-no-input": [null], +@@ -126,6 +130,10 @@ TEST_CASE("invoking actions and rpcs") + + + ++ ++ ++ ++ + + + +diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp +index 53d1fbb..23c2b79 100644 +--- a/tests/uri-parser.cpp ++++ b/tests/uri-parser.cpp +@@ -63,6 +63,8 @@ TEST_CASE("URI path parser") + auto ctx = libyang::Context{std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "tests" / "yang"}; + ctx.loadModule("example", std::nullopt, {"f1"}); + ctx.loadModule("example-augment"); ++ ctx.setSearchDir(std::filesystem::path{CMAKE_CURRENT_SOURCE_DIR} / "yang"); ++ ctx.loadModule("ietf-subscribed-notifications"); + + SECTION("Valid paths") + { +@@ -398,23 +400,32 @@ TEST_CASE("URI path parser") + { + std::string uri; + std::string expectedPath; ++ RestconfRequest::Type expectedRequestType; + + SECTION("RPC") + { + uri = "/restconf/operations/example:test-rpc"; + expectedPath = "/example:test-rpc"; ++ expectedRequestType = RestconfRequest::Type::Execute; + } + SECTION("Action") + { + uri = "/restconf/data/example:tlc/list=hello-world/example-action"; + expectedPath = "/example:tlc/list[name='hello-world']/example-action"; ++ expectedRequestType = RestconfRequest::Type::Execute; ++ } ++ SECTION("Internally handled RPC") ++ { ++ uri = "/restconf/operations/ietf-subscribed-notifications:establish-subscription"; ++ expectedPath = "/ietf-subscribed-notifications:establish-subscription"; ++ expectedRequestType = RestconfRequest::Type::ExecuteInternal; + } + + CAPTURE(uri); + auto [action, datastore, path, queryParams] = rousette::restconf::asRestconfRequest(ctx, "POST", uri); + REQUIRE(path == expectedPath); + REQUIRE(datastore == std::nullopt); +- REQUIRE(action == RestconfRequest::Type::Execute); ++ REQUIRE(action == expectedRequestType); + REQUIRE(queryParams.empty()); + } + +diff --git a/yang/iana-if-type@2014-05-08.yang b/yang/iana-if-type@2014-05-08.yang +new file mode 100644 +index 0000000..81b2175 +--- /dev/null ++++ b/yang/iana-if-type@2014-05-08.yang +@@ -0,0 +1,1523 @@ ++module iana-if-type { ++ namespace "urn:ietf:params:xml:ns:yang:iana-if-type"; ++ prefix ianaift; ++ ++ import ietf-interfaces { ++ prefix if; ++ } ++ ++ organization "IANA"; ++ contact ++ " Internet Assigned Numbers Authority ++ ++ Postal: ICANN ++ 4676 Admiralty Way, Suite 330 ++ Marina del Rey, CA 90292 ++ ++ Tel: +1 310 823 9358 ++ "; ++ description ++ "This YANG module defines YANG identities for IANA-registered ++ interface types. ++ ++ This YANG module is maintained by IANA and reflects the ++ 'ifType definitions' registry. ++ ++ The latest revision of this YANG module can be obtained from ++ the IANA web site. ++ ++ Requests for new values should be made to IANA via ++ email (iana@iana.org). ++ ++ Copyright (c) 2014 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (http://trustee.ietf.org/license-info). ++ ++ The initial version of this YANG module is part of RFC 7224; ++ see the RFC itself for full legal notices."; ++ reference ++ "IANA 'ifType definitions' registry. ++ "; ++ ++ revision 2014-05-08 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 7224: IANA Interface Type YANG Module"; ++ } ++ ++ identity iana-interface-type { ++ base if:interface-type; ++ description ++ "This identity is used as a base for all interface types ++ defined in the 'ifType definitions' registry."; ++ } ++ ++ identity other { ++ base iana-interface-type; ++ } ++ identity regular1822 { ++ base iana-interface-type; ++ } ++ identity hdh1822 { ++ base iana-interface-type; ++ } ++ identity ddnX25 { ++ base iana-interface-type; ++ } ++ identity rfc877x25 { ++ base iana-interface-type; ++ reference ++ "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer"; ++ } ++ identity ethernetCsmacd { ++ base iana-interface-type; ++ description ++ "For all Ethernet-like interfaces, regardless of speed, ++ as per RFC 3635."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity iso88023Csmacd { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Deprecated via RFC 3635. ++ Use ethernetCsmacd(6) instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity iso88024TokenBus { ++ base iana-interface-type; ++ } ++ identity iso88025TokenRing { ++ base iana-interface-type; ++ } ++ identity iso88026Man { ++ base iana-interface-type; ++ } ++ identity starLan { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Deprecated via RFC 3635. ++ Use ethernetCsmacd(6) instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity proteon10Mbit { ++ base iana-interface-type; ++ } ++ identity proteon80Mbit { ++ base iana-interface-type; ++ } ++ identity hyperchannel { ++ base iana-interface-type; ++ } ++ identity fddi { ++ base iana-interface-type; ++ reference ++ "RFC 1512 - FDDI Management Information Base"; ++ } ++ identity lapb { ++ base iana-interface-type; ++ reference ++ "RFC 1381 - SNMP MIB Extension for X.25 LAPB"; ++ } ++ identity sdlc { ++ base iana-interface-type; ++ } ++ identity ds1 { ++ base iana-interface-type; ++ description ++ "DS1-MIB."; ++ reference ++ "RFC 4805 - Definitions of Managed Objects for the ++ DS1, J1, E1, DS2, and E2 Interface Types"; ++ } ++ identity e1 { ++ base iana-interface-type; ++ status obsolete; ++ description ++ "Obsolete; see DS1-MIB."; ++ reference ++ "RFC 4805 - Definitions of Managed Objects for the ++ DS1, J1, E1, DS2, and E2 Interface Types"; ++ } ++ ++ identity basicISDN { ++ base iana-interface-type; ++ description ++ "No longer used. See also RFC 2127."; ++ } ++ identity primaryISDN { ++ base iana-interface-type; ++ description ++ "No longer used. See also RFC 2127."; ++ } ++ identity propPointToPointSerial { ++ base iana-interface-type; ++ description ++ "Proprietary serial."; ++ } ++ identity ppp { ++ base iana-interface-type; ++ } ++ identity softwareLoopback { ++ base iana-interface-type; ++ } ++ identity eon { ++ base iana-interface-type; ++ description ++ "CLNP over IP."; ++ } ++ identity ethernet3Mbit { ++ base iana-interface-type; ++ } ++ identity nsip { ++ base iana-interface-type; ++ description ++ "XNS over IP."; ++ } ++ identity slip { ++ base iana-interface-type; ++ description ++ "Generic SLIP."; ++ } ++ identity ultra { ++ base iana-interface-type; ++ description ++ "Ultra Technologies."; ++ } ++ identity ds3 { ++ base iana-interface-type; ++ description ++ "DS3-MIB."; ++ reference ++ "RFC 3896 - Definitions of Managed Objects for the ++ DS3/E3 Interface Type"; ++ } ++ identity sip { ++ base iana-interface-type; ++ description ++ "SMDS, coffee."; ++ reference ++ "RFC 1694 - Definitions of Managed Objects for SMDS ++ Interfaces using SMIv2"; ++ } ++ identity frameRelay { ++ base iana-interface-type; ++ description ++ "DTE only."; ++ reference ++ "RFC 2115 - Management Information Base for Frame Relay ++ DTEs Using SMIv2"; ++ } ++ identity rs232 { ++ base iana-interface-type; ++ reference ++ "RFC 1659 - Definitions of Managed Objects for RS-232-like ++ Hardware Devices using SMIv2"; ++ } ++ identity para { ++ base iana-interface-type; ++ description ++ "Parallel-port."; ++ reference ++ "RFC 1660 - Definitions of Managed Objects for ++ Parallel-printer-like Hardware Devices using ++ SMIv2"; ++ } ++ identity arcnet { ++ base iana-interface-type; ++ description ++ "ARCnet."; ++ } ++ identity arcnetPlus { ++ base iana-interface-type; ++ description ++ "ARCnet Plus."; ++ } ++ ++ identity atm { ++ base iana-interface-type; ++ description ++ "ATM cells."; ++ } ++ identity miox25 { ++ base iana-interface-type; ++ reference ++ "RFC 1461 - SNMP MIB extension for Multiprotocol ++ Interconnect over X.25"; ++ } ++ identity sonet { ++ base iana-interface-type; ++ description ++ "SONET or SDH."; ++ } ++ identity x25ple { ++ base iana-interface-type; ++ reference ++ "RFC 2127 - ISDN Management Information Base using SMIv2"; ++ } ++ identity iso88022llc { ++ base iana-interface-type; ++ } ++ identity localTalk { ++ base iana-interface-type; ++ } ++ identity smdsDxi { ++ base iana-interface-type; ++ } ++ identity frameRelayService { ++ base iana-interface-type; ++ description ++ "FRNETSERV-MIB."; ++ reference ++ "RFC 2954 - Definitions of Managed Objects for Frame ++ Relay Service"; ++ } ++ identity v35 { ++ base iana-interface-type; ++ } ++ identity hssi { ++ base iana-interface-type; ++ } ++ identity hippi { ++ base iana-interface-type; ++ } ++ ++ identity modem { ++ base iana-interface-type; ++ description ++ "Generic modem."; ++ } ++ identity aal5 { ++ base iana-interface-type; ++ description ++ "AAL5 over ATM."; ++ } ++ identity sonetPath { ++ base iana-interface-type; ++ } ++ identity sonetVT { ++ base iana-interface-type; ++ } ++ identity smdsIcip { ++ base iana-interface-type; ++ description ++ "SMDS InterCarrier Interface."; ++ } ++ identity propVirtual { ++ base iana-interface-type; ++ description ++ "Proprietary virtual/internal."; ++ reference ++ "RFC 2863 - The Interfaces Group MIB"; ++ } ++ identity propMultiplexor { ++ base iana-interface-type; ++ description ++ "Proprietary multiplexing."; ++ reference ++ "RFC 2863 - The Interfaces Group MIB"; ++ } ++ identity ieee80212 { ++ base iana-interface-type; ++ description ++ "100BaseVG."; ++ } ++ identity fibreChannel { ++ base iana-interface-type; ++ description ++ "Fibre Channel."; ++ } ++ ++ identity hippiInterface { ++ base iana-interface-type; ++ description ++ "HIPPI interfaces."; ++ } ++ identity frameRelayInterconnect { ++ base iana-interface-type; ++ status obsolete; ++ description ++ "Obsolete; use either ++ frameRelay(32) or frameRelayService(44)."; ++ } ++ identity aflane8023 { ++ base iana-interface-type; ++ description ++ "ATM Emulated LAN for 802.3."; ++ } ++ identity aflane8025 { ++ base iana-interface-type; ++ description ++ "ATM Emulated LAN for 802.5."; ++ } ++ identity cctEmul { ++ base iana-interface-type; ++ description ++ "ATM Emulated circuit."; ++ } ++ identity fastEther { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Obsoleted via RFC 3635. ++ ethernetCsmacd(6) should be used instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity isdn { ++ base iana-interface-type; ++ description ++ "ISDN and X.25."; ++ reference ++ "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN ++ in the Packet Mode"; ++ } ++ ++ identity v11 { ++ base iana-interface-type; ++ description ++ "CCITT V.11/X.21."; ++ } ++ identity v36 { ++ base iana-interface-type; ++ description ++ "CCITT V.36."; ++ } ++ identity g703at64k { ++ base iana-interface-type; ++ description ++ "CCITT G703 at 64Kbps."; ++ } ++ identity g703at2mb { ++ base iana-interface-type; ++ status obsolete; ++ description ++ "Obsolete; see DS1-MIB."; ++ } ++ identity qllc { ++ base iana-interface-type; ++ description ++ "SNA QLLC."; ++ } ++ identity fastEtherFX { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Obsoleted via RFC 3635. ++ ethernetCsmacd(6) should be used instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity channel { ++ base iana-interface-type; ++ description ++ "Channel."; ++ } ++ identity ieee80211 { ++ base iana-interface-type; ++ description ++ "Radio spread spectrum."; ++ } ++ identity ibm370parChan { ++ base iana-interface-type; ++ description ++ "IBM System 360/370 OEMI Channel."; ++ } ++ identity escon { ++ base iana-interface-type; ++ description ++ "IBM Enterprise Systems Connection."; ++ } ++ identity dlsw { ++ base iana-interface-type; ++ description ++ "Data Link Switching."; ++ } ++ identity isdns { ++ base iana-interface-type; ++ description ++ "ISDN S/T interface."; ++ } ++ identity isdnu { ++ base iana-interface-type; ++ description ++ "ISDN U interface."; ++ } ++ identity lapd { ++ base iana-interface-type; ++ description ++ "Link Access Protocol D."; ++ } ++ identity ipSwitch { ++ base iana-interface-type; ++ description ++ "IP Switching Objects."; ++ } ++ identity rsrb { ++ base iana-interface-type; ++ description ++ "Remote Source Route Bridging."; ++ } ++ identity atmLogical { ++ base iana-interface-type; ++ description ++ "ATM Logical Port."; ++ reference ++ "RFC 3606 - Definitions of Supplemental Managed Objects ++ for ATM Interface"; ++ } ++ identity ds0 { ++ base iana-interface-type; ++ description ++ "Digital Signal Level 0."; ++ reference ++ "RFC 2494 - Definitions of Managed Objects for the DS0 ++ and DS0 Bundle Interface Type"; ++ } ++ identity ds0Bundle { ++ base iana-interface-type; ++ description ++ "Group of ds0s on the same ds1."; ++ reference ++ "RFC 2494 - Definitions of Managed Objects for the DS0 ++ and DS0 Bundle Interface Type"; ++ } ++ identity bsc { ++ base iana-interface-type; ++ description ++ "Bisynchronous Protocol."; ++ } ++ identity async { ++ base iana-interface-type; ++ description ++ "Asynchronous Protocol."; ++ } ++ identity cnr { ++ base iana-interface-type; ++ description ++ "Combat Net Radio."; ++ } ++ identity iso88025Dtr { ++ base iana-interface-type; ++ description ++ "ISO 802.5r DTR."; ++ } ++ identity eplrs { ++ base iana-interface-type; ++ description ++ "Ext Pos Loc Report Sys."; ++ } ++ identity arap { ++ base iana-interface-type; ++ description ++ "Appletalk Remote Access Protocol."; ++ } ++ identity propCnls { ++ base iana-interface-type; ++ description ++ "Proprietary Connectionless Protocol."; ++ } ++ identity hostPad { ++ base iana-interface-type; ++ description ++ "CCITT-ITU X.29 PAD Protocol."; ++ } ++ identity termPad { ++ base iana-interface-type; ++ description ++ "CCITT-ITU X.3 PAD Facility."; ++ } ++ identity frameRelayMPI { ++ base iana-interface-type; ++ description ++ "Multiproto Interconnect over FR."; ++ } ++ identity x213 { ++ base iana-interface-type; ++ description ++ "CCITT-ITU X213."; ++ } ++ identity adsl { ++ base iana-interface-type; ++ description ++ "Asymmetric Digital Subscriber Loop."; ++ } ++ identity radsl { ++ base iana-interface-type; ++ description ++ "Rate-Adapt. Digital Subscriber Loop."; ++ } ++ identity sdsl { ++ base iana-interface-type; ++ description ++ "Symmetric Digital Subscriber Loop."; ++ } ++ identity vdsl { ++ base iana-interface-type; ++ description ++ "Very H-Speed Digital Subscrib. Loop."; ++ } ++ identity iso88025CRFPInt { ++ base iana-interface-type; ++ description ++ "ISO 802.5 CRFP."; ++ } ++ identity myrinet { ++ base iana-interface-type; ++ description ++ "Myricom Myrinet."; ++ } ++ identity voiceEM { ++ base iana-interface-type; ++ description ++ "Voice recEive and transMit."; ++ } ++ identity voiceFXO { ++ base iana-interface-type; ++ description ++ "Voice Foreign Exchange Office."; ++ } ++ identity voiceFXS { ++ base iana-interface-type; ++ description ++ "Voice Foreign Exchange Station."; ++ } ++ identity voiceEncap { ++ base iana-interface-type; ++ description ++ "Voice encapsulation."; ++ } ++ identity voiceOverIp { ++ base iana-interface-type; ++ description ++ "Voice over IP encapsulation."; ++ } ++ identity atmDxi { ++ base iana-interface-type; ++ description ++ "ATM DXI."; ++ } ++ identity atmFuni { ++ base iana-interface-type; ++ description ++ "ATM FUNI."; ++ } ++ identity atmIma { ++ base iana-interface-type; ++ description ++ "ATM IMA."; ++ } ++ identity pppMultilinkBundle { ++ base iana-interface-type; ++ description ++ "PPP Multilink Bundle."; ++ } ++ identity ipOverCdlc { ++ base iana-interface-type; ++ description ++ "IBM ipOverCdlc."; ++ } ++ identity ipOverClaw { ++ base iana-interface-type; ++ description ++ "IBM Common Link Access to Workstn."; ++ } ++ identity stackToStack { ++ base iana-interface-type; ++ description ++ "IBM stackToStack."; ++ } ++ identity virtualIpAddress { ++ base iana-interface-type; ++ description ++ "IBM VIPA."; ++ } ++ identity mpc { ++ base iana-interface-type; ++ description ++ "IBM multi-protocol channel support."; ++ } ++ identity ipOverAtm { ++ base iana-interface-type; ++ description ++ "IBM ipOverAtm."; ++ reference ++ "RFC 2320 - Definitions of Managed Objects for Classical IP ++ and ARP Over ATM Using SMIv2 (IPOA-MIB)"; ++ } ++ identity iso88025Fiber { ++ base iana-interface-type; ++ description ++ "ISO 802.5j Fiber Token Ring."; ++ } ++ identity tdlc { ++ base iana-interface-type; ++ description ++ "IBM twinaxial data link control."; ++ } ++ identity gigabitEthernet { ++ base iana-interface-type; ++ status deprecated; ++ ++ description ++ "Obsoleted via RFC 3635. ++ ethernetCsmacd(6) should be used instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity hdlc { ++ base iana-interface-type; ++ description ++ "HDLC."; ++ } ++ identity lapf { ++ base iana-interface-type; ++ description ++ "LAP F."; ++ } ++ identity v37 { ++ base iana-interface-type; ++ description ++ "V.37."; ++ } ++ identity x25mlp { ++ base iana-interface-type; ++ description ++ "Multi-Link Protocol."; ++ } ++ identity x25huntGroup { ++ base iana-interface-type; ++ description ++ "X25 Hunt Group."; ++ } ++ identity transpHdlc { ++ base iana-interface-type; ++ description ++ "Transp HDLC."; ++ } ++ identity interleave { ++ base iana-interface-type; ++ description ++ "Interleave channel."; ++ } ++ identity fast { ++ base iana-interface-type; ++ description ++ "Fast channel."; ++ } ++ ++ identity ip { ++ base iana-interface-type; ++ description ++ "IP (for APPN HPR in IP networks)."; ++ } ++ identity docsCableMaclayer { ++ base iana-interface-type; ++ description ++ "CATV Mac Layer."; ++ } ++ identity docsCableDownstream { ++ base iana-interface-type; ++ description ++ "CATV Downstream interface."; ++ } ++ identity docsCableUpstream { ++ base iana-interface-type; ++ description ++ "CATV Upstream interface."; ++ } ++ identity a12MppSwitch { ++ base iana-interface-type; ++ description ++ "Avalon Parallel Processor."; ++ } ++ identity tunnel { ++ base iana-interface-type; ++ description ++ "Encapsulation interface."; ++ } ++ identity coffee { ++ base iana-interface-type; ++ description ++ "Coffee pot."; ++ reference ++ "RFC 2325 - Coffee MIB"; ++ } ++ identity ces { ++ base iana-interface-type; ++ description ++ "Circuit Emulation Service."; ++ } ++ identity atmSubInterface { ++ base iana-interface-type; ++ description ++ "ATM Sub Interface."; ++ } ++ ++ identity l2vlan { ++ base iana-interface-type; ++ description ++ "Layer 2 Virtual LAN using 802.1Q."; ++ } ++ identity l3ipvlan { ++ base iana-interface-type; ++ description ++ "Layer 3 Virtual LAN using IP."; ++ } ++ identity l3ipxvlan { ++ base iana-interface-type; ++ description ++ "Layer 3 Virtual LAN using IPX."; ++ } ++ identity digitalPowerline { ++ base iana-interface-type; ++ description ++ "IP over Power Lines."; ++ } ++ identity mediaMailOverIp { ++ base iana-interface-type; ++ description ++ "Multimedia Mail over IP."; ++ } ++ identity dtm { ++ base iana-interface-type; ++ description ++ "Dynamic synchronous Transfer Mode."; ++ } ++ identity dcn { ++ base iana-interface-type; ++ description ++ "Data Communications Network."; ++ } ++ identity ipForward { ++ base iana-interface-type; ++ description ++ "IP Forwarding Interface."; ++ } ++ identity msdsl { ++ base iana-interface-type; ++ description ++ "Multi-rate Symmetric DSL."; ++ } ++ identity ieee1394 { ++ base iana-interface-type; ++ ++ description ++ "IEEE1394 High Performance Serial Bus."; ++ } ++ identity if-gsn { ++ base iana-interface-type; ++ description ++ "HIPPI-6400."; ++ } ++ identity dvbRccMacLayer { ++ base iana-interface-type; ++ description ++ "DVB-RCC MAC Layer."; ++ } ++ identity dvbRccDownstream { ++ base iana-interface-type; ++ description ++ "DVB-RCC Downstream Channel."; ++ } ++ identity dvbRccUpstream { ++ base iana-interface-type; ++ description ++ "DVB-RCC Upstream Channel."; ++ } ++ identity atmVirtual { ++ base iana-interface-type; ++ description ++ "ATM Virtual Interface."; ++ } ++ identity mplsTunnel { ++ base iana-interface-type; ++ description ++ "MPLS Tunnel Virtual Interface."; ++ } ++ identity srp { ++ base iana-interface-type; ++ description ++ "Spatial Reuse Protocol."; ++ } ++ identity voiceOverAtm { ++ base iana-interface-type; ++ description ++ "Voice over ATM."; ++ } ++ identity voiceOverFrameRelay { ++ base iana-interface-type; ++ description ++ "Voice Over Frame Relay."; ++ } ++ identity idsl { ++ base iana-interface-type; ++ description ++ "Digital Subscriber Loop over ISDN."; ++ } ++ identity compositeLink { ++ base iana-interface-type; ++ description ++ "Avici Composite Link Interface."; ++ } ++ identity ss7SigLink { ++ base iana-interface-type; ++ description ++ "SS7 Signaling Link."; ++ } ++ identity propWirelessP2P { ++ base iana-interface-type; ++ description ++ "Prop. P2P wireless interface."; ++ } ++ identity frForward { ++ base iana-interface-type; ++ description ++ "Frame Forward Interface."; ++ } ++ identity rfc1483 { ++ base iana-interface-type; ++ description ++ "Multiprotocol over ATM AAL5."; ++ reference ++ "RFC 1483 - Multiprotocol Encapsulation over ATM ++ Adaptation Layer 5"; ++ } ++ identity usb { ++ base iana-interface-type; ++ description ++ "USB Interface."; ++ } ++ identity ieee8023adLag { ++ base iana-interface-type; ++ description ++ "IEEE 802.3ad Link Aggregate."; ++ } ++ identity bgppolicyaccounting { ++ base iana-interface-type; ++ description ++ "BGP Policy Accounting."; ++ } ++ identity frf16MfrBundle { ++ base iana-interface-type; ++ description ++ "FRF.16 Multilink Frame Relay."; ++ } ++ identity h323Gatekeeper { ++ base iana-interface-type; ++ description ++ "H323 Gatekeeper."; ++ } ++ identity h323Proxy { ++ base iana-interface-type; ++ description ++ "H323 Voice and Video Proxy."; ++ } ++ identity mpls { ++ base iana-interface-type; ++ description ++ "MPLS."; ++ } ++ identity mfSigLink { ++ base iana-interface-type; ++ description ++ "Multi-frequency signaling link."; ++ } ++ identity hdsl2 { ++ base iana-interface-type; ++ description ++ "High Bit-Rate DSL - 2nd generation."; ++ } ++ identity shdsl { ++ base iana-interface-type; ++ description ++ "Multirate HDSL2."; ++ } ++ identity ds1FDL { ++ base iana-interface-type; ++ description ++ "Facility Data Link (4Kbps) on a DS1."; ++ } ++ identity pos { ++ base iana-interface-type; ++ description ++ "Packet over SONET/SDH Interface."; ++ } ++ ++ identity dvbAsiIn { ++ base iana-interface-type; ++ description ++ "DVB-ASI Input."; ++ } ++ identity dvbAsiOut { ++ base iana-interface-type; ++ description ++ "DVB-ASI Output."; ++ } ++ identity plc { ++ base iana-interface-type; ++ description ++ "Power Line Communications."; ++ } ++ identity nfas { ++ base iana-interface-type; ++ description ++ "Non-Facility Associated Signaling."; ++ } ++ identity tr008 { ++ base iana-interface-type; ++ description ++ "TR008."; ++ } ++ identity gr303RDT { ++ base iana-interface-type; ++ description ++ "Remote Digital Terminal."; ++ } ++ identity gr303IDT { ++ base iana-interface-type; ++ description ++ "Integrated Digital Terminal."; ++ } ++ identity isup { ++ base iana-interface-type; ++ description ++ "ISUP."; ++ } ++ identity propDocsWirelessMaclayer { ++ base iana-interface-type; ++ description ++ "Cisco proprietary Maclayer."; ++ } ++ ++ identity propDocsWirelessDownstream { ++ base iana-interface-type; ++ description ++ "Cisco proprietary Downstream."; ++ } ++ identity propDocsWirelessUpstream { ++ base iana-interface-type; ++ description ++ "Cisco proprietary Upstream."; ++ } ++ identity hiperlan2 { ++ base iana-interface-type; ++ description ++ "HIPERLAN Type 2 Radio Interface."; ++ } ++ identity propBWAp2Mp { ++ base iana-interface-type; ++ description ++ "PropBroadbandWirelessAccesspt2Multipt (use of this value ++ for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f ++ is deprecated, and ieee80216WMAN(237) should be used ++ instead)."; ++ } ++ identity sonetOverheadChannel { ++ base iana-interface-type; ++ description ++ "SONET Overhead Channel."; ++ } ++ identity digitalWrapperOverheadChannel { ++ base iana-interface-type; ++ description ++ "Digital Wrapper."; ++ } ++ identity aal2 { ++ base iana-interface-type; ++ description ++ "ATM adaptation layer 2."; ++ } ++ identity radioMAC { ++ base iana-interface-type; ++ description ++ "MAC layer over radio links."; ++ } ++ identity atmRadio { ++ base iana-interface-type; ++ description ++ "ATM over radio links."; ++ } ++ identity imt { ++ base iana-interface-type; ++ description ++ "Inter-Machine Trunks."; ++ } ++ identity mvl { ++ base iana-interface-type; ++ description ++ "Multiple Virtual Lines DSL."; ++ } ++ identity reachDSL { ++ base iana-interface-type; ++ description ++ "Long Reach DSL."; ++ } ++ identity frDlciEndPt { ++ base iana-interface-type; ++ description ++ "Frame Relay DLCI End Point."; ++ } ++ identity atmVciEndPt { ++ base iana-interface-type; ++ description ++ "ATM VCI End Point."; ++ } ++ identity opticalChannel { ++ base iana-interface-type; ++ description ++ "Optical Channel."; ++ } ++ identity opticalTransport { ++ base iana-interface-type; ++ description ++ "Optical Transport."; ++ } ++ identity propAtm { ++ base iana-interface-type; ++ description ++ "Proprietary ATM."; ++ } ++ identity voiceOverCable { ++ base iana-interface-type; ++ description ++ "Voice Over Cable Interface."; ++ } ++ ++ identity infiniband { ++ base iana-interface-type; ++ description ++ "Infiniband."; ++ } ++ identity teLink { ++ base iana-interface-type; ++ description ++ "TE Link."; ++ } ++ identity q2931 { ++ base iana-interface-type; ++ description ++ "Q.2931."; ++ } ++ identity virtualTg { ++ base iana-interface-type; ++ description ++ "Virtual Trunk Group."; ++ } ++ identity sipTg { ++ base iana-interface-type; ++ description ++ "SIP Trunk Group."; ++ } ++ identity sipSig { ++ base iana-interface-type; ++ description ++ "SIP Signaling."; ++ } ++ identity docsCableUpstreamChannel { ++ base iana-interface-type; ++ description ++ "CATV Upstream Channel."; ++ } ++ identity econet { ++ base iana-interface-type; ++ description ++ "Acorn Econet."; ++ } ++ identity pon155 { ++ base iana-interface-type; ++ description ++ "FSAN 155Mb Symetrical PON interface."; ++ } ++ ++ identity pon622 { ++ base iana-interface-type; ++ description ++ "FSAN 622Mb Symetrical PON interface."; ++ } ++ identity bridge { ++ base iana-interface-type; ++ description ++ "Transparent bridge interface."; ++ } ++ identity linegroup { ++ base iana-interface-type; ++ description ++ "Interface common to multiple lines."; ++ } ++ identity voiceEMFGD { ++ base iana-interface-type; ++ description ++ "Voice E&M Feature Group D."; ++ } ++ identity voiceFGDEANA { ++ base iana-interface-type; ++ description ++ "Voice FGD Exchange Access North American."; ++ } ++ identity voiceDID { ++ base iana-interface-type; ++ description ++ "Voice Direct Inward Dialing."; ++ } ++ identity mpegTransport { ++ base iana-interface-type; ++ description ++ "MPEG transport interface."; ++ } ++ identity sixToFour { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "6to4 interface (DEPRECATED)."; ++ reference ++ "RFC 4087 - IP Tunnel MIB"; ++ } ++ identity gtp { ++ base iana-interface-type; ++ description ++ "GTP (GPRS Tunneling Protocol)."; ++ } ++ identity pdnEtherLoop1 { ++ base iana-interface-type; ++ description ++ "Paradyne EtherLoop 1."; ++ } ++ identity pdnEtherLoop2 { ++ base iana-interface-type; ++ description ++ "Paradyne EtherLoop 2."; ++ } ++ identity opticalChannelGroup { ++ base iana-interface-type; ++ description ++ "Optical Channel Group."; ++ } ++ identity homepna { ++ base iana-interface-type; ++ description ++ "HomePNA ITU-T G.989."; ++ } ++ identity gfp { ++ base iana-interface-type; ++ description ++ "Generic Framing Procedure (GFP)."; ++ } ++ identity ciscoISLvlan { ++ base iana-interface-type; ++ description ++ "Layer 2 Virtual LAN using Cisco ISL."; ++ } ++ identity actelisMetaLOOP { ++ base iana-interface-type; ++ description ++ "Acteleis proprietary MetaLOOP High Speed Link."; ++ } ++ identity fcipLink { ++ base iana-interface-type; ++ description ++ "FCIP Link."; ++ } ++ identity rpr { ++ base iana-interface-type; ++ description ++ "Resilient Packet Ring Interface Type."; ++ } ++ ++ identity qam { ++ base iana-interface-type; ++ description ++ "RF Qam Interface."; ++ } ++ identity lmp { ++ base iana-interface-type; ++ description ++ "Link Management Protocol."; ++ reference ++ "RFC 4327 - Link Management Protocol (LMP) Management ++ Information Base (MIB)"; ++ } ++ identity cblVectaStar { ++ base iana-interface-type; ++ description ++ "Cambridge Broadband Networks Limited VectaStar."; ++ } ++ identity docsCableMCmtsDownstream { ++ base iana-interface-type; ++ description ++ "CATV Modular CMTS Downstream Interface."; ++ } ++ identity adsl2 { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Asymmetric Digital Subscriber Loop Version 2 ++ (DEPRECATED/OBSOLETED - please use adsl2plus(238) ++ instead)."; ++ reference ++ "RFC 4706 - Definitions of Managed Objects for Asymmetric ++ Digital Subscriber Line 2 (ADSL2)"; ++ } ++ identity macSecControlledIF { ++ base iana-interface-type; ++ description ++ "MACSecControlled."; ++ } ++ identity macSecUncontrolledIF { ++ base iana-interface-type; ++ description ++ "MACSecUncontrolled."; ++ } ++ identity aviciOpticalEther { ++ base iana-interface-type; ++ description ++ "Avici Optical Ethernet Aggregate."; ++ } ++ identity atmbond { ++ base iana-interface-type; ++ description ++ "atmbond."; ++ } ++ identity voiceFGDOS { ++ base iana-interface-type; ++ description ++ "Voice FGD Operator Services."; ++ } ++ identity mocaVersion1 { ++ base iana-interface-type; ++ description ++ "MultiMedia over Coax Alliance (MoCA) Interface ++ as documented in information provided privately to IANA."; ++ } ++ identity ieee80216WMAN { ++ base iana-interface-type; ++ description ++ "IEEE 802.16 WMAN interface."; ++ } ++ identity adsl2plus { ++ base iana-interface-type; ++ description ++ "Asymmetric Digital Subscriber Loop Version 2 - ++ Version 2 Plus and all variants."; ++ } ++ identity dvbRcsMacLayer { ++ base iana-interface-type; ++ description ++ "DVB-RCS MAC Layer."; ++ reference ++ "RFC 5728 - The SatLabs Group DVB-RCS MIB"; ++ } ++ identity dvbTdm { ++ base iana-interface-type; ++ description ++ "DVB Satellite TDM."; ++ reference ++ "RFC 5728 - The SatLabs Group DVB-RCS MIB"; ++ } ++ identity dvbRcsTdma { ++ base iana-interface-type; ++ description ++ "DVB-RCS TDMA."; ++ reference ++ "RFC 5728 - The SatLabs Group DVB-RCS MIB"; ++ } ++ identity x86Laps { ++ base iana-interface-type; ++ description ++ "LAPS based on ITU-T X.86/Y.1323."; ++ } ++ identity wwanPP { ++ base iana-interface-type; ++ description ++ "3GPP WWAN."; ++ } ++ identity wwanPP2 { ++ base iana-interface-type; ++ description ++ "3GPP2 WWAN."; ++ } ++ identity voiceEBS { ++ base iana-interface-type; ++ description ++ "Voice P-phone EBS physical interface."; ++ } ++ identity ifPwType { ++ base iana-interface-type; ++ description ++ "Pseudowire interface type."; ++ reference ++ "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)"; ++ } ++ identity ilan { ++ base iana-interface-type; ++ description ++ "Internal LAN on a bridge per IEEE 802.1ap."; ++ } ++ identity pip { ++ base iana-interface-type; ++ description ++ "Provider Instance Port on a bridge per IEEE 802.1ah PBB."; ++ } ++ identity aluELP { ++ base iana-interface-type; ++ description ++ "Alcatel-Lucent Ethernet Link Protection."; ++ } ++ identity gpon { ++ base iana-interface-type; ++ description ++ "Gigabit-capable passive optical networks (G-PON) as per ++ ITU-T G.948."; ++ } ++ identity vdsl2 { ++ base iana-interface-type; ++ description ++ "Very high speed digital subscriber line Version 2 ++ (as per ITU-T Recommendation G.993.2)."; ++ reference ++ "RFC 5650 - Definitions of Managed Objects for Very High ++ Speed Digital Subscriber Line 2 (VDSL2)"; ++ } ++ identity capwapDot11Profile { ++ base iana-interface-type; ++ description ++ "WLAN Profile Interface."; ++ reference ++ "RFC 5834 - Control and Provisioning of Wireless Access ++ Points (CAPWAP) Protocol Binding MIB for ++ IEEE 802.11"; ++ } ++ identity capwapDot11Bss { ++ base iana-interface-type; ++ description ++ "WLAN BSS Interface."; ++ reference ++ "RFC 5834 - Control and Provisioning of Wireless Access ++ Points (CAPWAP) Protocol Binding MIB for ++ IEEE 802.11"; ++ } ++ identity capwapWtpVirtualRadio { ++ base iana-interface-type; ++ description ++ "WTP Virtual Radio Interface."; ++ reference ++ "RFC 5833 - Control and Provisioning of Wireless Access ++ Points (CAPWAP) Protocol Base MIB"; ++ } ++ identity bits { ++ base iana-interface-type; ++ description ++ "bitsport."; ++ } ++ identity docsCableUpstreamRfPort { ++ base iana-interface-type; ++ description ++ "DOCSIS CATV Upstream RF Port."; ++ } ++ ++ identity cableDownstreamRfPort { ++ base iana-interface-type; ++ description ++ "CATV downstream RF Port."; ++ } ++ identity vmwareVirtualNic { ++ base iana-interface-type; ++ description ++ "VMware Virtual Network Interface."; ++ } ++ identity ieee802154 { ++ base iana-interface-type; ++ description ++ "IEEE 802.15.4 WPAN interface."; ++ reference ++ "IEEE 802.15.4-2006"; ++ } ++ identity otnOdu { ++ base iana-interface-type; ++ description ++ "OTN Optical Data Unit."; ++ } ++ identity otnOtu { ++ base iana-interface-type; ++ description ++ "OTN Optical channel Transport Unit."; ++ } ++ identity ifVfiType { ++ base iana-interface-type; ++ description ++ "VPLS Forwarding Instance Interface Type."; ++ } ++ identity g9981 { ++ base iana-interface-type; ++ description ++ "G.998.1 bonded interface."; ++ } ++ identity g9982 { ++ base iana-interface-type; ++ description ++ "G.998.2 bonded interface."; ++ } ++ identity g9983 { ++ base iana-interface-type; ++ description ++ "G.998.3 bonded interface."; ++ } ++ ++ identity aluEpon { ++ base iana-interface-type; ++ description ++ "Ethernet Passive Optical Networks (E-PON)."; ++ } ++ identity aluEponOnu { ++ base iana-interface-type; ++ description ++ "EPON Optical Network Unit."; ++ } ++ identity aluEponPhysicalUni { ++ base iana-interface-type; ++ description ++ "EPON physical User to Network interface."; ++ } ++ identity aluEponLogicalLink { ++ base iana-interface-type; ++ description ++ "The emulation of a point-to-point link over the EPON ++ layer."; ++ } ++ identity aluGponOnu { ++ base iana-interface-type; ++ description ++ "GPON Optical Network Unit."; ++ reference ++ "ITU-T G.984.2"; ++ } ++ identity aluGponPhysicalUni { ++ base iana-interface-type; ++ description ++ "GPON physical User to Network interface."; ++ reference ++ "ITU-T G.984.2"; ++ } ++ identity vmwareNicTeam { ++ base iana-interface-type; ++ description ++ "VMware NIC Team."; ++ } ++} +diff --git a/yang/ietf-interfaces@2018-02-20.yang b/yang/ietf-interfaces@2018-02-20.yang +new file mode 100644 +index 0000000..f66c205 +--- /dev/null ++++ b/yang/ietf-interfaces@2018-02-20.yang +@@ -0,0 +1,1123 @@ ++module ietf-interfaces { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; ++ prefix if; ++ ++ import ietf-yang-types { ++ prefix yang; ++ } ++ ++ organization ++ "IETF NETMOD (Network Modeling) Working Group"; ++ ++ contact ++ "WG Web: ++ WG List: ++ ++ Editor: Martin Bjorklund ++ "; ++ ++ description ++ "This module contains a collection of YANG definitions for ++ managing network interfaces. ++ ++ Copyright (c) 2018 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8343; see ++ the RFC itself for full legal notices."; ++ ++ revision 2018-02-20 { ++ description ++ "Updated to support NMDA."; ++ reference ++ "RFC 8343: A YANG Data Model for Interface Management"; ++ } ++ ++ revision 2014-05-08 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 7223: A YANG Data Model for Interface Management"; ++ } ++ ++ /* ++ * Typedefs ++ */ ++ ++ typedef interface-ref { ++ type leafref { ++ path "/if:interfaces/if:interface/if:name"; ++ } ++ description ++ "This type is used by data models that need to reference ++ interfaces."; ++ } ++ ++ /* ++ * Identities ++ */ ++ ++ identity interface-type { ++ description ++ "Base identity from which specific interface types are ++ derived."; ++ } ++ ++ /* ++ * Features ++ */ ++ ++ feature arbitrary-names { ++ description ++ "This feature indicates that the device allows user-controlled ++ interfaces to be named arbitrarily."; ++ } ++ feature pre-provisioning { ++ description ++ "This feature indicates that the device supports ++ pre-provisioning of interface configuration, i.e., it is ++ possible to configure an interface whose physical interface ++ hardware is not present on the device."; ++ } ++ feature if-mib { ++ description ++ "This feature indicates that the device implements ++ the IF-MIB."; ++ reference ++ "RFC 2863: The Interfaces Group MIB"; ++ } ++ ++ /* ++ * Data nodes ++ */ ++ ++ container interfaces { ++ description ++ "Interface parameters."; ++ ++ list interface { ++ key "name"; ++ ++ description ++ "The list of interfaces on the device. ++ ++ The status of an interface is available in this list in the ++ operational state. If the configuration of a ++ system-controlled interface cannot be used by the system ++ (e.g., the interface hardware present does not match the ++ interface type), then the configuration is not applied to ++ the system-controlled interface shown in the operational ++ state. If the configuration of a user-controlled interface ++ cannot be used by the system, the configured interface is ++ not instantiated in the operational state. ++ ++ System-controlled interfaces created by the system are ++ always present in this list in the operational state, ++ whether or not they are configured."; ++ ++ leaf name { ++ type string; ++ description ++ "The name of the interface. ++ ++ A device MAY restrict the allowed values for this leaf, ++ possibly depending on the type of the interface. ++ For system-controlled interfaces, this leaf is the ++ device-specific name of the interface. ++ ++ If a client tries to create configuration for a ++ system-controlled interface that is not present in the ++ operational state, the server MAY reject the request if ++ the implementation does not support pre-provisioning of ++ interfaces or if the name refers to an interface that can ++ never exist in the system. A Network Configuration ++ Protocol (NETCONF) server MUST reply with an rpc-error ++ with the error-tag 'invalid-value' in this case. ++ ++ If the device supports pre-provisioning of interface ++ configuration, the 'pre-provisioning' feature is ++ advertised. ++ ++ If the device allows arbitrarily named user-controlled ++ interfaces, the 'arbitrary-names' feature is advertised. ++ ++ When a configured user-controlled interface is created by ++ the system, it is instantiated with the same name in the ++ operational state. ++ ++ A server implementation MAY map this leaf to the ifName ++ MIB object. Such an implementation needs to use some ++ mechanism to handle the differences in size and characters ++ allowed between this leaf and ifName. The definition of ++ such a mechanism is outside the scope of this document."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifName"; ++ } ++ ++ leaf description { ++ type string; ++ description ++ "A textual description of the interface. ++ ++ A server implementation MAY map this leaf to the ifAlias ++ MIB object. Such an implementation needs to use some ++ mechanism to handle the differences in size and characters ++ allowed between this leaf and ifAlias. The definition of ++ such a mechanism is outside the scope of this document. ++ ++ Since ifAlias is defined to be stored in non-volatile ++ storage, the MIB implementation MUST map ifAlias to the ++ value of 'description' in the persistently stored ++ configuration."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAlias"; ++ } ++ ++ leaf type { ++ type identityref { ++ base interface-type; ++ } ++ mandatory true; ++ description ++ "The type of the interface. ++ ++ When an interface entry is created, a server MAY ++ initialize the type leaf with a valid value, e.g., if it ++ is possible to derive the type from the name of the ++ interface. ++ ++ If a client tries to set the type of an interface to a ++ value that can never be used by the system, e.g., if the ++ type is not supported or if the type does not match the ++ name of the interface, the server MUST reject the request. ++ A NETCONF server MUST reply with an rpc-error with the ++ error-tag 'invalid-value' in this case."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifType"; ++ } ++ ++ leaf enabled { ++ type boolean; ++ default "true"; ++ description ++ "This leaf contains the configured, desired state of the ++ interface. ++ ++ Systems that implement the IF-MIB use the value of this ++ leaf in the intended configuration to set ++ IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry ++ has been initialized, as described in RFC 2863. ++ ++ Changes in this leaf in the intended configuration are ++ reflected in ifAdminStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; ++ } ++ ++ leaf link-up-down-trap-enable { ++ if-feature if-mib; ++ type enumeration { ++ enum enabled { ++ value 1; ++ description ++ "The device will generate linkUp/linkDown SNMP ++ notifications for this interface."; ++ } ++ enum disabled { ++ value 2; ++ description ++ "The device will not generate linkUp/linkDown SNMP ++ notifications for this interface."; ++ } ++ } ++ description ++ "Controls whether linkUp/linkDown SNMP notifications ++ should be generated for this interface. ++ ++ If this node is not configured, the value 'enabled' is ++ operationally used by the server for interfaces that do ++ not operate on top of any other interface (i.e., there are ++ no 'lower-layer-if' entries), and 'disabled' otherwise."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifLinkUpDownTrapEnable"; ++ } ++ ++ leaf admin-status { ++ if-feature if-mib; ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ description ++ "Not ready to pass packets and not in some test mode."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode."; ++ } ++ } ++ config false; ++ mandatory true; ++ description ++ "The desired state of the interface. ++ ++ This leaf has the same read semantics as ifAdminStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; ++ } ++ ++ leaf oper-status { ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ ++ description ++ "The interface does not pass any packets."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode. No operational packets can ++ be passed."; ++ } ++ enum unknown { ++ value 4; ++ description ++ "Status cannot be determined for some reason."; ++ } ++ enum dormant { ++ value 5; ++ description ++ "Waiting for some external event."; ++ } ++ enum not-present { ++ value 6; ++ description ++ "Some component (typically hardware) is missing."; ++ } ++ enum lower-layer-down { ++ value 7; ++ description ++ "Down due to state of lower-layer interface(s)."; ++ } ++ } ++ config false; ++ mandatory true; ++ description ++ "The current operational state of the interface. ++ ++ This leaf has the same semantics as ifOperStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOperStatus"; ++ } ++ ++ leaf last-change { ++ type yang:date-and-time; ++ config false; ++ description ++ "The time the interface entered its current operational ++ state. If the current state was entered prior to the ++ last re-initialization of the local network management ++ subsystem, then this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifLastChange"; ++ } ++ ++ leaf if-index { ++ if-feature if-mib; ++ type int32 { ++ range "1..2147483647"; ++ } ++ config false; ++ mandatory true; ++ description ++ "The ifIndex value for the ifEntry represented by this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifIndex"; ++ } ++ ++ leaf phys-address { ++ type yang:phys-address; ++ config false; ++ description ++ "The interface's address at its protocol sub-layer. For ++ example, for an 802.x interface, this object normally ++ contains a Media Access Control (MAC) address. The ++ interface's media-specific modules must define the bit ++ and byte ordering and the format of the value of this ++ object. For interfaces that do not have such an address ++ (e.g., a serial line), this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; ++ } ++ ++ leaf-list higher-layer-if { ++ type interface-ref; ++ config false; ++ description ++ "A list of references to interfaces layered on top of this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf-list lower-layer-if { ++ type interface-ref; ++ config false; ++ ++ description ++ "A list of references to interfaces layered underneath this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf speed { ++ type yang:gauge64; ++ units "bits/second"; ++ config false; ++ description ++ "An estimate of the interface's current bandwidth in bits ++ per second. For interfaces that do not vary in ++ bandwidth or for those where no accurate estimation can ++ be made, this node should contain the nominal bandwidth. ++ For interfaces that have no concept of bandwidth, this ++ node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifSpeed, ifHighSpeed"; ++ } ++ ++ container statistics { ++ config false; ++ description ++ "A collection of interface-related statistics objects."; ++ ++ leaf discontinuity-time { ++ type yang:date-and-time; ++ mandatory true; ++ description ++ "The time on the most recent occasion at which any one or ++ more of this interface's counters suffered a ++ discontinuity. If no such discontinuities have occurred ++ since the last re-initialization of the local management ++ subsystem, then this node contains the time the local ++ management subsystem re-initialized itself."; ++ } ++ ++ leaf in-octets { ++ type yang:counter64; ++ description ++ "The total number of octets received on the interface, ++ including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; ++ } ++ ++ leaf in-unicast-pkts { ++ type yang:counter64; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were not addressed to a ++ multicast or broadcast address at this sub-layer. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; ++ } ++ ++ leaf in-broadcast-pkts { ++ type yang:counter64; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a broadcast ++ address at this sub-layer. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInBroadcastPkts"; ++ } ++ ++ leaf in-multicast-pkts { ++ type yang:counter64; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a multicast ++ address at this sub-layer. For a MAC-layer protocol, ++ this includes both Group and Functional addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInMulticastPkts"; ++ } ++ ++ leaf in-discards { ++ type yang:counter32; ++ description ++ "The number of inbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being deliverable to a higher-layer ++ protocol. One possible reason for discarding such a ++ packet could be to free up buffer space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInDiscards"; ++ } ++ ++ leaf in-errors { ++ type yang:counter32; ++ description ++ "For packet-oriented interfaces, the number of inbound ++ packets that contained errors preventing them from being ++ deliverable to a higher-layer protocol. For character- ++ oriented or fixed-length interfaces, the number of ++ inbound transmission units that contained errors ++ preventing them from being deliverable to a higher-layer ++ protocol. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInErrors"; ++ } ++ ++ leaf in-unknown-protos { ++ type yang:counter32; ++ ++ description ++ "For packet-oriented interfaces, the number of packets ++ received via the interface that were discarded because ++ of an unknown or unsupported protocol. For ++ character-oriented or fixed-length interfaces that ++ support protocol multiplexing, the number of ++ transmission units received via the interface that were ++ discarded because of an unknown or unsupported protocol. ++ For any interface that does not support protocol ++ multiplexing, this counter is not present. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; ++ } ++ ++ leaf out-octets { ++ type yang:counter64; ++ description ++ "The total number of octets transmitted out of the ++ interface, including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; ++ } ++ ++ leaf out-unicast-pkts { ++ type yang:counter64; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were not addressed ++ to a multicast or broadcast address at this sub-layer, ++ including those that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; ++ } ++ ++ leaf out-broadcast-pkts { ++ type yang:counter64; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ broadcast address at this sub-layer, including those ++ that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutBroadcastPkts"; ++ } ++ ++ leaf out-multicast-pkts { ++ type yang:counter64; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ multicast address at this sub-layer, including those ++ that were discarded or not sent. For a MAC-layer ++ protocol, this includes both Group and Functional ++ addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutMulticastPkts"; ++ } ++ ++ leaf out-discards { ++ type yang:counter32; ++ description ++ "The number of outbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being transmitted. One possible reason ++ for discarding such a packet could be to free up buffer ++ space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; ++ } ++ ++ leaf out-errors { ++ type yang:counter32; ++ description ++ "For packet-oriented interfaces, the number of outbound ++ packets that could not be transmitted because of errors. ++ For character-oriented or fixed-length interfaces, the ++ number of outbound transmission units that could not be ++ transmitted because of errors. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutErrors"; ++ } ++ } ++ ++ } ++ } ++ ++ /* ++ * Legacy typedefs ++ */ ++ ++ typedef interface-state-ref { ++ type leafref { ++ path "/if:interfaces-state/if:interface/if:name"; ++ } ++ status deprecated; ++ description ++ "This type is used by data models that need to reference ++ the operationally present interfaces."; ++ } ++ ++ /* ++ * Legacy operational state data nodes ++ */ ++ ++ container interfaces-state { ++ config false; ++ status deprecated; ++ description ++ "Data nodes for the operational state of interfaces."; ++ ++ list interface { ++ key "name"; ++ status deprecated; ++ ++ description ++ "The list of interfaces on the device. ++ ++ System-controlled interfaces created by the system are ++ always present in this list, whether or not they are ++ configured."; ++ ++ leaf name { ++ type string; ++ status deprecated; ++ description ++ "The name of the interface. ++ ++ A server implementation MAY map this leaf to the ifName ++ MIB object. Such an implementation needs to use some ++ mechanism to handle the differences in size and characters ++ allowed between this leaf and ifName. The definition of ++ such a mechanism is outside the scope of this document."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifName"; ++ } ++ ++ leaf type { ++ type identityref { ++ base interface-type; ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The type of the interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifType"; ++ } ++ ++ leaf admin-status { ++ if-feature if-mib; ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ description ++ "Not ready to pass packets and not in some test mode."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode."; ++ } ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The desired state of the interface. ++ ++ This leaf has the same read semantics as ifAdminStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; ++ } ++ ++ leaf oper-status { ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ description ++ "The interface does not pass any packets."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode. No operational packets can ++ be passed."; ++ } ++ enum unknown { ++ value 4; ++ description ++ "Status cannot be determined for some reason."; ++ } ++ enum dormant { ++ value 5; ++ description ++ "Waiting for some external event."; ++ } ++ enum not-present { ++ value 6; ++ description ++ "Some component (typically hardware) is missing."; ++ } ++ enum lower-layer-down { ++ value 7; ++ description ++ "Down due to state of lower-layer interface(s)."; ++ } ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The current operational state of the interface. ++ ++ This leaf has the same semantics as ifOperStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOperStatus"; ++ } ++ ++ leaf last-change { ++ type yang:date-and-time; ++ status deprecated; ++ description ++ "The time the interface entered its current operational ++ state. If the current state was entered prior to the ++ last re-initialization of the local network management ++ subsystem, then this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifLastChange"; ++ } ++ ++ leaf if-index { ++ if-feature if-mib; ++ type int32 { ++ range "1..2147483647"; ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The ifIndex value for the ifEntry represented by this ++ interface."; ++ ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifIndex"; ++ } ++ ++ leaf phys-address { ++ type yang:phys-address; ++ status deprecated; ++ description ++ "The interface's address at its protocol sub-layer. For ++ example, for an 802.x interface, this object normally ++ contains a Media Access Control (MAC) address. The ++ interface's media-specific modules must define the bit ++ and byte ordering and the format of the value of this ++ object. For interfaces that do not have such an address ++ (e.g., a serial line), this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; ++ } ++ ++ leaf-list higher-layer-if { ++ type interface-state-ref; ++ status deprecated; ++ description ++ "A list of references to interfaces layered on top of this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf-list lower-layer-if { ++ type interface-state-ref; ++ status deprecated; ++ description ++ "A list of references to interfaces layered underneath this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf speed { ++ type yang:gauge64; ++ units "bits/second"; ++ status deprecated; ++ description ++ "An estimate of the interface's current bandwidth in bits ++ per second. For interfaces that do not vary in ++ bandwidth or for those where no accurate estimation can ++ ++ be made, this node should contain the nominal bandwidth. ++ For interfaces that have no concept of bandwidth, this ++ node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifSpeed, ifHighSpeed"; ++ } ++ ++ container statistics { ++ status deprecated; ++ description ++ "A collection of interface-related statistics objects."; ++ ++ leaf discontinuity-time { ++ type yang:date-and-time; ++ mandatory true; ++ status deprecated; ++ description ++ "The time on the most recent occasion at which any one or ++ more of this interface's counters suffered a ++ discontinuity. If no such discontinuities have occurred ++ since the last re-initialization of the local management ++ subsystem, then this node contains the time the local ++ management subsystem re-initialized itself."; ++ } ++ ++ leaf in-octets { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of octets received on the interface, ++ including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; ++ } ++ ++ leaf in-unicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were not addressed to a ++ multicast or broadcast address at this sub-layer. ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; ++ } ++ ++ leaf in-broadcast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a broadcast ++ address at this sub-layer. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInBroadcastPkts"; ++ } ++ ++ leaf in-multicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a multicast ++ address at this sub-layer. For a MAC-layer protocol, ++ this includes both Group and Functional addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInMulticastPkts"; ++ } ++ ++ leaf in-discards { ++ type yang:counter32; ++ status deprecated; ++ ++ description ++ "The number of inbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being deliverable to a higher-layer ++ protocol. One possible reason for discarding such a ++ packet could be to free up buffer space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInDiscards"; ++ } ++ ++ leaf in-errors { ++ type yang:counter32; ++ status deprecated; ++ description ++ "For packet-oriented interfaces, the number of inbound ++ packets that contained errors preventing them from being ++ deliverable to a higher-layer protocol. For character- ++ oriented or fixed-length interfaces, the number of ++ inbound transmission units that contained errors ++ preventing them from being deliverable to a higher-layer ++ protocol. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInErrors"; ++ } ++ ++ leaf in-unknown-protos { ++ type yang:counter32; ++ status deprecated; ++ description ++ "For packet-oriented interfaces, the number of packets ++ received via the interface that were discarded because ++ of an unknown or unsupported protocol. For ++ character-oriented or fixed-length interfaces that ++ support protocol multiplexing, the number of ++ transmission units received via the interface that were ++ discarded because of an unknown or unsupported protocol. ++ For any interface that does not support protocol ++ multiplexing, this counter is not present. ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; ++ } ++ ++ leaf out-octets { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of octets transmitted out of the ++ interface, including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; ++ } ++ ++ leaf out-unicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were not addressed ++ to a multicast or broadcast address at this sub-layer, ++ including those that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; ++ } ++ ++ leaf out-broadcast-pkts { ++ type yang:counter64; ++ status deprecated; ++ ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ broadcast address at this sub-layer, including those ++ that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutBroadcastPkts"; ++ } ++ ++ leaf out-multicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ multicast address at this sub-layer, including those ++ that were discarded or not sent. For a MAC-layer ++ protocol, this includes both Group and Functional ++ addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutMulticastPkts"; ++ } ++ ++ leaf out-discards { ++ type yang:counter32; ++ status deprecated; ++ description ++ "The number of outbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being transmitted. One possible reason ++ for discarding such a packet could be to free up buffer ++ space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; ++ } ++ ++ leaf out-errors { ++ type yang:counter32; ++ status deprecated; ++ description ++ "For packet-oriented interfaces, the number of outbound ++ packets that could not be transmitted because of errors. ++ For character-oriented or fixed-length interfaces, the ++ number of outbound transmission units that could not be ++ transmitted because of errors. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutErrors"; ++ } ++ } ++ } ++ } ++} +diff --git a/yang/ietf-ip@2018-02-22.yang b/yang/ietf-ip@2018-02-22.yang +new file mode 100644 +index 0000000..a270f67 +--- /dev/null ++++ b/yang/ietf-ip@2018-02-22.yang +@@ -0,0 +1,876 @@ ++module ietf-ip { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-ip"; ++ prefix ip; ++ ++ import ietf-interfaces { ++ prefix if; ++ } ++ import ietf-inet-types { ++ prefix inet; ++ } ++ import ietf-yang-types { ++ prefix yang; ++ } ++ ++ organization ++ "IETF NETMOD (Network Modeling) Working Group"; ++ ++ contact ++ "WG Web: ++ WG List: ++ ++ Editor: Martin Bjorklund ++ "; ++ description ++ "This module contains a collection of YANG definitions for ++ managing IP implementations. ++ ++ Copyright (c) 2018 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8344; see ++ the RFC itself for full legal notices."; ++ ++ revision 2018-02-22 { ++ description ++ "Updated to support NMDA."; ++ reference ++ "RFC 8344: A YANG Data Model for IP Management"; ++ } ++ ++ revision 2014-06-16 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 7277: A YANG Data Model for IP Management"; ++ } ++ ++ /* ++ * Features ++ */ ++ ++ feature ipv4-non-contiguous-netmasks { ++ description ++ "Indicates support for configuring non-contiguous ++ subnet masks."; ++ } ++ ++ feature ipv6-privacy-autoconf { ++ description ++ "Indicates support for privacy extensions for stateless address ++ autoconfiguration in IPv6."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6"; ++ } ++ ++ /* ++ * Typedefs ++ */ ++ ++ typedef ip-address-origin { ++ type enumeration { ++ enum other { ++ description ++ "None of the following."; ++ } ++ ++ enum static { ++ description ++ "Indicates that the address has been statically ++ configured -- for example, using the Network Configuration ++ Protocol (NETCONF) or a command line interface."; ++ } ++ enum dhcp { ++ description ++ "Indicates an address that has been assigned to this ++ system by a DHCP server."; ++ } ++ enum link-layer { ++ description ++ "Indicates an address created by IPv6 stateless ++ autoconfiguration that embeds a link-layer address in its ++ interface identifier."; ++ } ++ enum random { ++ description ++ "Indicates an address chosen by the system at ++ random, e.g., an IPv4 address within 169.254/16, a ++ temporary address as described in RFC 4941, or a ++ semantically opaque address as described in RFC 7217."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6 ++ RFC 7217: A Method for Generating Semantically Opaque ++ Interface Identifiers with IPv6 Stateless ++ Address Autoconfiguration (SLAAC)"; ++ } ++ } ++ description ++ "The origin of an address."; ++ } ++ ++ typedef neighbor-origin { ++ type enumeration { ++ enum other { ++ description ++ "None of the following."; ++ } ++ enum static { ++ description ++ "Indicates that the mapping has been statically ++ configured -- for example, using NETCONF or a command line ++ interface."; ++ } ++ ++ enum dynamic { ++ description ++ "Indicates that the mapping has been dynamically resolved ++ using, for example, IPv4 ARP or the IPv6 Neighbor ++ Discovery protocol."; ++ } ++ } ++ description ++ "The origin of a neighbor entry."; ++ } ++ ++ /* ++ * Data nodes ++ */ ++ ++ augment "/if:interfaces/if:interface" { ++ description ++ "IP parameters on interfaces. ++ ++ If an interface is not capable of running IP, the server ++ must not allow the client to configure these parameters."; ++ ++ container ipv4 { ++ presence ++ "Enables IPv4 unless the 'enabled' leaf ++ (which defaults to 'true') is set to 'false'"; ++ description ++ "Parameters for the IPv4 address family."; ++ ++ leaf enabled { ++ type boolean; ++ default true; ++ description ++ "Controls whether IPv4 is enabled or disabled on this ++ interface. When IPv4 is enabled, this interface is ++ connected to an IPv4 stack, and the interface can send ++ and receive IPv4 packets."; ++ } ++ leaf forwarding { ++ type boolean; ++ default false; ++ description ++ "Controls IPv4 packet forwarding of datagrams received by, ++ but not addressed to, this interface. IPv4 routers ++ forward datagrams. IPv4 hosts do not (except those ++ source-routed via the host)."; ++ } ++ ++ leaf mtu { ++ type uint16 { ++ range "68..max"; ++ } ++ units "octets"; ++ description ++ "The size, in octets, of the largest IPv4 packet that the ++ interface will send and receive. ++ ++ The server may restrict the allowed values for this leaf, ++ depending on the interface's type. ++ ++ If this leaf is not configured, the operationally used MTU ++ depends on the interface's type."; ++ reference ++ "RFC 791: Internet Protocol"; ++ } ++ list address { ++ key "ip"; ++ description ++ "The list of IPv4 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ description ++ "The IPv4 address on the interface."; ++ } ++ choice subnet { ++ mandatory true; ++ description ++ "The subnet can be specified as a prefix length or, ++ if the server supports non-contiguous netmasks, as ++ a netmask."; ++ leaf prefix-length { ++ type uint8 { ++ range "0..32"; ++ } ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf netmask { ++ if-feature ipv4-non-contiguous-netmasks; ++ type yang:dotted-quad; ++ description ++ "The subnet specified as a netmask."; ++ } ++ } ++ ++ leaf origin { ++ type ip-address-origin; ++ config false; ++ description ++ "The origin of this address."; ++ } ++ } ++ list neighbor { ++ key "ip"; ++ description ++ "A list of mappings from IPv4 addresses to ++ link-layer addresses. ++ ++ Entries in this list in the intended configuration are ++ used as static entries in the ARP Cache. ++ ++ In the operational state, this list represents the ARP ++ Cache."; ++ reference ++ "RFC 826: An Ethernet Address Resolution Protocol"; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ description ++ "The IPv4 address of the neighbor node."; ++ } ++ leaf link-layer-address { ++ type yang:phys-address; ++ mandatory true; ++ description ++ "The link-layer address of the neighbor node."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ config false; ++ description ++ "The origin of this neighbor entry."; ++ } ++ } ++ } ++ ++ container ipv6 { ++ presence ++ "Enables IPv6 unless the 'enabled' leaf ++ (which defaults to 'true') is set to 'false'"; ++ description ++ "Parameters for the IPv6 address family."; ++ ++ leaf enabled { ++ type boolean; ++ default true; ++ description ++ "Controls whether IPv6 is enabled or disabled on this ++ interface. When IPv6 is enabled, this interface is ++ connected to an IPv6 stack, and the interface can send ++ and receive IPv6 packets."; ++ } ++ leaf forwarding { ++ type boolean; ++ default false; ++ description ++ "Controls IPv6 packet forwarding of datagrams received by, ++ but not addressed to, this interface. IPv6 routers ++ forward datagrams. IPv6 hosts do not (except those ++ source-routed via the host)."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 6.2.1, IsRouter"; ++ } ++ leaf mtu { ++ type uint32 { ++ range "1280..max"; ++ } ++ units "octets"; ++ description ++ "The size, in octets, of the largest IPv6 packet that the ++ interface will send and receive. ++ ++ The server may restrict the allowed values for this leaf, ++ depending on the interface's type. ++ ++ If this leaf is not configured, the operationally used MTU ++ depends on the interface's type."; ++ reference ++ "RFC 8200: Internet Protocol, Version 6 (IPv6) ++ Specification ++ Section 5"; ++ } ++ ++ list address { ++ key "ip"; ++ description ++ "The list of IPv6 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ description ++ "The IPv6 address on the interface."; ++ } ++ leaf prefix-length { ++ type uint8 { ++ range "0..128"; ++ } ++ mandatory true; ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf origin { ++ type ip-address-origin; ++ config false; ++ description ++ "The origin of this address."; ++ } ++ leaf status { ++ type enumeration { ++ enum preferred { ++ description ++ "This is a valid address that can appear as the ++ destination or source address of a packet."; ++ } ++ enum deprecated { ++ description ++ "This is a valid but deprecated address that should ++ no longer be used as a source address in new ++ communications, but packets addressed to such an ++ address are processed as expected."; ++ } ++ enum invalid { ++ description ++ "This isn't a valid address, and it shouldn't appear ++ as the destination or source address of a packet."; ++ } ++ ++ enum inaccessible { ++ description ++ "The address is not accessible because the interface ++ to which this address is assigned is not ++ operational."; ++ } ++ enum unknown { ++ description ++ "The status cannot be determined for some reason."; ++ } ++ enum tentative { ++ description ++ "The uniqueness of the address on the link is being ++ verified. Addresses in this state should not be ++ used for general communication and should only be ++ used to determine the uniqueness of the address."; ++ } ++ enum duplicate { ++ description ++ "The address has been determined to be non-unique on ++ the link and so must not be used."; ++ } ++ enum optimistic { ++ description ++ "The address is available for use, subject to ++ restrictions, while its uniqueness on a link is ++ being verified."; ++ } ++ } ++ config false; ++ description ++ "The status of an address. Most of the states correspond ++ to states from the IPv6 Stateless Address ++ Autoconfiguration protocol."; ++ reference ++ "RFC 4293: Management Information Base for the ++ Internet Protocol (IP) ++ - IpAddressStatusTC ++ RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ } ++ } ++ ++ list neighbor { ++ key "ip"; ++ description ++ "A list of mappings from IPv6 addresses to ++ link-layer addresses. ++ ++ Entries in this list in the intended configuration are ++ used as static entries in the Neighbor Cache. ++ ++ In the operational state, this list represents the ++ Neighbor Cache."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ description ++ "The IPv6 address of the neighbor node."; ++ } ++ leaf link-layer-address { ++ type yang:phys-address; ++ mandatory true; ++ description ++ "The link-layer address of the neighbor node. ++ ++ In the operational state, if the neighbor's 'state' leaf ++ is 'incomplete', this leaf is not instantiated."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ config false; ++ description ++ "The origin of this neighbor entry."; ++ } ++ leaf is-router { ++ type empty; ++ config false; ++ description ++ "Indicates that the neighbor node acts as a router."; ++ } ++ ++ leaf state { ++ type enumeration { ++ enum incomplete { ++ description ++ "Address resolution is in progress, and the ++ link-layer address of the neighbor has not yet been ++ determined."; ++ } ++ enum reachable { ++ description ++ "Roughly speaking, the neighbor is known to have been ++ reachable recently (within tens of seconds ago)."; ++ } ++ enum stale { ++ description ++ "The neighbor is no longer known to be reachable, but ++ until traffic is sent to the neighbor no attempt ++ should be made to verify its reachability."; ++ } ++ enum delay { ++ description ++ "The neighbor is no longer known to be reachable, and ++ traffic has recently been sent to the neighbor. ++ Rather than probe the neighbor immediately, however, ++ delay sending probes for a short while in order to ++ give upper-layer protocols a chance to provide ++ reachability confirmation."; ++ } ++ enum probe { ++ description ++ "The neighbor is no longer known to be reachable, and ++ unicast Neighbor Solicitation probes are being sent ++ to verify reachability."; ++ } ++ } ++ config false; ++ description ++ "The Neighbor Unreachability Detection state of this ++ entry."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 7.3.2"; ++ } ++ } ++ ++ leaf dup-addr-detect-transmits { ++ type uint32; ++ default 1; ++ description ++ "The number of consecutive Neighbor Solicitation messages ++ sent while performing Duplicate Address Detection on a ++ tentative address. A value of zero indicates that ++ Duplicate Address Detection is not performed on ++ tentative addresses. A value of one indicates a single ++ transmission with no follow-up retransmissions."; ++ reference ++ "RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ } ++ container autoconf { ++ description ++ "Parameters to control the autoconfiguration of IPv6 ++ addresses, as described in RFC 4862."; ++ reference ++ "RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ ++ leaf create-global-addresses { ++ type boolean; ++ default true; ++ description ++ "If enabled, the host creates global addresses as ++ described in RFC 4862."; ++ reference ++ "RFC 4862: IPv6 Stateless Address Autoconfiguration ++ Section 5.5"; ++ } ++ leaf create-temporary-addresses { ++ if-feature ipv6-privacy-autoconf; ++ type boolean; ++ default false; ++ description ++ "If enabled, the host creates temporary addresses as ++ described in RFC 4941."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6"; ++ } ++ ++ leaf temporary-valid-lifetime { ++ if-feature ipv6-privacy-autoconf; ++ type uint32; ++ units "seconds"; ++ default 604800; ++ description ++ "The time period during which the temporary address ++ is valid."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6 ++ - TEMP_VALID_LIFETIME"; ++ } ++ leaf temporary-preferred-lifetime { ++ if-feature ipv6-privacy-autoconf; ++ type uint32; ++ units "seconds"; ++ default 86400; ++ description ++ "The time period during which the temporary address is ++ preferred."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6 ++ - TEMP_PREFERRED_LIFETIME"; ++ } ++ } ++ } ++ } ++ ++ /* ++ * Legacy operational state data nodes ++ */ ++ ++ augment "/if:interfaces-state/if:interface" { ++ status deprecated; ++ description ++ "Data nodes for the operational state of IP on interfaces."; ++ ++ container ipv4 { ++ presence ++ "Present if IPv4 is enabled on this interface"; ++ config false; ++ status deprecated; ++ description ++ "Interface-specific parameters for the IPv4 address family."; ++ ++ leaf forwarding { ++ type boolean; ++ status deprecated; ++ description ++ "Indicates whether IPv4 packet forwarding is enabled or ++ disabled on this interface."; ++ } ++ leaf mtu { ++ type uint16 { ++ range "68..max"; ++ } ++ units "octets"; ++ status deprecated; ++ description ++ "The size, in octets, of the largest IPv4 packet that the ++ interface will send and receive."; ++ reference ++ "RFC 791: Internet Protocol"; ++ } ++ list address { ++ key "ip"; ++ status deprecated; ++ description ++ "The list of IPv4 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ status deprecated; ++ description ++ "The IPv4 address on the interface."; ++ } ++ choice subnet { ++ status deprecated; ++ description ++ "The subnet can be specified as a prefix length or, ++ if the server supports non-contiguous netmasks, as ++ a netmask."; ++ leaf prefix-length { ++ type uint8 { ++ range "0..32"; ++ } ++ status deprecated; ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf netmask { ++ if-feature ipv4-non-contiguous-netmasks; ++ type yang:dotted-quad; ++ status deprecated; ++ description ++ "The subnet specified as a netmask."; ++ } ++ } ++ leaf origin { ++ type ip-address-origin; ++ status deprecated; ++ description ++ "The origin of this address."; ++ } ++ } ++ list neighbor { ++ key "ip"; ++ status deprecated; ++ description ++ "A list of mappings from IPv4 addresses to ++ link-layer addresses. ++ ++ This list represents the ARP Cache."; ++ reference ++ "RFC 826: An Ethernet Address Resolution Protocol"; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ status deprecated; ++ description ++ "The IPv4 address of the neighbor node."; ++ } ++ ++ leaf link-layer-address { ++ type yang:phys-address; ++ status deprecated; ++ description ++ "The link-layer address of the neighbor node."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ status deprecated; ++ description ++ "The origin of this neighbor entry."; ++ } ++ } ++ } ++ ++ container ipv6 { ++ presence ++ "Present if IPv6 is enabled on this interface"; ++ config false; ++ status deprecated; ++ description ++ "Parameters for the IPv6 address family."; ++ ++ leaf forwarding { ++ type boolean; ++ default false; ++ status deprecated; ++ description ++ "Indicates whether IPv6 packet forwarding is enabled or ++ disabled on this interface."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 6.2.1, IsRouter"; ++ } ++ leaf mtu { ++ type uint32 { ++ range "1280..max"; ++ } ++ units "octets"; ++ status deprecated; ++ description ++ "The size, in octets, of the largest IPv6 packet that the ++ interface will send and receive."; ++ reference ++ "RFC 8200: Internet Protocol, Version 6 (IPv6) ++ Specification ++ Section 5"; ++ } ++ list address { ++ key "ip"; ++ status deprecated; ++ description ++ "The list of IPv6 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ status deprecated; ++ description ++ "The IPv6 address on the interface."; ++ } ++ leaf prefix-length { ++ type uint8 { ++ range "0..128"; ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf origin { ++ type ip-address-origin; ++ status deprecated; ++ description ++ "The origin of this address."; ++ } ++ leaf status { ++ type enumeration { ++ enum preferred { ++ description ++ "This is a valid address that can appear as the ++ destination or source address of a packet."; ++ } ++ enum deprecated { ++ description ++ "This is a valid but deprecated address that should ++ no longer be used as a source address in new ++ communications, but packets addressed to such an ++ address are processed as expected."; ++ } ++ enum invalid { ++ description ++ "This isn't a valid address, and it shouldn't appear ++ as the destination or source address of a packet."; ++ } ++ ++ enum inaccessible { ++ description ++ "The address is not accessible because the interface ++ to which this address is assigned is not ++ operational."; ++ } ++ enum unknown { ++ description ++ "The status cannot be determined for some reason."; ++ } ++ enum tentative { ++ description ++ "The uniqueness of the address on the link is being ++ verified. Addresses in this state should not be ++ used for general communication and should only be ++ used to determine the uniqueness of the address."; ++ } ++ enum duplicate { ++ description ++ "The address has been determined to be non-unique on ++ the link and so must not be used."; ++ } ++ enum optimistic { ++ description ++ "The address is available for use, subject to ++ restrictions, while its uniqueness on a link is ++ being verified."; ++ } ++ } ++ status deprecated; ++ description ++ "The status of an address. Most of the states correspond ++ to states from the IPv6 Stateless Address ++ Autoconfiguration protocol."; ++ reference ++ "RFC 4293: Management Information Base for the ++ Internet Protocol (IP) ++ - IpAddressStatusTC ++ RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ } ++ } ++ ++ list neighbor { ++ key "ip"; ++ status deprecated; ++ description ++ "A list of mappings from IPv6 addresses to ++ link-layer addresses. ++ ++ This list represents the Neighbor Cache."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ status deprecated; ++ description ++ "The IPv6 address of the neighbor node."; ++ } ++ leaf link-layer-address { ++ type yang:phys-address; ++ status deprecated; ++ description ++ "The link-layer address of the neighbor node."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ status deprecated; ++ description ++ "The origin of this neighbor entry."; ++ } ++ leaf is-router { ++ type empty; ++ status deprecated; ++ description ++ "Indicates that the neighbor node acts as a router."; ++ } ++ leaf state { ++ type enumeration { ++ enum incomplete { ++ description ++ "Address resolution is in progress, and the ++ link-layer address of the neighbor has not yet been ++ determined."; ++ } ++ enum reachable { ++ description ++ "Roughly speaking, the neighbor is known to have been ++ reachable recently (within tens of seconds ago)."; ++ } ++ enum stale { ++ description ++ "The neighbor is no longer known to be reachable, but ++ until traffic is sent to the neighbor no attempt ++ should be made to verify its reachability."; ++ } ++ enum delay { ++ description ++ "The neighbor is no longer known to be reachable, and ++ traffic has recently been sent to the neighbor. ++ Rather than probe the neighbor immediately, however, ++ delay sending probes for a short while in order to ++ give upper-layer protocols a chance to provide ++ reachability confirmation."; ++ } ++ enum probe { ++ description ++ "The neighbor is no longer known to be reachable, and ++ unicast Neighbor Solicitation probes are being sent ++ to verify reachability."; ++ } ++ } ++ status deprecated; ++ description ++ "The Neighbor Unreachability Detection state of this ++ entry."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 7.3.2"; ++ } ++ } ++ } ++ } ++} +diff --git a/yang/ietf-network-instance@2019-01-21.yang b/yang/ietf-network-instance@2019-01-21.yang +new file mode 100644 +index 0000000..dfde7fb +--- /dev/null ++++ b/yang/ietf-network-instance@2019-01-21.yang +@@ -0,0 +1,282 @@ ++module ietf-network-instance { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-network-instance"; ++ prefix ni; ++ ++ // import some basic types ++ ++ import ietf-interfaces { ++ prefix if; ++ reference ++ "RFC 8343: A YANG Data Model for Interface Management"; ++ } ++ import ietf-ip { ++ prefix ip; ++ reference ++ "RFC 8344: A YANG Data Model for IP Management"; ++ } ++ import ietf-yang-schema-mount { ++ prefix yangmnt; ++ reference ++ "RFC 8528: YANG Schema Mount"; ++ } ++ ++ organization ++ "IETF Routing Area (rtgwg) Working Group"; ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Lou Berger ++ ++ Author: Christian Hopps ++ ++ Author: Acee Lindem ++ ++ Author: Dean Bogdanovic ++ "; ++ description ++ "This module is used to support multiple network instances ++ within a single physical or virtual device. Network ++ instances are commonly known as VRFs (VPN Routing and ++ Forwarding) and VSIs (Virtual Switching Instances). ++ The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', ++ 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', ++ 'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document ++ are to be interpreted as described in BCP 14 (RFC 2119) ++ (RFC 8174) when, and only when, they appear in all capitals, ++ as shown here. ++ ++ Copyright (c) 2019 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD ++ License set forth in Section 4.c of the IETF Trust's Legal ++ Provisions Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8529; see ++ the RFC itself for full legal notices."; ++ ++ revision 2019-01-21 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 8529"; ++ } ++ ++ // top-level device definition statements ++ ++ container network-instances { ++ description ++ "Network instances, each of which consists of ++ VRFs and/or VSIs."; ++ reference ++ "RFC 8349: A YANG Data Model for Routing Management"; ++ list network-instance { ++ key "name"; ++ description ++ "List of network instances."; ++ leaf name { ++ type string; ++ mandatory true; ++ description ++ "device-scoped identifier for the network ++ instance."; ++ } ++ leaf enabled { ++ type boolean; ++ default "true"; ++ description ++ "Flag indicating whether or not the network ++ instance is enabled."; ++ } ++ leaf description { ++ type string; ++ description ++ "Description of the network instance ++ and its intended purpose."; ++ } ++ choice ni-type { ++ description ++ "This node serves as an anchor point for different types ++ of network instances. Each 'case' is expected to ++ differ in terms of the information needed in the ++ parent/core to support the NI and may differ in their ++ mounted-schema definition. When the mounted schema is ++ not expected to be the same for a specific type of NI, ++ a mount point should be defined."; ++ } ++ choice root-type { ++ mandatory true; ++ description ++ "Well-known mount points."; ++ container vrf-root { ++ description ++ "Container for mount point."; ++ yangmnt:mount-point "vrf-root" { ++ description ++ "Root for L3VPN-type models. This will typically ++ not be an inline-type mount point."; ++ } ++ } ++ container vsi-root { ++ description ++ "Container for mount point."; ++ yangmnt:mount-point "vsi-root" { ++ description ++ "Root for L2VPN-type models. This will typically ++ not be an inline-type mount point."; ++ } ++ } ++ container vv-root { ++ description ++ "Container for mount point."; ++ yangmnt:mount-point "vv-root" { ++ description ++ "Root models that support both L2VPN-type bridging ++ and L3VPN-type routing. This will typically ++ not be an inline-type mount point."; ++ } ++ } ++ } ++ } ++ } ++ ++ // augment statements ++ ++ augment "/if:interfaces/if:interface" { ++ description ++ "Add a node for the identification of the network ++ instance associated with the information configured ++ on a interface. ++ ++ Note that a standard error will be returned if the ++ identified leafref isn't present. If an interface cannot ++ be assigned for any other reason, the operation SHALL fail ++ with an error-tag of 'operation-failed' and an ++ error-app-tag of 'ni-assignment-failed'. A meaningful ++ error-info that indicates the source of the assignment ++ failure SHOULD also be provided."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/network-instances/network-instance/name"; ++ } ++ description ++ "Network instance to which an interface is bound."; ++ } ++ } ++ augment "/if:interfaces/if:interface/ip:ipv4" { ++ description ++ "Add a node for the identification of the network ++ instance associated with the information configured ++ on an IPv4 interface. ++ ++ Note that a standard error will be returned if the ++ identified leafref isn't present. If an interface cannot ++ be assigned for any other reason, the operation SHALL fail ++ with an error-tag of 'operation-failed' and an ++ error-app-tag of 'ni-assignment-failed'. A meaningful ++ error-info that indicates the source of the assignment ++ failure SHOULD also be provided."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/network-instances/network-instance/name"; ++ } ++ description ++ "Network instance to which IPv4 interface is bound."; ++ } ++ } ++ augment "/if:interfaces/if:interface/ip:ipv6" { ++ description ++ "Add a node for the identification of the network ++ instance associated with the information configured ++ on an IPv6 interface. ++ ++ Note that a standard error will be returned if the ++ identified leafref isn't present. If an interface cannot ++ be assigned for any other reason, the operation SHALL fail ++ with an error-tag of 'operation-failed' and an ++ error-app-tag of 'ni-assignment-failed'. A meaningful ++ error-info that indicates the source of the assignment ++ failure SHOULD also be provided."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/network-instances/network-instance/name"; ++ } ++ description ++ "Network instance to which IPv6 interface is bound."; ++ } ++ } ++ ++ // notification statements ++ ++ notification bind-ni-name-failed { ++ description ++ "Indicates an error in the association of an interface to an ++ NI. Only generated after success is initially returned when ++ bind-ni-name is set. ++ ++ Note: Some errors may need to be reported for multiple ++ associations, e.g., a single error may need to be reported ++ for an IPv4 and an IPv6 bind-ni-name. ++ ++ At least one container with a bind-ni-name leaf MUST be ++ included in this notification."; ++ leaf name { ++ type leafref { ++ path "/if:interfaces/if:interface/if:name"; ++ } ++ mandatory true; ++ description ++ "Contains the interface name associated with the ++ failure."; ++ } ++ container interface { ++ description ++ "Generic interface type."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/if:interfaces/if:interface" ++ + "/ni:bind-ni-name"; ++ } ++ description ++ "Contains the bind-ni-name associated with the ++ failure."; ++ } ++ } ++ container ipv4 { ++ description ++ "IPv4 interface type."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/if:interfaces/if:interface/ip:ipv4/ni:bind-ni-name"; ++ } ++ description ++ "Contains the bind-ni-name associated with the ++ failure."; ++ } ++ } ++ container ipv6 { ++ description ++ "IPv6 interface type."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/if:interfaces/if:interface/ip:ipv6" ++ + "/ni:bind-ni-name"; ++ } ++ description ++ "Contains the bind-ni-name associated with the ++ failure."; ++ } ++ } ++ leaf error-info { ++ type string; ++ description ++ "Optionally, indicates the source of the assignment ++ failure."; ++ } ++ } ++} +diff --git a/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang b/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang +new file mode 100644 +index 0000000..98a7990 +--- /dev/null ++++ b/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang +@@ -0,0 +1,85 @@ ++module ietf-restconf-subscribed-notifications { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:" ++ + "ietf-restconf-subscribed-notifications"; ++ prefix rsn; ++ ++ import ietf-subscribed-notifications { ++ prefix sn; ++ } ++ import ietf-inet-types { ++ prefix inet; ++ } ++ ++ organization ++ "IETF NETCONF (Network Configuration) Working Group"; ++ contact ++ "WG Web: ++ WG List: ++ ++ Editor: Eric Voit ++ ++ ++ Editor: Alexander Clemm ++ ++ ++ Editor: Reshad Rahman ++ "; ++ description ++ "Defines RESTCONF as a supported transport for subscribed ++ event notifications. ++ ++ Copyright (c) 2019 IETF Trust and the persons identified ++ as authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject to ++ the license terms contained in, the Simplified BSD License set ++ forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8650; see the ++ RFC itself for full legal notices."; ++ ++ revision 2019-11-17 { ++ description ++ "Initial version"; ++ reference ++ "RFC 8650: Dynamic Subscription to YANG Events and Datastores ++ over RESTCONF"; ++ } ++ ++ grouping uri { ++ description ++ "Provides a reusable description of a URI."; ++ leaf uri { ++ type inet:uri; ++ config false; ++ description ++ "Location of a subscription-specific URI on the publisher."; ++ } ++ } ++ ++ augment "/sn:establish-subscription/sn:output" { ++ description ++ "This augmentation allows RESTCONF-specific parameters for a ++ response to a publisher's subscription request."; ++ uses uri; ++ } ++ ++ augment "/sn:subscriptions/sn:subscription" { ++ description ++ "This augmentation allows RESTCONF-specific parameters to be ++ exposed for a subscription."; ++ uses uri; ++ } ++ ++ augment "/sn:subscription-modified" { ++ description ++ "This augmentation allows RESTCONF-specific parameters to be ++ included as part of the notification that a subscription has ++ been modified."; ++ uses uri; ++ } ++} +diff --git a/yang/ietf-subscribed-notifications@2019-09-09.yang b/yang/ietf-subscribed-notifications@2019-09-09.yang +new file mode 100644 +index 0000000..e04593c +--- /dev/null ++++ b/yang/ietf-subscribed-notifications@2019-09-09.yang +@@ -0,0 +1,1350 @@ ++module ietf-subscribed-notifications { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications"; ++ prefix sn; ++ ++ import ietf-inet-types { ++ prefix inet; ++ reference ++ "RFC 6991: Common YANG Data Types"; ++ } ++ import ietf-interfaces { ++ prefix if; ++ reference ++ "RFC 8343: A YANG Data Model for Interface Management"; ++ } ++ import ietf-netconf-acm { ++ prefix nacm; ++ reference ++ "RFC 8341: Network Configuration Access Control Model"; ++ } ++ import ietf-network-instance { ++ prefix ni; ++ reference ++ "RFC 8529: YANG Data Model for Network Instances"; ++ } ++ import ietf-restconf { ++ prefix rc; ++ reference ++ "RFC 8040: RESTCONF Protocol"; ++ } ++ import ietf-yang-types { ++ prefix yang; ++ reference ++ "RFC 6991: Common YANG Data Types"; ++ } ++ ++ organization ++ "IETF NETCONF (Network Configuration) Working Group"; ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Alexander Clemm ++ ++ ++ Author: Eric Voit ++ ++ ++ Author: Alberto Gonzalez Prieto ++ ++ ++ Author: Einar Nilsen-Nygaard ++ ++ ++ Author: Ambika Prasad Tripathy ++ "; ++ description ++ "This module defines a YANG data model for subscribing to event ++ records and receiving matching content in notification messages. ++ ++ The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL ++ NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', ++ 'MAY', and 'OPTIONAL' in this document are to be interpreted as ++ described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, ++ they appear in all capitals, as shown here. ++ ++ Copyright (c) 2019 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject to ++ the license terms contained in, the Simplified BSD License set ++ forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8639; see the ++ RFC itself for full legal notices."; ++ ++ revision 2019-09-09 { ++ description ++ "Initial version."; ++ reference ++ "RFC 8639: A YANG Data Model for Subscriptions to ++ Event Notifications"; ++ } ++ ++ /* ++ * FEATURES ++ */ ++ ++ feature configured { ++ description ++ "This feature indicates that configuration of subscriptions is ++ supported."; ++ } ++ ++ feature dscp { ++ description ++ "This feature indicates that a publisher supports the ability ++ to set the Differentiated Services Code Point (DSCP) value in ++ outgoing packets."; ++ } ++ ++ feature encode-json { ++ description ++ "This feature indicates that JSON encoding of notification ++ messages is supported."; ++ } ++ ++ feature encode-xml { ++ description ++ "This feature indicates that XML encoding of notification ++ messages is supported."; ++ } ++ ++ feature interface-designation { ++ description ++ "This feature indicates that a publisher supports sourcing all ++ receiver interactions for a configured subscription from a ++ single designated egress interface."; ++ } ++ ++ feature qos { ++ description ++ "This feature indicates that a publisher supports absolute ++ dependencies of one subscription's traffic over another ++ as well as weighted bandwidth sharing between subscriptions. ++ Both of these are Quality of Service (QoS) features that allow ++ differentiated treatment of notification messages between a ++ publisher and a specific receiver."; ++ } ++ ++ feature replay { ++ description ++ "This feature indicates that historical event record replay is ++ supported. With replay, it is possible for past event records ++ to be streamed in chronological order."; ++ } ++ ++ feature subtree { ++ description ++ "This feature indicates support for YANG subtree filtering."; ++ reference ++ "RFC 6241: Network Configuration Protocol (NETCONF), ++ Section 6"; ++ } ++ ++ feature supports-vrf { ++ description ++ "This feature indicates that a publisher supports VRF ++ configuration for configured subscriptions. VRF support for ++ dynamic subscriptions does not require this feature."; ++ reference ++ "RFC 8529: YANG Data Model for Network Instances, ++ Section 6"; ++ } ++ ++ feature xpath { ++ description ++ "This feature indicates support for XPath filtering."; ++ reference ++ "XML Path Language (XPath) Version 1.0 ++ (https://www.w3.org/TR/1999/REC-xpath-19991116)"; ++ } ++ ++ /* ++ * EXTENSIONS ++ */ ++ ++ extension subscription-state-notification { ++ description ++ "This statement applies only to notifications. It indicates ++ that the notification is a subscription state change ++ notification. Therefore, it does not participate in a regular ++ event stream and does not need to be specifically subscribed ++ to in order to be received. This statement can only occur as ++ a substatement of the YANG 'notification' statement. This ++ statement is not for use outside of this YANG module."; ++ } ++ ++ /* ++ * IDENTITIES ++ */ ++ /* Identities for RPC and notification errors */ ++ ++ identity delete-subscription-error { ++ description ++ "Base identity for the problem found while attempting to ++ fulfill either a 'delete-subscription' RPC request or a ++ 'kill-subscription' RPC request."; ++ } ++ ++ identity establish-subscription-error { ++ description ++ "Base identity for the problem found while attempting to ++ fulfill an 'establish-subscription' RPC request."; ++ } ++ ++ identity modify-subscription-error { ++ description ++ "Base identity for the problem found while attempting to ++ fulfill a 'modify-subscription' RPC request."; ++ } ++ ++ identity subscription-suspended-reason { ++ description ++ "Base identity for the problem condition communicated to a ++ receiver as part of a 'subscription-suspended' ++ notification."; ++ } ++ ++ identity subscription-terminated-reason { ++ description ++ "Base identity for the problem condition communicated to a ++ receiver as part of a 'subscription-terminated' ++ notification."; ++ } ++ ++ identity dscp-unavailable { ++ base establish-subscription-error; ++ if-feature "dscp"; ++ description ++ "The publisher is unable to mark notification messages with ++ prioritization information in a way that will be respected ++ during network transit."; ++ } ++ ++ identity encoding-unsupported { ++ base establish-subscription-error; ++ description ++ "Unable to encode notification messages in the desired ++ format."; ++ } ++ ++ identity filter-unavailable { ++ base subscription-terminated-reason; ++ description ++ "Referenced filter does not exist. This means a receiver is ++ referencing a filter that doesn't exist or to which it ++ does not have access permissions."; ++ } ++ ++ identity filter-unsupported { ++ base establish-subscription-error; ++ base modify-subscription-error; ++ description ++ "Cannot parse syntax in the filter. This failure can be from ++ a syntax error or a syntax too complex to be processed by the ++ publisher."; ++ } ++ ++ identity insufficient-resources { ++ base establish-subscription-error; ++ base modify-subscription-error; ++ base subscription-suspended-reason; ++ description ++ "The publisher does not have sufficient resources to support ++ the requested subscription. An example might be that ++ allocated CPU is too limited to generate the desired set of ++ notification messages."; ++ } ++ ++ identity no-such-subscription { ++ base modify-subscription-error; ++ base delete-subscription-error; ++ base subscription-terminated-reason; ++ description ++ "Referenced subscription doesn't exist. This may be as a ++ result of a nonexistent subscription ID, an ID that belongs to ++ another subscriber, or an ID for a configured subscription."; ++ } ++ ++ identity replay-unsupported { ++ base establish-subscription-error; ++ if-feature "replay"; ++ description ++ "Replay cannot be performed for this subscription. This means ++ the publisher will not provide the requested historic ++ information from the event stream via replay to this ++ receiver."; ++ } ++ ++ identity stream-unavailable { ++ base subscription-terminated-reason; ++ description ++ "Not a subscribable event stream. This means the referenced ++ event stream is not available for subscription by the ++ receiver."; ++ } ++ ++ identity suspension-timeout { ++ base subscription-terminated-reason; ++ description ++ "Termination of a previously suspended subscription. The ++ publisher has eliminated the subscription, as it exceeded a ++ time limit for suspension."; ++ } ++ ++ identity unsupportable-volume { ++ base subscription-suspended-reason; ++ description ++ "The publisher does not have the network bandwidth needed to ++ get the volume of generated information intended for a ++ receiver."; ++ } ++ ++ /* Identities for encodings */ ++ ++ identity configurable-encoding { ++ description ++ "If a transport identity derives from this identity, it means ++ that it supports configurable encodings. An example of a ++ configurable encoding might be a new identity such as ++ 'encode-cbor'. Such an identity could use ++ 'configurable-encoding' as its base. This would allow a ++ dynamic subscription encoded in JSON (RFC 8259) to request ++ that notification messages be encoded via the Concise Binary ++ Object Representation (CBOR) (RFC 7049). Further details for ++ any specific configurable encoding would be explored in a ++ transport document based on this specification."; ++ reference ++ "RFC 8259: The JavaScript Object Notation (JSON) Data ++ Interchange Format ++ RFC 7049: Concise Binary Object Representation (CBOR)"; ++ } ++ ++ identity encoding { ++ description ++ "Base identity to represent data encodings."; ++ } ++ ++ identity encode-xml { ++ base encoding; ++ if-feature "encode-xml"; ++ description ++ "Encode data using XML as described in RFC 7950."; ++ reference ++ "RFC 7950: The YANG 1.1 Data Modeling Language"; ++ } ++ ++ identity encode-json { ++ base encoding; ++ if-feature "encode-json"; ++ description ++ "Encode data using JSON as described in RFC 7951."; ++ reference ++ "RFC 7951: JSON Encoding of Data Modeled with YANG"; ++ } ++ ++ /* Identities for transports */ ++ ++ identity transport { ++ description ++ "An identity that represents the underlying mechanism for ++ passing notification messages."; ++ } ++ ++ /* ++ * TYPEDEFs ++ */ ++ ++ typedef encoding { ++ type identityref { ++ base encoding; ++ } ++ description ++ "Specifies a data encoding, e.g., for a data subscription."; ++ } ++ ++ typedef stream-filter-ref { ++ type leafref { ++ path "/sn:filters/sn:stream-filter/sn:name"; ++ } ++ description ++ "This type is used to reference an event stream filter."; ++ } ++ ++ typedef stream-ref { ++ type leafref { ++ path "/sn:streams/sn:stream/sn:name"; ++ } ++ description ++ "This type is used to reference a system-provided ++ event stream."; ++ } ++ ++ typedef subscription-id { ++ type uint32; ++ description ++ "A type for subscription identifiers."; ++ } ++ ++ typedef transport { ++ type identityref { ++ base transport; ++ } ++ description ++ "Specifies the transport used to send notification messages ++ to a receiver."; ++ } ++ ++ /* ++ * GROUPINGS ++ */ ++ ++ grouping stream-filter-elements { ++ description ++ "This grouping defines the base for filters applied to event ++ streams."; ++ choice filter-spec { ++ description ++ "The content filter specification for this request."; ++ anydata stream-subtree-filter { ++ if-feature "subtree"; ++ description ++ "Event stream evaluation criteria encoded in the syntax of ++ a subtree filter as defined in RFC 6241, Section 6. ++ ++ The subtree filter is applied to the representation of ++ individual, delineated event records as contained in the ++ event stream. ++ ++ If the subtree filter returns a non-empty node set, the ++ filter matches the event record, and the event record is ++ included in the notification message sent to the ++ receivers."; ++ reference ++ "RFC 6241: Network Configuration Protocol (NETCONF), ++ Section 6"; ++ } ++ leaf stream-xpath-filter { ++ if-feature "xpath"; ++ type yang:xpath1.0; ++ description ++ "Event stream evaluation criteria encoded in the syntax of ++ an XPath 1.0 expression. ++ ++ The XPath expression is evaluated on the representation of ++ individual, delineated event records as contained in ++ the event stream. ++ ++ The result of the XPath expression is converted to a ++ boolean value using the standard XPath 1.0 rules. If the ++ boolean value is 'true', the filter matches the event ++ record, and the event record is included in the ++ notification message sent to the receivers. ++ ++ The expression is evaluated in the following XPath ++ context: ++ ++ o The set of namespace declarations is the set of ++ prefix and namespace pairs for all YANG modules ++ implemented by the server, where the prefix is the ++ YANG module name and the namespace is as defined by ++ the 'namespace' statement in the YANG module. ++ ++ If the leaf is encoded in XML, all namespace ++ declarations in scope on the 'stream-xpath-filter' ++ leaf element are added to the set of namespace ++ declarations. If a prefix found in the XML is ++ already present in the set of namespace ++ declarations, the namespace in the XML is used. ++ ++ o The set of variable bindings is empty. ++ ++ o The function library is comprised of the core ++ function library and the XPath functions defined in ++ Section 10 in RFC 7950. ++ ++ o The context node is the root node."; ++ reference ++ "XML Path Language (XPath) Version 1.0 ++ (https://www.w3.org/TR/1999/REC-xpath-19991116) ++ RFC 7950: The YANG 1.1 Data Modeling Language, ++ Section 10"; ++ } ++ } ++ } ++ ++ grouping update-qos { ++ description ++ "This grouping describes QoS information concerning a ++ subscription. This information is passed to lower layers ++ for transport prioritization and treatment."; ++ leaf dscp { ++ if-feature "dscp"; ++ type inet:dscp; ++ default "0"; ++ description ++ "The desired network transport priority level. This is the ++ priority set on notification messages encapsulating the ++ results of the subscription. This transport priority is ++ shared for all receivers of a given subscription."; ++ } ++ leaf weighting { ++ if-feature "qos"; ++ type uint8 { ++ range "0 .. 255"; ++ } ++ description ++ "Relative weighting for a subscription. Larger weights get ++ more resources. Allows an underlying transport layer to ++ perform informed load-balance allocations between various ++ subscriptions."; ++ reference ++ "RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2), ++ Section 5.3.2"; ++ } ++ leaf dependency { ++ if-feature "qos"; ++ type subscription-id; ++ description ++ "Provides the 'subscription-id' of a parent subscription. ++ The parent subscription has absolute precedence should ++ that parent have push updates ready to egress the publisher. ++ In other words, there should be no streaming of objects from ++ the current subscription if the parent has something ready ++ to push. ++ ++ If a dependency is asserted via configuration or via an RPC ++ but the referenced 'subscription-id' does not exist, the ++ dependency is silently discarded. If a referenced ++ subscription is deleted, this dependency is removed."; ++ reference ++ "RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2), ++ Section 5.3.1"; ++ } ++ } ++ ++ grouping subscription-policy-modifiable { ++ description ++ "This grouping describes all objects that may be changed ++ in a subscription."; ++ choice target { ++ mandatory true; ++ description ++ "Identifies the source of information against which a ++ subscription is being applied as well as specifics on the ++ subset of information desired from that source."; ++ case stream { ++ choice stream-filter { ++ description ++ "An event stream filter can be applied to a subscription. ++ That filter will either come referenced from a global ++ list or be provided in the subscription itself."; ++ case by-reference { ++ description ++ "Apply a filter that has been configured separately."; ++ leaf stream-filter-name { ++ type stream-filter-ref; ++ mandatory true; ++ description ++ "References an existing event stream filter that is ++ to be applied to an event stream for the ++ subscription."; ++ } ++ } ++ case within-subscription { ++ description ++ "A local definition allows a filter to have the same ++ lifecycle as the subscription."; ++ uses stream-filter-elements; ++ } ++ } ++ } ++ } ++ leaf stop-time { ++ type yang:date-and-time; ++ description ++ "Identifies a time after which notification messages for a ++ subscription should not be sent. If 'stop-time' is not ++ present, the notification messages will continue until the ++ subscription is terminated. If 'replay-start-time' exists, ++ 'stop-time' must be for a subsequent time. If ++ 'replay-start-time' doesn't exist, 'stop-time', when ++ established, must be for a future time."; ++ } ++ } ++ ++ grouping subscription-policy-dynamic { ++ description ++ "This grouping describes the only information concerning a ++ subscription that can be passed over the RPCs defined in this ++ data model."; ++ uses subscription-policy-modifiable { ++ augment "target/stream" { ++ description ++ "Adds additional objects that can be modified by an RPC."; ++ leaf stream { ++ type stream-ref { ++ require-instance false; ++ } ++ mandatory true; ++ description ++ "Indicates the event stream to be considered for ++ this subscription."; ++ } ++ leaf replay-start-time { ++ if-feature "replay"; ++ type yang:date-and-time; ++ config false; ++ description ++ "Used to trigger the 'replay' feature for a dynamic ++ subscription, where event records that are selected ++ need to be at or after the specified starting time. If ++ 'replay-start-time' is not present, this is not a replay ++ subscription and event record push should start ++ immediately. It is never valid to specify start times ++ that are later than or equal to the current time."; ++ } ++ } ++ } ++ uses update-qos; ++ } ++ ++ grouping subscription-policy { ++ description ++ "This grouping describes the full set of policy information ++ concerning both dynamic and configured subscriptions, with the ++ exclusion of both receivers and networking information ++ specific to the publisher, such as what interface should be ++ used to transmit notification messages."; ++ uses subscription-policy-dynamic; ++ leaf transport { ++ if-feature "configured"; ++ type transport; ++ description ++ "For a configured subscription, this leaf specifies the ++ transport used to deliver messages destined for all ++ receivers of that subscription."; ++ } ++ leaf encoding { ++ when 'not(../transport) or derived-from(../transport, ++ "sn:configurable-encoding")'; ++ type encoding; ++ description ++ "The type of encoding for notification messages. For a ++ dynamic subscription, if not included as part of an ++ 'establish-subscription' RPC, the encoding will be populated ++ with the encoding used by that RPC. For a configured ++ subscription, if not explicitly configured, the encoding ++ will be the default encoding for an underlying transport."; ++ } ++ leaf purpose { ++ if-feature "configured"; ++ type string; ++ description ++ "Open text allowing a configuring entity to embed the ++ originator or other specifics of this subscription."; ++ } ++ } ++ ++ /* ++ * RPCs ++ */ ++ ++ rpc establish-subscription { ++ description ++ "This RPC allows a subscriber to create (and possibly ++ negotiate) a subscription on its own behalf. If successful, ++ the subscription remains in effect for the duration of the ++ subscriber's association with the publisher or until the ++ subscription is terminated. If an error occurs or the ++ publisher cannot meet the terms of a subscription, an RPC ++ error is returned, and the subscription is not created. ++ In that case, the RPC reply's 'error-info' MAY include ++ suggested parameter settings that would have a higher ++ likelihood of succeeding in a subsequent ++ 'establish-subscription' request."; ++ input { ++ uses subscription-policy-dynamic; ++ leaf encoding { ++ type encoding; ++ description ++ "The type of encoding for the subscribed data. If not ++ included as part of the RPC, the encoding MUST be set by ++ the publisher to be the encoding used by this RPC."; ++ } ++ } ++ output { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier used for this subscription."; ++ } ++ leaf replay-start-time-revision { ++ if-feature "replay"; ++ type yang:date-and-time; ++ description ++ "If a replay has been requested, this object represents ++ the earliest time covered by the event buffer for the ++ requested event stream. The value of this object is the ++ 'replay-log-aged-time' if it exists. Otherwise, it is ++ the 'replay-log-creation-time'. All buffered event ++ records after this time will be replayed to a receiver. ++ This object will only be sent if the starting time has ++ been revised to be later than the time requested by the ++ subscriber."; ++ } ++ } ++ } ++ ++ rc:yang-data establish-subscription-stream-error-info { ++ container establish-subscription-stream-error-info { ++ description ++ "If any 'establish-subscription' RPC parameters are ++ unsupportable against the event stream, a subscription ++ is not created and the RPC error response MUST indicate the ++ reason why the subscription failed to be created. This ++ yang-data MAY be inserted as structured data in a ++ subscription's RPC error response to indicate the reason for ++ the failure. This yang-data MUST be inserted if hints are ++ to be provided back to the subscriber."; ++ leaf reason { ++ type identityref { ++ base establish-subscription-error; ++ } ++ description ++ "Indicates the reason why the subscription has failed to ++ be created to a targeted event stream."; ++ } ++ leaf filter-failure-hint { ++ type string; ++ description ++ "Information describing where and/or why a provided ++ filter was unsupportable for a subscription. The ++ syntax and semantics of this hint are ++ implementation specific."; ++ } ++ } ++ } ++ ++ rpc modify-subscription { ++ description ++ "This RPC allows a subscriber to modify a dynamic ++ subscription's parameters. If successful, the changed ++ subscription parameters remain in effect for the duration of ++ the subscription, until the subscription is again modified, or ++ until the subscription is terminated. In the case of an error ++ or an inability to meet the modified parameters, the ++ subscription is not modified and the original subscription ++ parameters remain in effect. In that case, the RPC error MAY ++ include 'error-info' suggested parameter hints that would have ++ a high likelihood of succeeding in a subsequent ++ 'modify-subscription' request. A successful ++ 'modify-subscription' will return a suspended subscription to ++ the 'active' state."; ++ input { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier to use for this subscription."; ++ } ++ uses subscription-policy-modifiable; ++ } ++ } ++ ++ rc:yang-data modify-subscription-stream-error-info { ++ container modify-subscription-stream-error-info { ++ description ++ "This yang-data MAY be provided as part of a subscription's ++ RPC error response when there is a failure of a ++ 'modify-subscription' RPC that has been made against an ++ event stream. This yang-data MUST be used if hints are to ++ be provided back to the subscriber."; ++ leaf reason { ++ type identityref { ++ base modify-subscription-error; ++ } ++ description ++ "Information in a 'modify-subscription' RPC error response ++ that indicates the reason why the subscription to an event ++ stream has failed to be modified."; ++ } ++ leaf filter-failure-hint { ++ type string; ++ description ++ "Information describing where and/or why a provided ++ filter was unsupportable for a subscription. The syntax ++ and semantics of this hint are ++ implementation specific."; ++ } ++ } ++ } ++ ++ rpc delete-subscription { ++ description ++ "This RPC allows a subscriber to delete a subscription that ++ was previously created by that same subscriber using the ++ 'establish-subscription' RPC. ++ ++ If an error occurs, the server replies with an 'rpc-error' ++ where the 'error-info' field MAY contain a ++ 'delete-subscription-error-info' structure."; ++ input { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier of the subscription that is to be deleted. ++ Only subscriptions that were created using ++ 'establish-subscription' from the same origin as this RPC ++ can be deleted via this RPC."; ++ } ++ } ++ } ++ ++ rpc kill-subscription { ++ nacm:default-deny-all; ++ description ++ "This RPC allows an operator to delete a dynamic subscription ++ without restrictions on the originating subscriber or ++ underlying transport session. ++ ++ If an error occurs, the server replies with an 'rpc-error' ++ where the 'error-info' field MAY contain a ++ 'delete-subscription-error-info' structure."; ++ input { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier of the subscription that is to be deleted. ++ Only subscriptions that were created using ++ 'establish-subscription' can be deleted via this RPC."; ++ } ++ } ++ } ++ ++ rc:yang-data delete-subscription-error-info { ++ container delete-subscription-error-info { ++ description ++ "If a 'delete-subscription' RPC or a 'kill-subscription' RPC ++ fails, the subscription is not deleted and the RPC error ++ response MUST indicate the reason for this failure. This ++ yang-data MAY be inserted as structured data in a ++ subscription's RPC error response to indicate the reason ++ for the failure."; ++ leaf reason { ++ type identityref { ++ base delete-subscription-error; ++ } ++ mandatory true; ++ description ++ "Indicates the reason why the subscription has failed to be ++ deleted."; ++ } ++ } ++ } ++ ++ /* ++ * NOTIFICATIONS ++ */ ++ ++ notification replay-completed { ++ sn:subscription-state-notification; ++ if-feature "replay"; ++ description ++ "This notification is sent to indicate that all of the replay ++ notifications have been sent."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ } ++ ++ notification subscription-completed { ++ sn:subscription-state-notification; ++ if-feature "configured"; ++ description ++ "This notification is sent to indicate that a subscription has ++ finished passing event records, as the 'stop-time' has been ++ reached."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the gracefully completed subscription."; ++ } ++ } ++ ++ notification subscription-modified { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a subscription has been ++ modified. Notification messages sent from this point on will ++ conform to the modified terms of the subscription. For ++ completeness, this subscription state change notification ++ includes both modified and unmodified aspects of a ++ subscription."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ uses subscription-policy { ++ refine "target/stream/stream-filter/within-subscription" { ++ description ++ "Filter applied to the subscription. If the ++ 'stream-filter-name' is populated, the filter in the ++ subscription came from the 'filters' container. ++ Otherwise, it is populated in-line as part of the ++ subscription."; ++ } ++ } ++ } ++ ++ notification subscription-resumed { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a subscription that had ++ previously been suspended has resumed. Notifications will ++ once again be sent. In addition, a 'subscription-resumed' ++ indicates that no modification of parameters has occurred ++ since the last time event records have been sent."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ } ++ ++ notification subscription-started { ++ sn:subscription-state-notification; ++ if-feature "configured"; ++ description ++ "This notification indicates that a subscription has started ++ and notifications will now be sent."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ uses subscription-policy { ++ refine "target/stream/replay-start-time" { ++ description ++ "Indicates the time that a replay is using for the ++ streaming of buffered event records. This will be ++ populated with the most recent of the following: ++ the event time of the previous event record sent to a ++ receiver, the 'replay-log-creation-time', the ++ 'replay-log-aged-time', or the most recent publisher ++ boot time."; ++ } ++ refine "target/stream/stream-filter/within-subscription" { ++ description ++ "Filter applied to the subscription. If the ++ 'stream-filter-name' is populated, the filter in the ++ subscription came from the 'filters' container. ++ Otherwise, it is populated in-line as part of the ++ subscription."; ++ } ++ augment "target/stream" { ++ description ++ "This augmentation adds additional parameters specific to a ++ 'subscription-started' notification."; ++ leaf replay-previous-event-time { ++ when '../replay-start-time'; ++ if-feature "replay"; ++ type yang:date-and-time; ++ description ++ "If there is at least one event in the replay buffer ++ prior to 'replay-start-time', this gives the time of ++ the event generated immediately prior to the ++ 'replay-start-time'. ++ ++ If a receiver previously received event records for ++ this configured subscription, it can compare this time ++ to the last event record previously received. If the ++ two are not the same (perhaps due to a reboot), then a ++ dynamic replay can be initiated to acquire any missing ++ event records."; ++ } ++ } ++ } ++ } ++ ++ notification subscription-suspended { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a suspension of the ++ subscription by the publisher has occurred. No further ++ notifications will be sent until the subscription resumes. ++ This notification shall only be sent to receivers of a ++ subscription; it does not constitute a general-purpose ++ notification."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ leaf reason { ++ type identityref { ++ base subscription-suspended-reason; ++ } ++ mandatory true; ++ description ++ "Identifies the condition that resulted in the suspension."; ++ } ++ } ++ ++ notification subscription-terminated { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a subscription has been ++ terminated."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ leaf reason { ++ type identityref { ++ base subscription-terminated-reason; ++ } ++ mandatory true; ++ description ++ "Identifies the condition that resulted in the termination."; ++ } ++ } ++ ++ /* ++ * DATA NODES ++ */ ++ ++ container streams { ++ config false; ++ description ++ "Contains information on the built-in event streams provided by ++ the publisher."; ++ list stream { ++ key "name"; ++ description ++ "Identifies the built-in event streams that are supported by ++ the publisher."; ++ leaf name { ++ type string; ++ description ++ "A handle for a system-provided event stream made up of a ++ sequential set of event records, each of which is ++ characterized by its own domain and semantics."; ++ } ++ leaf description { ++ type string; ++ description ++ "A description of the event stream, including such ++ information as the type of event records that are ++ available in this event stream."; ++ } ++ leaf replay-support { ++ if-feature "replay"; ++ type empty; ++ description ++ "Indicates that event record replay is available on this ++ event stream."; ++ } ++ leaf replay-log-creation-time { ++ when '../replay-support'; ++ if-feature "replay"; ++ type yang:date-and-time; ++ mandatory true; ++ description ++ "The timestamp of the creation of the log used to support ++ the replay function on this event stream. This time ++ might be earlier than the earliest available information ++ contained in the log. This object is updated if the log ++ resets for some reason."; ++ } ++ leaf replay-log-aged-time { ++ when '../replay-support'; ++ if-feature "replay"; ++ type yang:date-and-time; ++ description ++ "The timestamp associated with the last event record that ++ has been aged out of the log. This timestamp identifies ++ how far back in history this replay log extends, if it ++ doesn't extend back to the 'replay-log-creation-time'. ++ This object MUST be present if replay is supported and any ++ event records have been aged out of the log."; ++ } ++ } ++ } ++ container filters { ++ description ++ "Contains a list of configurable filters that can be applied to ++ subscriptions. This facilitates the reuse of complex filters ++ once defined."; ++ list stream-filter { ++ key "name"; ++ description ++ "A list of preconfigured filters that can be applied to ++ subscriptions."; ++ leaf name { ++ type string; ++ description ++ "A name to differentiate between filters."; ++ } ++ uses stream-filter-elements; ++ } ++ } ++ container subscriptions { ++ description ++ "Contains the list of currently active subscriptions, i.e., ++ subscriptions that are currently in effect, used for ++ subscription management and monitoring purposes. This ++ includes subscriptions that have been set up via ++ RPC primitives as well as subscriptions that have been ++ established via configuration."; ++ list subscription { ++ key "id"; ++ description ++ "The identity and specific parameters of a subscription. ++ Subscriptions in this list can be created using a control ++ channel or RPC or can be established through configuration. ++ ++ If the 'kill-subscription' RPC or configuration operations ++ are used to delete a subscription, a ++ 'subscription-terminated' message is sent to any active or ++ suspended receivers."; ++ leaf id { ++ type subscription-id; ++ description ++ "Identifier of a subscription; unique in a given ++ publisher."; ++ } ++ uses subscription-policy { ++ refine "target/stream/stream" { ++ description ++ "Indicates the event stream to be considered for this ++ subscription. If an event stream has been removed ++ and can no longer be referenced by an active ++ subscription, send a 'subscription-terminated' ++ notification with 'stream-unavailable' as the reason. ++ If a configured subscription refers to a nonexistent ++ event stream, move that subscription to the ++ 'invalid' state."; ++ } ++ refine "transport" { ++ description ++ "For a configured subscription, this leaf specifies the ++ transport used to deliver messages destined for all ++ receivers of that subscription. This object is ++ mandatory for subscriptions in the configuration ++ datastore. This object (1) is not mandatory for dynamic ++ subscriptions in the operational state datastore and ++ (2) should not be present for other types of dynamic ++ subscriptions."; ++ } ++ augment "target/stream" { ++ description ++ "Enables objects to be added to a configured stream ++ subscription."; ++ leaf configured-replay { ++ if-feature "configured"; ++ if-feature "replay"; ++ type empty; ++ description ++ "The presence of this leaf indicates that replay for ++ the configured subscription should start at the ++ earliest time in the event log or at the publisher ++ boot time, whichever is later."; ++ } ++ } ++ } ++ choice notification-message-origin { ++ if-feature "configured"; ++ description ++ "Identifies the egress interface on the publisher ++ from which notification messages are to be sent."; ++ case interface-originated { ++ description ++ "When notification messages are to egress a specific, ++ designated interface on the publisher."; ++ leaf source-interface { ++ if-feature "interface-designation"; ++ type if:interface-ref; ++ description ++ "References the interface for notification messages."; ++ } ++ } ++ case address-originated { ++ description ++ "When notification messages are to depart from a ++ publisher using a specific originating address and/or ++ routing context information."; ++ leaf source-vrf { ++ if-feature "supports-vrf"; ++ type leafref { ++ path "/ni:network-instances/ni:network-instance/ni:name"; ++ } ++ description ++ "VRF from which notification messages should egress a ++ publisher."; ++ } ++ leaf source-address { ++ type inet:ip-address-no-zone; ++ description ++ "The source address for the notification messages. ++ If a source VRF exists but this object doesn't, a ++ publisher's default address for that VRF must ++ be used."; ++ } ++ } ++ } ++ leaf configured-subscription-state { ++ if-feature "configured"; ++ type enumeration { ++ enum valid { ++ value 1; ++ description ++ "The subscription is supportable with its current ++ parameters."; ++ } ++ enum invalid { ++ value 2; ++ description ++ "The subscription as a whole is unsupportable with its ++ current parameters."; ++ } ++ enum concluded { ++ value 3; ++ description ++ "A subscription is inactive, as it has hit a ++ stop time. It no longer has receivers in the ++ 'active' or 'suspended' state, but the subscription ++ has not yet been removed from configuration."; ++ } ++ } ++ config false; ++ description ++ "The presence of this leaf indicates that the subscription ++ originated from configuration, not through a control ++ channel or RPC. The value indicates the state of the ++ subscription as established by the publisher."; ++ } ++ container receivers { ++ description ++ "Set of receivers in a subscription."; ++ list receiver { ++ key "name"; ++ min-elements 1; ++ description ++ "A host intended as a recipient for the notification ++ messages of a subscription. For configured ++ subscriptions, transport-specific network parameters ++ (or a leafref to those parameters) may be augmented to a ++ specific receiver in this list."; ++ leaf name { ++ type string; ++ description ++ "Identifies a unique receiver for a subscription."; ++ } ++ leaf sent-event-records { ++ type yang:zero-based-counter64; ++ config false; ++ description ++ "The number of event records sent to the receiver. The ++ count is initialized when a dynamic subscription is ++ established or when a configured receiver ++ transitions to the 'valid' state."; ++ } ++ leaf excluded-event-records { ++ type yang:zero-based-counter64; ++ config false; ++ description ++ "The number of event records explicitly removed via ++ either an event stream filter or an access control ++ filter so that they are not passed to a receiver. ++ This count is set to zero each time ++ 'sent-event-records' is initialized."; ++ } ++ leaf state { ++ type enumeration { ++ enum active { ++ value 1; ++ description ++ "The receiver is currently being sent any ++ applicable notification messages for the ++ subscription."; ++ } ++ enum suspended { ++ value 2; ++ description ++ "The receiver state is 'suspended', so the ++ publisher is currently unable to provide ++ notification messages for the subscription."; ++ } ++ enum connecting { ++ value 3; ++ if-feature "configured"; ++ description ++ "A subscription has been configured, but a ++ 'subscription-started' subscription state change ++ notification needs to be successfully received ++ before notification messages are sent. ++ ++ If the 'reset' action is invoked for a receiver of ++ an active configured subscription, the state ++ must be moved to 'connecting'."; ++ } ++ enum disconnected { ++ value 4; ++ if-feature "configured"; ++ description ++ "A subscription has failed to send a ++ 'subscription-started' state change to the ++ receiver. Additional connection attempts are not ++ currently being made."; ++ } ++ } ++ config false; ++ mandatory true; ++ description ++ "Specifies the state of a subscription from the ++ perspective of a particular receiver. With this ++ information, it is possible to determine whether a ++ publisher is currently generating notification ++ messages intended for that receiver."; ++ } ++ action reset { ++ if-feature "configured"; ++ description ++ "Allows the reset of this configured subscription's ++ receiver to the 'connecting' state. This enables the ++ connection process to be reinitiated."; ++ output { ++ leaf time { ++ type yang:date-and-time; ++ mandatory true; ++ description ++ "Time at which a publisher returned the receiver to ++ the 'connecting' state."; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++} +-- +2.43.0 + diff --git a/package/rousette/0037-don-t-create-throwaway-sysrepo-sessions-during-start.patch b/package/rousette/0037-don-t-create-throwaway-sysrepo-sessions-during-start.patch new file mode 100644 index 00000000..81b2e2eb --- /dev/null +++ b/package/rousette/0037-don-t-create-throwaway-sysrepo-sessions-during-start.patch @@ -0,0 +1,31 @@ +From 10c6ae778724e5c15a12b58bc95c814cb17b772e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Tue, 8 Apr 2025 16:59:48 +0200 +Subject: [PATCH 37/44] don't create throwaway sysrepo sessions during startup +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: Ic3fc71c08c589a8e87bbdc1aa1b1089c88c7cb1e +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index 9fd3fa9..a840e6e 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -847,7 +847,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + {"ietf-subscribed-notifications", "2019-09-09", {}}, + {"ietf-restconf-subscribed-notifications", "2019-11-17", {}}, + }) { +- if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) { ++ if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) { + for (const auto& feature : features) { + if (!mod->featureEnabled(feature)) { + throw std::runtime_error("Module "s + module + "@" + version + " does not implement feature " + feature); +-- +2.43.0 + diff --git a/package/rousette/0038-requests-create-sysrepo-session-later.patch b/package/rousette/0038-requests-create-sysrepo-session-later.patch new file mode 100644 index 00000000..53653dc0 --- /dev/null +++ b/package/rousette/0038-requests-create-sysrepo-session-later.patch @@ -0,0 +1,59 @@ +From 4e287e1e2af95c1ec077689e4f353f657469e5e5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Tue, 8 Apr 2025 17:00:44 +0200 +Subject: [PATCH 38/44] requests: create sysrepo session later +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +I was looking at all places where a sysrepo session is used, and tried +to defer their creations until the last moment possible, just to +(potentially) save some resources when it isn't needed. Some of these +changes are cosmetic, but I was touching that code anyway. + +This cannot be done for the generic `restconfRoot` handler because of +its associated error handling which requires a libyang context. Well, it +*could* be done, but I don't feel like doing some m_monitoringSession +magic for little to no purpose. + +Change-Id: Ia84c90abc1464e4a0f9682779d7471aefad9e2de +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index a840e6e..f3c515b 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -902,7 +902,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + }); + + server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable { +- auto sess = conn.sessionStart(); + std::optional xpathFilter; + std::optional startTime; + std::optional stopTime; +@@ -914,6 +913,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + } + + try { ++ auto sess = conn.sessionStart(); + authorizeRequest(nacm, sess, req); + + auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query); +@@ -962,9 +962,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + return; + } + +- auto sess = conn.sessionStart(sysrepo::Datastore::Operational); +- + try { ++ auto sess = conn.sessionStart(sysrepo::Datastore::Operational); + authorizeRequest(nacm, sess, req); + + if (auto mod = asYangModule(sess.getContext(), req.uri().path); mod && hasAccessToYangSchema(sess, *mod)) { +-- +2.43.0 + diff --git a/package/rousette/0039-docs-doctest-has-moved.patch b/package/rousette/0039-docs-doctest-has-moved.patch new file mode 100644 index 00000000..952d7251 --- /dev/null +++ b/package/rousette/0039-docs-doctest-has-moved.patch @@ -0,0 +1,31 @@ +From 422eae2ae879872bd618e3ba19418d20c197650d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 9 Apr 2025 15:52:34 +0200 +Subject: [PATCH 39/44] docs: doctest has moved +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I45d605f080434a0726d729c7b186ab1bbeb8d8e1 +Signed-off-by: Mattias Walström +--- + README.md | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/README.md b/README.md +index 3fbfd21..646976d 100644 +--- a/README.md ++++ b/README.md +@@ -94,7 +94,7 @@ In practical terms, this means that the NACM access rules for the following XPat + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ + - optionally systemd - the shared library for logging to `sd-journal` +-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework ++- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework + - optionally for built-in tests, [trompeloeil](https://github.com/rollbear/trompeloeil) for mock objects in C++ + - optionally for built-in tests, [`pam_matrix` and `pam_wrapper`](https://cwrap.org/pam_wrapper.html) for PAM mocking + +-- +2.43.0 + diff --git a/package/rousette/0040-tests-capture-wrongly-formatted-SSE-messages.patch b/package/rousette/0040-tests-capture-wrongly-formatted-SSE-messages.patch new file mode 100644 index 00000000..7be717bc --- /dev/null +++ b/package/rousette/0040-tests-capture-wrongly-formatted-SSE-messages.patch @@ -0,0 +1,35 @@ +From b60c30e4eb482ce7b72ba95c8e6edc94f232ca37 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Tue, 8 Apr 2025 18:12:18 +0200 +Subject: [PATCH 40/44] tests: capture wrongly-formatted SSE messages +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Sometimes during development I do things wrong(TM) and the Server Sent +Events data stream is not prefixed. +The log failure is unhelpful, so this patch should make the failures +more clear in such cases. + +Change-Id: I0d4643a6d7fe56ea5ce694c734a76b6dd471ff4c +Signed-off-by: Mattias Walström +--- + tests/restconf_utils.cpp | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp +index c3ff1de..72c2c5a 100644 +--- a/tests/restconf_utils.cpp ++++ b/tests/restconf_utils.cpp +@@ -224,6 +224,7 @@ std::vector SSEClient::parseEvents(const std::string& msg) + res.emplace_back(std::move(event)); + event.clear(); + } else { ++ CAPTURE(msg); + FAIL("Unprefixed response"); + } + } +-- +2.43.0 + diff --git a/package/rousette/0041-tests-perform-factory-reset-between-individual-secti.patch b/package/rousette/0041-tests-perform-factory-reset-between-individual-secti.patch new file mode 100644 index 00000000..de671a18 --- /dev/null +++ b/package/rousette/0041-tests-perform-factory-reset-between-individual-secti.patch @@ -0,0 +1,32 @@ +From 2e866f4076df5cf68e7f8015e088baf45b45ddb3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 14 Apr 2025 13:15:29 +0200 +Subject: [PATCH 41/44] tests: perform factory-reset between individual + sections of NACM test +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I3b60439a8235e2a0efed58fcdb2ad8eb95f21714 +Signed-off-by: Mattias Walström +--- + tests/restconf-nacm.cpp | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp +index 51328f3..546d694 100644 +--- a/tests/restconf-nacm.cpp ++++ b/tests/restconf-nacm.cpp +@@ -20,6 +20,8 @@ TEST_CASE("NACM") + auto nacmGuard = manageNacm(srSess); + auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT}; + ++ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset")); ++ + // something we can read + srSess.switchDatastore(sysrepo::Datastore::Operational); + srSess.setItem("/ietf-system:system/contact", "contact"); +-- +2.43.0 + diff --git a/package/rousette/0042-restconf-simplify-condition-in-NACM-anonymous-access.patch b/package/rousette/0042-restconf-simplify-condition-in-NACM-anonymous-access.patch new file mode 100644 index 00000000..abc5bce8 --- /dev/null +++ b/package/rousette/0042-restconf-simplify-condition-in-NACM-anonymous-access.patch @@ -0,0 +1,70 @@ +From 0ef0307d6664affc0547c2e65c8c33900bd7cfc9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 14 Apr 2025 15:12:00 +0200 +Subject: [PATCH 42/44] restconf: simplify condition in NACM anonymous access +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +The condition required that all access-operations values are "read". But +access-operations is a union, so it is enough to check that we have only +one value in the union and that it is "read". + +Also test what happens if anonymous rule access-operation is a union of +read and something else. + +Change-Id: I291243d9e2167601b24d49684d53c361407f66be +Signed-off-by: Mattias Walström +--- + src/auth/Nacm.cpp | 4 +--- + tests/restconf-nacm.cpp | 18 ++++++++++++++++++ + 2 files changed, 19 insertions(+), 3 deletions(-) + +diff --git a/src/auth/Nacm.cpp b/src/auth/Nacm.cpp +index 61a7dcc..d0dc4a2 100644 +--- a/src/auth/Nacm.cpp ++++ b/src/auth/Nacm.cpp +@@ -12,9 +12,7 @@ namespace { + bool isRuleReadOnly(const libyang::DataNode& rule) + { + auto accessOperations = rule.findXPath("access-operations"); +- return !accessOperations.empty() && std::all_of(accessOperations.begin(), accessOperations.end(), [](const auto& e) { +- return e.asTerm().valueStr() == "read"; +- }); ++ return accessOperations.size() == 1 && accessOperations.begin()->asTerm().valueStr() == "read"; + } + + bool isRuleWildcardDeny(const libyang::DataNode& rule) +diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp +index 546d694..30f4210 100644 +--- a/tests/restconf-nacm.cpp ++++ b/tests/restconf-nacm.cpp +@@ -95,6 +95,24 @@ TEST_CASE("NACM") + srSess.applyChanges(); + } + ++ DOCTEST_SUBCASE("Anonymous has one rule with both read and update access operations") ++ { ++ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list"); ++ srSess.applyChanges(); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/group[.='yangnobody']", ""); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/module-name", "ietf-system"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/action", "permit"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/access-operations", "read update"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/path", "/ietf-system:system"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/module-name", "*"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/action", "deny"); ++ ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", ""); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/module-name", "ietf-system"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/action", "permit"); ++ srSess.applyChanges(); ++ } ++ + DOCTEST_SUBCASE("Anonymous rulelist OK, but not at first place") + { + srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list"); +-- +2.43.0 + diff --git a/package/rousette/0043-restconf-allow-specifying-exec-permissions-for-anony.patch b/package/rousette/0043-restconf-allow-specifying-exec-permissions-for-anony.patch new file mode 100644 index 00000000..beeca967 --- /dev/null +++ b/package/rousette/0043-restconf-allow-specifying-exec-permissions-for-anony.patch @@ -0,0 +1,140 @@ +From f97dcabf8bf74c463d3291d31e9b36fabec0654f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 9 Apr 2025 21:17:19 +0200 +Subject: [PATCH 43/44] restconf: allow specifying exec permissions for + anonymous user access +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +We want to be able to allow anonymous users to call the RPC +ietf-subscribed-notifications:establish-subscription. Our current NACM +anonymous setup did not allow specifying exec permissions. +After this patch admins are allowed to setup a rule in the anonymous +user rule-list that allows anonymous users to call the +establish-subscriptions RPC. We allow only this one RPC, specifying any +other will result in disabling the anonymous user access. + +Change-Id: I5aa6fee2bbdabc0b7deac7fb9afecac6c411aca0 +Signed-off-by: Mattias Walström +--- + README.md | 2 +- + src/auth/Nacm.cpp | 23 ++++++++++++++++++----- + tests/restconf-nacm.cpp | 21 +++++++++++++++++++++ + tests/restconf_utils.cpp | 4 ++++ + 4 files changed, 44 insertions(+), 6 deletions(-) + +diff --git a/README.md b/README.md +index 646976d..0796dce 100644 +--- a/README.md ++++ b/README.md +@@ -66,7 +66,7 @@ When certain conditions are met, the anonymous access will be mapped to a NACM a + There must be some specific access rights set up in `ietf-netconf-acm` model (these are currently very opinionated for our use-case): + + 1. The first entry of `rule-list` list must be configured for `ANONYMOUS_USER_GROUP`. +-2. All the rules except the last one in this rule-list entry must enable only "read" access operation. ++2. All the rules except the last one in this rule-list entry must enable either only "read" access operation or only "exec" operation on `ietf-subscribed-notifications:establish-subscription` RPC. + 3. The last rule in the first rule-set must be a wildcard rule that disables all operations over all modules. + + The anonymous user access is disabled whenever these rules are not met. +diff --git a/src/auth/Nacm.cpp b/src/auth/Nacm.cpp +index d0dc4a2..284fa85 100644 +--- a/src/auth/Nacm.cpp ++++ b/src/auth/Nacm.cpp +@@ -9,10 +9,23 @@ + #include "NacmIdentities.h" + + namespace { +-bool isRuleReadOnly(const libyang::DataNode& rule) ++bool isWhitelistedRPC(const std::optional& moduleNameNode, const std::optional& rpcNameNode) ++{ ++ if (!moduleNameNode || !rpcNameNode) { ++ return false; ++ } ++ ++ return moduleNameNode->asTerm().valueStr() == "ietf-subscribed-notifications" && rpcNameNode->asTerm().valueStr() == "establish-subscription"; ++} ++ ++bool isRuleForAnonymousAccess(const libyang::DataNode& rule) + { + auto accessOperations = rule.findXPath("access-operations"); +- return accessOperations.size() == 1 && accessOperations.begin()->asTerm().valueStr() == "read"; ++ auto rpcName = rule.findPath("rpc-name"); ++ return accessOperations.size() == 1 && // Combining access operations is not allowed in anonymous user rules ++ (accessOperations.begin()->asTerm().valueStr() == "read" || // Either read... ++ (accessOperations.begin()->asTerm().valueStr() == "exec" && isWhitelistedRPC(rule.findPath("module-name"), rule.findPath("rpc-name"))) // ...or exec on whitelisted RPC. ++ ); + } + + bool isRuleWildcardDeny(const libyang::DataNode& rule) +@@ -25,7 +38,7 @@ bool isRuleWildcardDeny(const libyang::DataNode& rule) + * + * The first rule-list element contains rules for anonymous user access, i.e.: + * - The group is set to @p anonGroup (this one should contain the anonymous user) +- * - In rules (except the last one) the access-operation allowed is "read" ++ * - In rules (except the last one) the access-operation allowed is either "read" or "exec" on a whitelisted RPC. + * - The last rule has module-name="*" and action "deny". + * + * @return boolean indicating whether the rules are configured properly for anonymous user access +@@ -60,8 +73,8 @@ bool validAnonymousNacmRules(sysrepo::Session session, const std::string& anonGr + return false; + } + +- if (!std::all_of(rules.begin(), rules.end() - 1, isRuleReadOnly)) { +- spdlog::debug("NACM config validation: First n-1 rules in the anonymous rule-list must be configured for read-access only"); ++ if (!std::all_of(rules.begin(), rules.end() - 1, isRuleForAnonymousAccess)) { ++ spdlog::debug("NACM config validation: First n-1 rules in the anonymous rule-list must be configured either for read-access only or exec on listed RPC paths"); + return false; + } + +diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp +index 30f4210..7799fe8 100644 +--- a/tests/restconf-nacm.cpp ++++ b/tests/restconf-nacm.cpp +@@ -113,6 +113,27 @@ TEST_CASE("NACM") + srSess.applyChanges(); + } + ++ DOCTEST_SUBCASE("This RPC is not allowed") ++ { ++ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list"); ++ srSess.applyChanges(); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/group[.='yangnobody']", ""); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/module-name", "ietf-system"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/action", "permit"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/access-operations", "read"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/path", "/ietf-system:system"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/module-name", "ietf-subscribed-notifications"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/action", "permit"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/access-operations", "read update"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/rpc-name", "/ietf-subscribed-notifications:modify-subscription"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='3']/module-name", "*"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='3']/action", "deny"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", ""); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/module-name", "ietf-system"); ++ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/action", "permit"); ++ srSess.applyChanges(); ++ } ++ + DOCTEST_SUBCASE("Anonymous rulelist OK, but not at first place") + { + srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list"); +diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp +index 72c2c5a..a137463 100644 +--- a/tests/restconf_utils.cpp ++++ b/tests/restconf_utils.cpp +@@ -152,6 +152,10 @@ void setupRealNacm(sysrepo::Session session) + session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/action", "permit"); + session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/access-operations", "read"); + session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/path", "/example-delete:immutable"); ++ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/module-name", "ietf-subscribed-notifications"); ++ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/action", "permit"); ++ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/access-operations", "exec"); ++ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/rpc-name", "establish-subscription"); + session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/module-name", "*"); + session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/action", "deny"); + session.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", ""); +-- +2.43.0 + diff --git a/package/rousette/0044-restconf-add-access-log-to-stream-root.patch b/package/rousette/0044-restconf-add-access-log-to-stream-root.patch new file mode 100644 index 00000000..61cdf9dc --- /dev/null +++ b/package/rousette/0044-restconf-add-access-log-to-stream-root.patch @@ -0,0 +1,32 @@ +From 438a18f5884ace3063f00a2dfc53dd2b4034f53e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 16 Apr 2025 10:56:50 +0200 +Subject: [PATCH 44/44] restconf: add access log to stream root +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I5e1db3f40bc6a7f294e6a5fef9402e0855055258 +Signed-off-by: Mattias Walström +--- + src/restconf/Server.cpp | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp +index f3c515b..5b1b2f9 100644 +--- a/src/restconf/Server.cpp ++++ b/src/restconf/Server.cpp +@@ -902,6 +902,9 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: + }); + + server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable { ++ const auto& peer = http::peer_from_request(req); ++ spdlog::info("{}: {} {}", peer, req.method(), req.uri().raw_path); ++ + std::optional xpathFilter; + std::optional startTime; + std::optional stopTime; +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch b/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch index 0c62b486..7d4b905e 100644 --- a/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch +++ b/package/sysrepo-cpp/0001-error-handling-changes-in-upstream-sysrepo.patch @@ -1,11 +1,11 @@ From 23fa270a747c35d65aeeba691599ed6b21b501f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 29 Oct 2024 15:15:52 +0100 -Subject: [PATCH 1/6] error handling changes in upstream sysrepo +Subject: [PATCH 01/20] error handling changes in upstream sysrepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Upstream commit 396e331e4634a417420a71ad723567b42d75c443 removed these extra error entries. diff --git a/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch b/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch index c59a87b1..f5c273dc 100644 --- a/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch +++ b/package/sysrepo-cpp/0002-Fix-error-message-when-sr_session_start-fails.patch @@ -1,11 +1,11 @@ From a73ffe1a5bbab51a67cf56dd2864c71a29c6685b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 21 Nov 2024 11:18:25 +0100 -Subject: [PATCH 2/6] Fix error message when sr_session_start fails +Subject: [PATCH 02/20] Fix error message when sr_session_start fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Thanks to Jason Patterson for sending a bugreport; its investigation led to discovering this bug. diff --git a/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch b/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch index 3611021d..6f1e645c 100644 --- a/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch +++ b/package/sysrepo-cpp/0003-We-don-t-support-sysrepo-v3-yet.patch @@ -1,11 +1,11 @@ From 552a366990fe1fe2cdd8d155fa5cecb3ff3fbc13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 4 Dec 2024 15:51:41 +0100 -Subject: [PATCH 3/6] We don't support sysrepo v3 yet +Subject: [PATCH 03/20] We don't support sysrepo v3 yet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires That version introduced some backward-incompatible changes to the way how operational data are treated, especially in presence of "discards" diff --git a/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch b/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch index bb1323b7..65edddaa 100644 --- a/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch +++ b/package/sysrepo-cpp/0004-refactor-don-t-use-module-as-a-variable-name.patch @@ -1,11 +1,11 @@ From f2db30721d909d127645721f4149de0cd36d2b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Tue, 17 Dec 2024 11:41:10 +0100 -Subject: [PATCH 4/6] refactor: don't use `module` as a variable name +Subject: [PATCH 04/20] refactor: don't use `module` as a variable name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires ...to unify stuff with all other occurrences, and to not use that magic identifier. diff --git a/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch b/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch index e7b7505a..7094334f 100644 --- a/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch +++ b/package/sysrepo-cpp/0005-DRY-a-dummy-leaf-XPath.patch @@ -1,11 +1,11 @@ From a923f490350dc61b9f3e6000c06c1d7950a78a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 18 Dec 2024 16:12:14 +0100 -Subject: [PATCH 5/6] DRY: a dummy leaf XPath +Subject: [PATCH 05/20] DRY: a dummy leaf XPath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Change-Id: I47fca61d27380af07b2327f3ee5b984a8a8afb66 Signed-off-by: Mattias Walström diff --git a/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch b/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch index a26bc92d..5a207611 100644 --- a/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch +++ b/package/sysrepo-cpp/0006-build-a-single-place-to-define-package-version.patch @@ -1,11 +1,11 @@ From 80e9659b41ea1de2e20d62f94319fe5af26adcee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 18 Dec 2024 17:57:36 +0100 -Subject: [PATCH 6/6] build: a single place to define package version +Subject: [PATCH 06/20] build: a single place to define package version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Don't ask me how I found out that I need this :). diff --git a/package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch b/package/sysrepo-cpp/0007-API-ABI-break-Change-how-pushed-ops-data-work.patch similarity index 90% rename from package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch rename to package/sysrepo-cpp/0007-API-ABI-break-Change-how-pushed-ops-data-work.patch index 753c9a15..a0b25417 100644 --- a/package/sysrepo-cpp/9999-GERRIT-API-ABI-break-Change-how-pushed-ops-data-work.patch +++ b/package/sysrepo-cpp/0007-API-ABI-break-Change-how-pushed-ops-data-work.patch @@ -1,7 +1,11 @@ -From b34fb200812f9e6bc72e80bf46bd006ebc06a9da Mon Sep 17 00:00:00 2001 -From: Jan Kundrát +From 32bdd8bcad04f890fe724fb6cc2fc74f9b982576 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Wed, 18 Dec 2024 17:16:55 +0100 -Subject: [PATCH] API/ABI break: Change how pushed ops data work +Subject: [PATCH 07/20] API/ABI break: Change how pushed ops data work +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires Upstream has changed the way how pushed operational data are stored and managed. From now on, each session has a separate edit, these edits are @@ -33,10 +37,18 @@ avoiding the possible confusion at the C++ layer: by `Session::applyChanges()`. Change-Id: Iba05a88411fd4c47c03d8c2b48cb7aadfd5dcd2a +Signed-off-by: Mattias Walström --- + .zuul.yaml | 4 +- + CMakeLists.txt | 4 +- + README.md | 1 - + include/sysrepo-cpp/Session.hpp | 6 ++- + src/Session.cpp | 96 ++++++++++++++++++++++++--------- + tests/session.cpp | 59 ++++++++++++++++---- + 6 files changed, 128 insertions(+), 42 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml -index c3aea22..dc87c6f 100644 +index c3aea22..70c5e49 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -6,7 +6,7 @@ @@ -44,7 +56,7 @@ index c3aea22..dc87c6f 100644 override-checkout: devel - name: github/sysrepo/sysrepo - override-checkout: cesnet/2024-10-before-oper-changes -+ override-checkout: devel ++ override-checkout: cesnet/2025-01-30--00 - name: github/onqtam/doctest override-checkout: v2.4.8 - name: github/rollbear/trompeloeil @@ -53,15 +65,15 @@ index c3aea22..dc87c6f 100644 override-checkout: devel - name: github/sysrepo/sysrepo - override-checkout: cesnet/2024-10-before-oper-changes -+ override-checkout: devel ++ override-checkout: cesnet/2025-01-30--00 - name: github/onqtam/doctest override-checkout: v2.4.11 - name: github/rollbear/trompeloeil diff --git a/CMakeLists.txt b/CMakeLists.txt -index 68ade53..cc4dba9 100644 +index 68ade53..9e09d3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -20,7 +20,7 @@ +@@ -20,7 +20,7 @@ add_custom_target(sysrepo-cpp-version-cmake cmake/ProjectGitVersionRunner.cmake ) include(cmake/ProjectGitVersion.cmake) @@ -70,12 +82,12 @@ index 68ade53..cc4dba9 100644 prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION}) find_package(Doxygen) -@@ -29,7 +29,7 @@ +@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON) find_package(PkgConfig) pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET) -pkg_check_modules(SYSREPO REQUIRED sysrepo>=2.12.0 sysrepo<3 IMPORTED_TARGET) -+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.0 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.7 IMPORTED_TARGET) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) @@ -83,7 +95,7 @@ diff --git a/README.md b/README.md index e14b772..27dc748 100644 --- a/README.md +++ b/README.md -@@ -9,7 +9,6 @@ +@@ -9,7 +9,6 @@ It uses RAII for automatic memory management. ## Dependencies - [sysrepo](https://github.com/sysrepo/sysrepo) - the `devel` branch (even for the `master` branch of *sysrepo-cpp*) @@ -95,7 +107,7 @@ diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp index 477e604..238f480 100644 --- a/include/sysrepo-cpp/Session.hpp +++ b/include/sysrepo-cpp/Session.hpp -@@ -85,13 +85,15 @@ +@@ -85,13 +85,15 @@ public: void setItem(const std::string& path, const std::optional& value, const EditOptions opts = sysrepo::EditOptions::Default); void editBatch(libyang::DataNode edit, const DefaultOperation op); void deleteItem(const std::string& path, const EditOptions opts = sysrepo::EditOptions::Default); @@ -117,7 +129,7 @@ diff --git a/src/Session.cpp b/src/Session.cpp index 94a0fad..634e450 100644 --- a/src/Session.cpp +++ b/src/Session.cpp -@@ -23,6 +23,26 @@ +@@ -23,6 +23,26 @@ extern "C" { using namespace std::string_literals; namespace sysrepo { @@ -144,7 +156,7 @@ index 94a0fad..634e450 100644 /** * Wraps a pointer to sr_session_ctx_s and manages the lifetime of it. Also extends the lifetime of the connection * specified by the `conn` argument. -@@ -119,20 +139,61 @@ +@@ -119,20 +139,61 @@ void Session::deleteItem(const std::string& path, const EditOptions opts) } /** @@ -209,7 +221,7 @@ index 94a0fad..634e450 100644 /** * Moves item (a list or a leaf-list) specified by `path`. * @param path Node to move. -@@ -155,25 +216,6 @@ +@@ -155,25 +216,6 @@ void Session::moveItem(const std::string& path, const MovePosition move, const s throwIfError(res, "Session::moveItem: Can't move '"s + path + "'", m_sess.get()); } @@ -235,16 +247,16 @@ index 94a0fad..634e450 100644 /** * @brief Returns a tree which contains all nodes that match the provided XPath. * -@@ -263,13 +305,15 @@ +@@ -263,13 +305,15 @@ void Session::applyChanges(std::chrono::milliseconds timeout) } /** - * Discards changes made in this Session. + * Discards changes made earlier in this Session, optionally only below a given XPath ++ * ++ * The changes are applied only after calling Session::applyChanges. * - * Wraps `sr_discard_changes`. -+ * The changes are applied only after calling Session::applyChanges. -+ * + * Wraps `sr_discard_changes_xpath`. */ -void Session::discardChanges() @@ -259,7 +271,7 @@ diff --git a/tests/session.cpp b/tests/session.cpp index f762f10..2637f67 100644 --- a/tests/session.cpp +++ b/tests/session.cpp -@@ -211,7 +211,7 @@ +@@ -211,7 +211,7 @@ TEST_CASE("session") } } @@ -268,7 +280,7 @@ index f762f10..2637f67 100644 { // Set some arbitrary leaf. sess.setItem(leaf, "123"); -@@ -230,15 +230,51 @@ +@@ -230,15 +230,51 @@ TEST_CASE("session") sess.switchDatastore(sysrepo::Datastore::Operational); REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); @@ -328,7 +340,7 @@ index f762f10..2637f67 100644 } DOCTEST_SUBCASE("edit batch") -@@ -321,6 +357,11 @@ +@@ -321,6 +357,11 @@ TEST_CASE("session") sess.discardChanges(); } @@ -340,3 +352,6 @@ index f762f10..2637f67 100644 REQUIRE(sess.getPendingChanges() == std::nullopt); } +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0008-upstream-break-sr_rpc_send_tree-s-output-might-be-NU.patch b/package/sysrepo-cpp/0008-upstream-break-sr_rpc_send_tree-s-output-might-be-NU.patch new file mode 100644 index 00000000..a20116c1 --- /dev/null +++ b/package/sysrepo-cpp/0008-upstream-break-sr_rpc_send_tree-s-output-might-be-NU.patch @@ -0,0 +1,131 @@ +From 27dc076cbdff3c9edf00abb05bdce59d1944988b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 31 Jan 2025 17:54:06 +0100 +Subject: [PATCH 08/20] upstream break: sr_rpc_send_tree's `output` might be + NULL now +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +As of upstream commit 927fd5221bd28ff50cc1ee7c2cb5c5f30b34c2c9, sysrepo +can now return a nullptr when the resulting tree is empty. This appears +to have been done in response to our bugreport where we pointed out that +`netopeer2-server` started crashing on empty RPCs. However, it's unclear +why this necessitated a change in sysrepo, given that this feature has +worked well for years. + +Anyway, let's propagate this through the C++ layer; what else can I do, +after all. + +Bug: https://github.com/CESNET/netopeer2/issues/1695 +Change-Id: I646d19f7448d98f0d21e9eec006f28fbb5e479fc +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + CMakeLists.txt | 4 ++-- + include/sysrepo-cpp/Session.hpp | 2 +- + src/Session.cpp | 6 ++++-- + tests/subscriptions.cpp | 7 ++++--- + 5 files changed, 13 insertions(+), 10 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index 70c5e49..dc87c6f 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -6,7 +6,7 @@ + - name: github/CESNET/libyang + override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: cesnet/2025-01-30--00 ++ override-checkout: devel + - name: github/onqtam/doctest + override-checkout: v2.4.8 + - name: github/rollbear/trompeloeil +@@ -17,7 +17,7 @@ + - name: github/CESNET/libyang + override-checkout: devel + - name: github/sysrepo/sysrepo +- override-checkout: cesnet/2025-01-30--00 ++ override-checkout: devel + - name: github/onqtam/doctest + override-checkout: v2.4.11 + - name: github/rollbear/trompeloeil +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 9e09d3d..85ff84c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -20,7 +20,7 @@ add_custom_target(sysrepo-cpp-version-cmake + cmake/ProjectGitVersionRunner.cmake + ) + include(cmake/ProjectGitVersion.cmake) +-set(SYSREPO_CPP_PKG_VERSION "4") ++set(SYSREPO_CPP_PKG_VERSION "5") + prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION}) + + find_package(Doxygen) +@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON) + + find_package(PkgConfig) + pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET) +-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.7 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.8 IMPORTED_TARGET) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 238f480..6e31fa2 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -95,7 +95,7 @@ public: + void discardOperationalChanges(const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + void dropForeignOperationalContent(const std::optional& xpath); + void copyConfig(const Datastore source, const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); +- libyang::DataNode sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); ++ std::optional sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + void sendNotification(libyang::DataNode notification, const Wait wait, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + void replaceConfig(std::optional config, const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + +diff --git a/src/Session.cpp b/src/Session.cpp +index 634e450..5df04bc 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -343,13 +343,15 @@ void Session::copyConfig(const Datastore source, const std::optional Session::sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout) + { + sr_data_t* output; + auto res = sr_rpc_send_tree(m_sess.get(), libyang::getRawNode(input), timeout.count(), &output); + throwIfError(res, "Couldn't send RPC", m_sess.get()); + +- assert(output); // TODO: sysrepo always gives the RPC node? (even when it has not output or output nodes?) ++ if (!output) { ++ return std::nullopt; ++ } + return wrapSrData(m_sess, output); + } + +diff --git a/tests/subscriptions.cpp b/tests/subscriptions.cpp +index 36fd61c..6fc9cbb 100644 +--- a/tests/subscriptions.cpp ++++ b/tests/subscriptions.cpp +@@ -420,9 +420,10 @@ TEST_CASE("subscriptions") + if (ret == sysrepo::ErrorCode::Ok) { + auto output = sess.sendRPC(sess.getContext().newPath(rpcPath)); + if (setFunction) { +- REQUIRE(output.findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output)); +- } else { +- REQUIRE(!output.findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output).has_value()); ++ REQUIRE(!!output); ++ REQUIRE(output->findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output)); ++ } else if (output) { ++ REQUIRE(!output->findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output).has_value()); + } + } else { + REQUIRE_THROWS(sess.sendRPC(sess.getContext().newPath(rpcPath))); +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0009-tests-adapt-to-upstream-breaking-change-in-sr_get_da.patch b/package/sysrepo-cpp/0009-tests-adapt-to-upstream-breaking-change-in-sr_get_da.patch new file mode 100644 index 00000000..54d1dde3 --- /dev/null +++ b/package/sysrepo-cpp/0009-tests-adapt-to-upstream-breaking-change-in-sr_get_da.patch @@ -0,0 +1,83 @@ +From dc8facb5befbd17f2848098bbc9267c9d9f58e2d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Mon, 10 Mar 2025 12:08:47 +0100 +Subject: [PATCH 09/20] tests: adapt to upstream breaking change in sr_get_data +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +It seems that since upstream commit ba8897ce3 [1] the max_depth +parameter started behaving differently. It now behaves as the +documentation [2] states, i.e: + + Maximum depth of the selected subtrees. 0 is unlimited, 1 will not + return any descendant nodes. If a list should be returned, its keys + are always returned as well. + +The previous behaviour was probably wrong. When reported via slack, +another fix was issued [3], so let's depend on that as well. + +[1] https://github.com/sysrepo/sysrepo/commit/ba8897ce3da0bc8203d5b964851fd60fb164760d +[2] https://netopeer.liberouter.org/doc/sysrepo/devel/html/group__get__data__api.html +[3] https://github.com/sysrepo/sysrepo/commit/309b34d28cc09771cf488d04478161f684f4b8a7 + +Change-Id: I337dc1f1f77b9501dda29931846116050123bcad +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + tests/session.cpp | 11 +++++++++-- + 2 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 85ff84c..fb132a2 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON) + + find_package(PkgConfig) + pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET) +-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.8 IMPORTED_TARGET) ++pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET) + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) + +diff --git a/tests/session.cpp b/tests/session.cpp +index 2637f67..5be0d79 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -127,6 +127,13 @@ TEST_CASE("session") + data = sess.getData("/test_module:popelnice", 1); + REQUIRE(data); + REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ ++ "test_module:popelnice": {} ++} ++)"); ++ ++ data = sess.getData("/test_module:popelnice", 2); ++ REQUIRE(data); ++ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ + "test_module:popelnice": { + "content": {} + } +@@ -134,7 +141,7 @@ TEST_CASE("session") + )"); + + // If a list should be returned, its keys are always returned as well. +- data = sess.getData("/test_module:popelnice", 2); ++ data = sess.getData("/test_module:popelnice", 3); + REQUIRE(data); + REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ + "test_module:popelnice": { +@@ -152,7 +159,7 @@ TEST_CASE("session") + } + )"); + +- data = sess.getData("/test_module:popelnice", 3); ++ data = sess.getData("/test_module:popelnice", 4); + REQUIRE(data); + REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({ + "test_module:popelnice": { +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0010-Utilities-for-working-with-existing-sysrepo-discard-.patch b/package/sysrepo-cpp/0010-Utilities-for-working-with-existing-sysrepo-discard-.patch new file mode 100644 index 00000000..fc92376e --- /dev/null +++ b/package/sysrepo-cpp/0010-Utilities-for-working-with-existing-sysrepo-discard-.patch @@ -0,0 +1,222 @@ +From 6ed5442db482a9a37128add02778aebd096f9207 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Fri, 14 Mar 2025 16:20:09 +0100 +Subject: [PATCH 10/20] Utilities for working with existing + sysrepo:discard-items nodes +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +findMatchingDiscard() and findMatchingDiscardPrefixes() are useful for +figuring out which nodes need to be prune when adding/modifying a +discarding node. + +unlinkFromForest() is useful for removing "a node" from a forest of data +node trees. Sometimes the node to be deleted *could* be a root node +which is also the first sibling among its collection of siblings, and in +that case some extra care is needed to ensure that we still have a +reference to something after we've done with the unlinking. + +In the real world this doesn't happen that often because there usually +is something like a top-level container node below which we're +processing these discards. Still, let's put this utility function into a +single place. + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8436 +Change-Id: Ib346be6887f75220a8a6435864c74aef8d5925bb +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/utils/utils.hpp | 3 ++ + src/Session.cpp | 3 ++ + src/utils/utils.cpp | 57 ++++++++++++++++++++++ + tests/session.cpp | 74 +++++++++++++++++++++++++++-- + 4 files changed, 134 insertions(+), 3 deletions(-) + +diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp +index 22d7d66..64b1c2e 100644 +--- a/include/sysrepo-cpp/utils/utils.hpp ++++ b/include/sysrepo-cpp/utils/utils.hpp +@@ -12,4 +12,7 @@ + namespace sysrepo { + Session wrapUnmanagedSession(sr_session_ctx_s* session); + void setLogLevelStderr(const LogLevel); ++std::optional findMatchingDiscard(libyang::DataNode root, const std::string& xpath); ++std::vector findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix); ++void unlinkFromForest(std::optional& root, libyang::DataNode node); + } +diff --git a/src/Session.cpp b/src/Session.cpp +index 5df04bc..3c21e84 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -153,6 +153,7 @@ void Session::deleteItem(const std::string& path, const EditOptions opts) + * Wraps `sr_discard_items`. + * + * @param xpath Expression filtering the nodes to discard, nullopt for all nodes. ++ * @see findMatchingDiscard() + */ + void Session::dropForeignOperationalContent(const std::optional& xpath) + { +@@ -167,6 +168,8 @@ void Session::dropForeignOperationalContent(const std::optional& xp + * with the "replace" operation. + * + * Wraps `sr_get_oper_changes`. ++ * ++ * @see findMatchingDiscard() + */ + std::optional Session::operationalChanges(const std::optional& moduleName) const + { +diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp +index 972a4fa..e339e88 100644 +--- a/src/utils/utils.cpp ++++ b/src/utils/utils.cpp +@@ -72,4 +72,61 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional findMatchingDiscard(libyang::DataNode root, const std::string& xpath) ++{ ++ auto discard = root.firstOpaqueSibling(); ++ while (discard) { ++ if (discard->name().matches("sysrepo", "discard-items") && discard->value() == xpath) { ++ return discard; ++ } ++ if (auto next = discard->nextSibling()) { ++ discard = next->asOpaque(); ++ } else { ++ break; ++ } ++ } ++ return std::nullopt; ++} ++ ++/** ++ * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendats of this XPath ++ */ ++std::vector findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix) ++{ ++ auto withSlash = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '/') ? xpathPrefix : xpathPrefix + '/'; ++ auto withBracket = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '[') ? xpathPrefix : xpathPrefix + '['; ++ std::vector res; ++ auto discard = root.firstOpaqueSibling(); ++ while (discard) { ++ if (discard->name().matches("sysrepo", "discard-items")) { ++ if (auto text = discard->value(); text == xpathPrefix || text.starts_with(withSlash) || text.starts_with(withBracket)) { ++ res.emplace_back(*discard); ++ } ++ } ++ if (auto next = discard->nextSibling()) { ++ discard = next->asOpaque(); ++ } else { ++ break; ++ } ++ } ++ return res; ++} ++ ++/** ++ * @short Remove a node from a forest of tree nodes while modifying the root in-place ++ */ ++void unlinkFromForest(std::optional& root, libyang::DataNode node) ++{ ++ if (node == root) { ++ root = node.nextSibling(); ++ } ++ node.unlink(); ++} ++ + } +diff --git a/tests/session.cpp b/tests/session.cpp +index 5be0d79..9a419a1 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -245,9 +245,77 @@ TEST_CASE("session") + sess.applyChanges(); + REQUIRE(!sess.getData(leaf)); + +- // Using discardOperationalChanges makes the leaf visible again (in the operational datastore). +- // Also, no need to applyChanges(). +- sess.discardOperationalChanges("test_module"); ++ // check that a magic sysrepo:discard-items node is in place ++ REQUIRE(!!sess.operationalChanges()); ++ auto matchingDiscard = sysrepo::findMatchingDiscard(*sess.operationalChanges(), leaf); ++ REQUIRE(!!matchingDiscard); ++ REQUIRE(matchingDiscard->value() == leaf); ++ REQUIRE(matchingDiscard->name().moduleOrNamespace == "sysrepo"); ++ REQUIRE(matchingDiscard->name().name == "discard-items"); ++ REQUIRE(!sysrepo::findMatchingDiscard(*sess.operationalChanges(), "something else")); ++ ++ DOCTEST_SUBCASE("forget changes via discardOperationalChanges(module)") ++ { ++ // Using discardOperationalChanges makes the leaf visible again (in the operational datastore). ++ // Also, no need to applyChanges(). ++ sess.discardOperationalChanges("test_module"); ++ } ++ ++ DOCTEST_SUBCASE("forget changes via a selective edit") ++ { ++ // this edit only has a single node, which means that we cannot really call unlink() and hope for a sane result ++ REQUIRE(matchingDiscard->firstSibling() == *matchingDiscard); ++ ++ // so, we add a dummy node instead... ++ auto root = matchingDiscard->newPath("/test_module:popelnice/s", "foo"); ++ // ...and only then we nuke the eixtsing discard-items node ++ matchingDiscard->unlink(); ++ sess.editBatch(*root, sysrepo::DefaultOperation::Replace); ++ sess.applyChanges(); ++ } ++ ++ DOCTEST_SUBCASE("multiple sysrepo:discard-items nodes") ++ { ++ sess.dropForeignOperationalContent("/test_module:popelnice"); ++ sess.dropForeignOperationalContent("/test_module:popelnice/s"); ++ sess.dropForeignOperationalContent("/test_module:values"); ++ sess.dropForeignOperationalContent("/test_module:popelnice/content"); ++ sess.dropForeignOperationalContent("/test_module:denyAllLeaf"); ++ sess.dropForeignOperationalContent(leaf); // yup, once more, in addition to the one at the very beginning ++ sess.applyChanges(); ++ ++ auto forPopelnice = sysrepo::findMatchingDiscard(*sess.operationalChanges(), "/test_module:popelnice"); ++ REQUIRE(!!forPopelnice); ++ REQUIRE(forPopelnice->value() == "/test_module:popelnice"); ++ auto oneMatch = sysrepo::findMatchingDiscard(*sess.operationalChanges(), "/test_module:values"); ++ REQUIRE(!!oneMatch); ++ REQUIRE(oneMatch->value() == "/test_module:values"); ++ ++ auto atOrBelowPopelnice = sysrepo::findMatchingDiscardPrefixes(*sess.operationalChanges(), "/test_module:popelnice"); ++ REQUIRE(atOrBelowPopelnice.size() == 3); ++ // yup, these are apparently backwards compared to how I put them in. Never mind. ++ REQUIRE(atOrBelowPopelnice[2].value() == "/test_module:popelnice"); ++ REQUIRE(atOrBelowPopelnice[1].value() == "/test_module:popelnice/s"); ++ REQUIRE(atOrBelowPopelnice[0].value() == "/test_module:popelnice/content"); ++ ++ auto belowPopelnice = sysrepo::findMatchingDiscardPrefixes(*sess.operationalChanges(), "/test_module:popelnice/"); ++ REQUIRE(belowPopelnice.size() == 2); ++ // again, the order is reversed ++ REQUIRE(belowPopelnice[1].value() == "/test_module:popelnice/s"); ++ REQUIRE(belowPopelnice[0].value() == "/test_module:popelnice/content"); ++ ++ auto newEdit = sess.operationalChanges(); ++ auto forLeaf = sysrepo::findMatchingDiscardPrefixes(*newEdit, leaf); ++ REQUIRE(forLeaf.size() == 2); ++ REQUIRE(forLeaf[0].value() == leaf); ++ REQUIRE(forLeaf[1].value() == leaf); ++ for (auto node : forLeaf) { ++ sysrepo::unlinkFromForest(newEdit, node); ++ } ++ sess.editBatch(*newEdit, sysrepo::DefaultOperation::Replace); ++ sess.applyChanges(); ++ } ++ + REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123"); + } + +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0011-Fix-a-typo.patch b/package/sysrepo-cpp/0011-Fix-a-typo.patch new file mode 100644 index 00000000..b4078ed0 --- /dev/null +++ b/package/sysrepo-cpp/0011-Fix-a-typo.patch @@ -0,0 +1,36 @@ +From aa78815b05090df63bef9ac1b6473f32aac5363d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Tue, 18 Mar 2025 11:25:05 +0100 +Subject: [PATCH 11/20] Fix a typo +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +...because I don't want to go through the CI again, hence a separate +commit. + +Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes +Reported-by: Tomáš Pecka +Change-Id: I31f3efc1006fe222b7853b24a116b3d8cc04dc8d +Signed-off-by: Mattias Walström +--- + src/utils/utils.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp +index e339e88..ff4a32c 100644 +--- a/src/utils/utils.cpp ++++ b/src/utils/utils.cpp +@@ -95,7 +95,7 @@ std::optional findMatchingDiscard(libyang::DataNode root, con + } + + /** +- * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendats of this XPath ++ * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath + */ + std::vector findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix) + { +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0012-wrap-dynamic-subscription-functions.patch b/package/sysrepo-cpp/0012-wrap-dynamic-subscription-functions.patch new file mode 100644 index 00000000..01516ad2 --- /dev/null +++ b/package/sysrepo-cpp/0012-wrap-dynamic-subscription-functions.patch @@ -0,0 +1,7598 @@ +From 415c401bc12070dfa3b7705e9a2c0c99d899411c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 27 Nov 2024 14:40:20 +0100 +Subject: [PATCH 12/20] wrap dynamic subscription functions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This patch wraps the dynamic subscription functions. We add a new +handle for the subscription that contains the file descriptor where +clients are supposed to send the notifications. + +We tried to reuse the current Subscription class for dynamic +subscriptions as well and not let sysrepo create its own internal +background threads. However, subscribing to yang push periodic timer +creates a new thread in sysrepo anyway, so completely decoupling from +the C API would be much work. + +This patch does not yet wrap the functions for modifying subscriptions. + +The new DynamicSubscription class implements pimpl idiom. The class is +movable. When the subscription is moved, the destructor is called on +the old object. However, the destructor is supposed to close the file +descriptor. We need to make sure that the file descriptor is not closed +twice. +I was thinking about using std::unique_ptr to manage the file descriptor +or by setting the fd to something "invalid" after the move. I found the +former solution more elegant. + +Maybe we should refactor the Subscription class to use pimpl idiom as +well. + +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8149 +Change-Id: I1cee5e730fcc762b2e0a58d3d5c40bf358504397 +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 10 + + include/sysrepo-cpp/Session.hpp | 16 + + include/sysrepo-cpp/Subscription.hpp | 49 + + src/Session.cpp | 118 ++ + src/Subscription.cpp | 101 ++ + tests/subscriptions-dynamic.cpp | 475 +++++ + tests/test_module.yang | 4 + + tests/yang/iana-if-type@2014-05-08.yang | 1523 +++++++++++++++++ + tests/yang/ietf-interfaces@2018-02-20.yang | 1123 ++++++++++++ + tests/yang/ietf-ip@2018-02-22.yang | 876 ++++++++++ + .../ietf-network-instance@2019-01-21.yang | 282 +++ + tests/yang/ietf-restconf@2017-01-26.yang | 278 +++ + ...f-subscribed-notifications@2019-09-09.yang | 1350 +++++++++++++++ + tests/yang/ietf-yang-patch@2017-02-22.yang | 390 +++++ + tests/yang/ietf-yang-push@2019-09-09.yang | 797 +++++++++ + 15 files changed, 7392 insertions(+) + create mode 100644 tests/subscriptions-dynamic.cpp + create mode 100644 tests/yang/iana-if-type@2014-05-08.yang + create mode 100644 tests/yang/ietf-interfaces@2018-02-20.yang + create mode 100644 tests/yang/ietf-ip@2018-02-22.yang + create mode 100644 tests/yang/ietf-network-instance@2019-01-21.yang + create mode 100644 tests/yang/ietf-restconf@2017-01-26.yang + create mode 100644 tests/yang/ietf-subscribed-notifications@2019-09-09.yang + create mode 100644 tests/yang/ietf-yang-patch@2017-02-22.yang + create mode 100644 tests/yang/ietf-yang-push@2019-09-09.yang + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index fb132a2..01ec967 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -76,9 +76,19 @@ if(BUILD_TESTING) + set(fixture-test-module + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_module.yang + ) ++ set(fixture-dynamic-subscriptions ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_module.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/iana-if-type@2014-05-08.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-interfaces@2018-02-20.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-ip@2018-02-22.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-network-instance@2019-01-21.yang ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-yang-push@2019-09-09.yang -e on-change ++ ) + + sysrepo_cpp_test(NAME session FIXTURE fixture-test-module) + sysrepo_cpp_test(NAME subscriptions FIXTURE fixture-test-module LIBRARIES Threads::Threads) ++ sysrepo_cpp_test(NAME subscriptions-dynamic FIXTURE fixture-dynamic-subscriptions LIBRARIES PkgConfig::SYSREPO) + sysrepo_cpp_test(NAME unsafe FIXTURE fixture-test-module LIBRARIES PkgConfig::SYSREPO) + endif() + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 6e31fa2..255f861 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -136,6 +136,22 @@ public: + ExceptionHandler handler = nullptr, + const std::optional& callbacks = std::nullopt); + ++ [[nodiscard]] DynamicSubscription yangPushPeriodic( ++ const std::optional& xpathFilter, ++ std::chrono::milliseconds periodTime, ++ const std::optional& anchorTime = std::nullopt, ++ const std::optional& stopTime = std::nullopt); ++ [[nodiscard]] DynamicSubscription yangPushOnChange( ++ const std::optional& xpathFilter, ++ const std::optional& dampeningPeriod = std::nullopt, ++ SyncOnStart syncOnStart = SyncOnStart::No, ++ const std::optional& stopTime = std::nullopt); ++ [[nodiscard]] DynamicSubscription subscribeNotifications( ++ const std::optional& xpathFilter, ++ const std::optional& stream = std::nullopt, ++ const std::optional& stopTime = std::nullopt, ++ const std::optional& startTime = std::nullopt); ++ + ChangeCollection getChanges(const std::string& xpath = "//."); + void setErrorMessage(const std::string& msg); + void setNetconfError(const NetconfErrorInfo& info); +diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp +index 899af03..7fdc08b 100644 +--- a/include/sysrepo-cpp/Subscription.hpp ++++ b/include/sysrepo-cpp/Subscription.hpp +@@ -189,6 +189,13 @@ using RpcActionCb = std::function notificationTree, const NotificationTimeStamp timestamp)>; + ++/** ++ * A callback for YANG push notification subscriptions. ++ * @param notification The notification tree. ++ * @param timestamp Time when the notification was generated. ++ */ ++using YangPushNotifCb = std::function notificationTree, const NotificationTimeStamp timestamp)>; ++ + /** + * Exception handler type for handling exceptions thrown in user callbacks. + */ +@@ -268,4 +275,46 @@ private: + + bool m_didNacmInit; + }; ++ ++enum class SyncOnStart : bool { ++ No, ++ Yes, ++}; ++ ++/** ++ * @brief Manages lifetime of YANG push subscriptions. ++ * ++ * Users are supposed to create instances of this class via Session::yangPushPeriodic, Session::yangPushOnChange or Session::subscribeNotifications. ++ * Whenever notified about a change (by polling the file descriptor obtained by fd() function), ++ * there is at least one event waiting to be processed by a call to YangPushSubscription::processEvent. ++ * ++ * Internally, the sysrepo C library creates some background thread(s). These are used either for managing internal, ++ * sysrepo-level module subscriptions, or for scheduling of periodic timers. These threads are fully encapsulated by ++ * the C code, and there is no control over them from this C++ wrapper. The public interface of this class is a file ++ * descriptor that the caller is expected to poll for readability/closing (and the subscription ID). Once the FD is ++ * readable, invoke this class' processEvents(). There is no automatic event loop which would take care of this ++ * functionality, and users are expected to integrate this FD into their own event handling. ++ */ ++class DynamicSubscription { ++public: ++ DynamicSubscription(const DynamicSubscription&) = delete; ++ DynamicSubscription& operator=(const DynamicSubscription&) = delete; ++ DynamicSubscription(DynamicSubscription&&) noexcept; ++ DynamicSubscription& operator=(DynamicSubscription&&) noexcept; ++ ~DynamicSubscription(); ++ ++ int fd() const; ++ uint64_t subscriptionId() const; ++ std::optional replayStartTime() const; ++ void processEvent(YangPushNotifCb cb) const; ++ void terminate(const std::optional& reason = std::nullopt); ++ ++private: ++ DynamicSubscription(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime = std::nullopt); ++ ++ struct Data; ++ std::unique_ptr m_data; ++ ++ friend class Session; ++}; + } +diff --git a/src/Session.cpp b/src/Session.cpp +index 3c21e84..2c27c92 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -11,6 +11,7 @@ extern "C" { + #include + #include + #include ++#include + } + #include + #include +@@ -522,6 +523,123 @@ Subscription Session::onNotification( + return sub; + } + ++/** ++ * Subscribe for receiving notifications according to 'ietf-yang-push' YANG periodic subscriptions. ++ * ++ * Wraps `srsn_yang_push_periodic`. ++ * ++ * @param xpathFilter Optional XPath that filters received notification. ++ * @param periodTime Notification period. ++ * @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period. ++ * @param stopTime Optional stop time ending the notification subscription. ++ * ++ * @return A YangPushSubscription handle. ++ */ ++DynamicSubscription Session::yangPushPeriodic( ++ const std::optional& xpathFilter, ++ std::chrono::milliseconds periodTime, ++ const std::optional& anchorTime, ++ const std::optional& stopTime) ++{ ++ int fd; ++ uint32_t subId; ++ auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; ++ auto anchorSpec = anchorTime ? std::optional{toTimespec(*anchorTime)} : std::nullopt; ++ auto res = srsn_yang_push_periodic(m_sess.get(), ++ toDatastore(activeDatastore()), ++ xpathFilter ? xpathFilter->c_str() : nullptr, ++ periodTime.count(), ++ anchorSpec ? &anchorSpec.value() : nullptr, ++ stopSpec ? &stopSpec.value() : nullptr, ++ &fd, ++ &subId); ++ throwIfError(res, "Couldn't create yang-push periodic subscription", m_sess.get()); ++ ++ return {m_sess, fd, subId}; ++} ++ ++/** ++ * Subscribe for receiving notifications according to 'ietf-yang-push' YANG on-change subscriptions. ++ * ++ * Wraps `srsn_yang_push_on_change`. ++ * ++ * @param xpathFilter Optional XPath that filters received notification. ++ * @param dampeningPeriod Optional dampening period. ++ * @param syncOnStart Whether to start with a notification of the current state. ++ * @param stopTime Optional stop time ending the notification subscription. ++ * ++ * @return A YangPushSubscription handle. ++ */ ++DynamicSubscription Session::yangPushOnChange( ++ const std::optional& xpathFilter, ++ const std::optional& dampeningPeriod, ++ SyncOnStart syncOnStart, ++ const std::optional& stopTime) ++{ ++ int fd; ++ uint32_t subId; ++ auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; ++ auto res = srsn_yang_push_on_change(m_sess.get(), ++ toDatastore(activeDatastore()), ++ xpathFilter ? xpathFilter->c_str() : nullptr, ++ dampeningPeriod ? dampeningPeriod->count() : 0, ++ syncOnStart == SyncOnStart::Yes, ++ nullptr, ++ stopSpec ? &stopSpec.value() : nullptr, ++ 0, ++ nullptr, ++ &fd, ++ &subId); ++ throwIfError(res, "Couldn't create yang-push on-change subscription", m_sess.get()); ++ ++ return {m_sess, fd, subId}; ++} ++ ++/** ++ * Subscribe for receiving notifications according to 'ietf-subscribed-notifications' ++ * ++ * Wraps `srsn_subscribe. ++ * ++ * @param xpathFilter Optional XPath that filters received notification. ++ * @param stream Optional stream to subscribe to. ++ * @param stopTime Optional stop time ending the subscription. ++ * @param startTime Optional start time of the subscription, used for replaying stored notifications. ++ * ++ * @return A YangPushSubscription handle. ++ */ ++DynamicSubscription Session::subscribeNotifications( ++ const std::optional& xpathFilter, ++ const std::optional& stream, ++ const std::optional& stopTime, ++ const std::optional& startTime) ++{ ++ int fd; ++ uint32_t subId; ++ auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; ++ auto startSpec = startTime ? std::optional{toTimespec(*startTime)} : std::nullopt; ++ struct timespec replayStartSpec; ++ ++ auto res = srsn_subscribe(m_sess.get(), ++ stream ? stream->c_str() : nullptr, ++ xpathFilter ? xpathFilter->c_str() : nullptr, ++ stopSpec ? &stopSpec.value() : nullptr, ++ startSpec ? &startSpec.value() : nullptr, ++ false, ++ nullptr, ++ &replayStartSpec, ++ &fd, ++ &subId); ++ ++ throwIfError(res, "Couldn't create notification subscription", m_sess.get()); ++ ++ std::optional replayStart; ++ if (replayStartSpec.tv_sec != 0) { ++ replayStart = toTimePoint(replayStartSpec); ++ } ++ ++ return {m_sess, fd, subId, replayStart}; ++} ++ + /** + * Returns a collection of changes based on an `xpath`. Use "//." to get a full change subtree. + * +diff --git a/src/Subscription.cpp b/src/Subscription.cpp +index f71117e..ab27b16 100644 +--- a/src/Subscription.cpp ++++ b/src/Subscription.cpp +@@ -11,6 +11,7 @@ + extern "C" { + #include + #include ++#include + } + #include "utils/enum.hpp" + #include "utils/exception.hpp" +@@ -410,4 +411,104 @@ bool ChangeIterator::operator==(const ChangeIterator& other) const + // And then either both contain nothing or contain the same thing. + (!this->m_current.has_value() || this->m_current->node == other.m_current->node); + } ++ ++ ++struct DynamicSubscription::Data { ++ std::shared_ptr sess; ++ int fd; ++ uint64_t subId; ++ std::optional m_replayStartTime; ++ bool m_terminated; ++ ++ Data(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime, bool terminated); ++ ~Data(); ++ void terminate(const std::optional& reason = std::nullopt); ++}; ++ ++DynamicSubscription::DynamicSubscription(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime) ++ : m_data(std::make_unique(std::move(sess), fd, subId, replayStartTime, false)) ++{ ++} ++ ++DynamicSubscription::DynamicSubscription(DynamicSubscription&&) noexcept = default; ++DynamicSubscription& DynamicSubscription::operator=(DynamicSubscription&&) noexcept = default; ++DynamicSubscription::~DynamicSubscription() = default; ++ ++/** @brief Returns the file descriptor associated with this subscription. */ ++int DynamicSubscription::fd() const ++{ ++ return m_data->fd; ++} ++ ++/** @brief Returns the subscription ID associated with this subscription. */ ++uint64_t DynamicSubscription::subscriptionId() const ++{ ++ return m_data->subId; ++} ++ ++/** @brief Returns the actual start time of replayed notification subscription, if available. */ ++std::optional DynamicSubscription::replayStartTime() const ++{ ++ return m_data->m_replayStartTime; ++} ++ ++/** @brief Terminates the subscription. ++ * ++ * Wraps `srsn_terminate`. ++ */ ++void DynamicSubscription::terminate(const std::optional& reason) ++{ ++ m_data->terminate(reason); ++} ++ ++/** @brief Processes a single event associated with this subscription. ++ * ++ * Invoke only when the file descriptor associated with this subscription is ready for reading. ++ * Otherwise, the function blocks unless the FD is set to non-blocking. ++ * ++ * Wraps `srsn_read_notif`. ++ */ ++void DynamicSubscription::processEvent(YangPushNotifCb cb) const ++{ ++ struct timespec timestamp; ++ struct lyd_node* tree; ++ auto ctx = std::unique_ptr>(sr_session_acquire_context(m_data->sess.get()), [&](const ly_ctx*) { sr_session_release_context(m_data->sess.get()); }); ++ ++ auto err = srsn_read_notif(fd(), ctx.get(), ×tamp, &tree); ++ throwIfError(err, "Couldn't read yang-push notification"); ++ ++ const auto wrappedNotification = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt; ++ ++ // If we see subscription-terminated notification, the subscription is terminated by sysrepo (e.g. because of stop-time) ++ // This is not the correct notification as per RFC to use when reaching stop-time, see https://github.com/sysrepo/sysrepo/issues/3525. ++ if (wrappedNotification && wrappedNotification->path() == "/ietf-subscribed-notifications:subscription-terminated") { ++ m_data->m_terminated = true; ++ } ++ ++ cb(wrappedNotification, toTimePoint(timestamp)); ++} ++ ++DynamicSubscription::Data::Data(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime, bool terminated) ++ : sess(std::move(sess)) ++ , fd(fd) ++ , subId(subId) ++ , m_replayStartTime(replayStartTime) ++ , m_terminated(terminated) ++{ ++} ++ ++DynamicSubscription::Data::~Data() ++{ ++ if (!m_terminated) { ++ terminate(); ++ } ++ close(fd); ++} ++ ++void DynamicSubscription::Data::terminate(const std::optional& reason) ++{ ++ auto err = srsn_terminate(subId, reason ? reason->c_str() : nullptr); ++ throwIfError(err, "Couldn't terminate yang-push subscription with id " + std::to_string(subId)); ++ m_terminated = true; ++} + } +diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp +new file mode 100644 +index 0000000..a44bfba +--- /dev/null ++++ b/tests/subscriptions-dynamic.cpp +@@ -0,0 +1,475 @@ ++/* ++ * Copyright (C) 2024 CESNET, https://photonics.cesnet.cz/ ++ * ++ * Written by Tomáš Pecka ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "sysrepo-cpp/Subscription.hpp" ++#include "utils.hpp" ++ ++#define CLIENT_SEND_NOTIFICATION(NOTIFICATION) \ ++ { \ ++ auto notif = client.getContext().parseOp(NOTIFICATION, libyang::DataFormat::JSON, libyang::OperationType::NotificationYang); \ ++ client.sendNotification(*notif.tree, sysrepo::Wait::Yes); \ ++ } ++ ++#define REQUIRE_PIPE_HANGUP(SUBSCRIPTION) \ ++ REQUIRE(pipeStatus((SUBSCRIPTION).fd()) == PipeStatus::Hangup) ++ ++#define REQUIRE_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq); ++ ++#define READ_NOTIFICATION(SUBSCRIPTION) \ ++ REQUIRE(pipeStatus((SUBSCRIPTION).fd()) == PipeStatus::DataReady); \ ++ (SUBSCRIPTION).processEvent(cbNotif); ++ ++#define READ_NOTIFICATION_BLOCKING(SUBSCRIPTION) \ ++ REQUIRE(pipeStatus((SUBSCRIPTION).fd(), -1) == PipeStatus::DataReady); \ ++ (SUBSCRIPTION).processEvent(cbNotif); ++ ++#define SUBSCRIPTION_TERMINATED(SUBSCRIPTION) R"({ ++ "ietf-subscribed-notifications:subscription-terminated": { ++ "id": )" + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(, ++ "reason": "no-such-subscription" ++ } ++} ++)" ++ ++#define REPLAY_COMPLETED(SUBSCRIPTION) R"({ ++ "ietf-subscribed-notifications:replay-completed": { ++ "id": )" \ ++ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"( ++ } ++} ++)" ++ ++#define REQUIRE_YANG_PUSH_UPDATE(SUBSCRIPTION, NOTIFICATION) \ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate((SUBSCRIPTION).subscriptionId(), NOTIFICATION)).IN_SEQUENCE(seq); ++ ++#define READ_YANG_PUSH_UPDATE(SUBSCRIPTION) \ ++ REQUIRE(pipeStatus((SUBSCRIPTION).fd()) == PipeStatus::DataReady); \ ++ (SUBSCRIPTION).processEvent(cbYangPushUpdate); ++ ++ ++using namespace std::chrono_literals; ++ ++namespace trompeloeil { ++template <> ++struct printer> { ++ static void print(std::ostream& os, const std::optional& b) ++ { ++ if (!b) { ++ os << "std::nullopt"; ++ } else { ++ os << *b; ++ } ++ } ++}; ++} ++ ++class Recorder { ++public: ++ TROMPELOEIL_MAKE_CONST_MOCK1(recordNotification, void(std::optional)); ++ TROMPELOEIL_MAKE_CONST_MOCK2(recordYangPushUpdate, void(uint32_t, std::optional)); ++}; ++ ++enum class PipeStatus { ++ NoData, ++ DataReady, ++ Hangup, ++ Other, // represents any other poll() result, we are not interested in details ++}; ++ ++/** Check the status of the pipe for reading. ++ * ++ * @param timeout Timeout in milliseconds, 0 means check the status right now, -1 means blocking wait for an event, see poll(2) ++ */ ++PipeStatus pipeStatus(int fd, int timeout = 0) ++{ ++ pollfd fds = {.fd = fd, .events = POLLIN | POLLHUP, .revents = 0}; ++ ++ int r = poll(&fds, 1, timeout); ++ INFO("poll returned " << r << ", revents: " << fds.revents); ++ if (r == 0) { ++ return PipeStatus::NoData; ++ } else if (r == 1 && fds.revents & POLLIN) { ++ return PipeStatus::DataReady; ++ } else if (r == 1 && fds.revents & POLLHUP) { ++ return PipeStatus::Hangup; ++ } else { ++ return PipeStatus::Other; ++ } ++} ++ ++TEST_CASE("Dynamic subscriptions") ++{ ++ trompeloeil::sequence seq; ++ Recorder rec; ++ ++ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information); ++ sysrepo::Connection conn{}; ++ auto sess = conn.sessionStart(); ++ auto client = conn.sessionStart(); ++ sess.sendRPC(sess.getContext().newPath("/ietf-factory-default:factory-reset")); ++ ++ auto cbYangPushUpdate = [&](const std::optional& tree, auto) { ++ REQUIRE(tree); ++ ++ // the yang-push notification contains subscription id. This is a hack that enables us to compare string representations ++ // the subscription is compared using another parameter in the expectation ++ auto idNode = tree->findPath("id"); ++ const auto subId = std::get(idNode->asTerm().value()); ++ idNode->unlink(); ++ ++ rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)); ++ }; ++ ++ auto cbNotif = [&](const std::optional& tree, auto) { ++ REQUIRE(tree); ++ rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)); ++ }; ++ ++ // write some initial data ++ client.setItem("/test_module:values[.='2']", std::nullopt); ++ client.setItem("/test_module:values[.='3']", std::nullopt); ++ client.applyChanges(); ++ ++ DOCTEST_SUBCASE("Subscribed notifications") ++ { ++ /* notification JSONs are very verbose, let's give them names even if they are not very descriptive ++ * but still better than polluting the tests below with repeated long R-strings */ ++ const std::vector notifications = { ++ R"({ ++ "test_module:ping": { ++ "myLeaf": 1 ++ } ++} ++)", ++ R"({ ++ "test_module:silent-ping": {} ++} ++)", ++ R"({ ++ "test_module:ping": { ++ "myLeaf": 2 ++ } ++} ++)"}; ++ ++ DOCTEST_SUBCASE("Subscribe to everything from test_module") ++ { ++ auto sub = sess.subscribeNotifications("/test_module:*", std::nullopt); ++ REQUIRE(!sub.replayStartTime()); ++ ++ CLIENT_SEND_NOTIFICATION(notifications[0]); ++ CLIENT_SEND_NOTIFICATION(notifications[1]); ++ ++ REQUIRE_NOTIFICATION(sub, notifications[0]); ++ READ_NOTIFICATION(sub); ++ ++ CLIENT_SEND_NOTIFICATION(notifications[2]); ++ ++ REQUIRE_NOTIFICATION(sub, notifications[1]); ++ READ_NOTIFICATION(sub); ++ ++ REQUIRE_NOTIFICATION(sub, notifications[2]); ++ READ_NOTIFICATION(sub); ++ ++ sub.terminate(); ++ ++ // Notification was sent after the subscription was terminated, so it should not be received ++ CLIENT_SEND_NOTIFICATION(notifications[0]); ++ ++ REQUIRE_PIPE_HANGUP(sub); ++ } ++ ++ DOCTEST_SUBCASE("Subscribe to the notification about stop-time reached") ++ { ++ auto stopTime = std::chrono::system_clock::now() + 500ms /* let's hope 500 ms should be enough to create subscription and notification */; ++ auto sub = sess.subscribeNotifications("/ietf-subscribed-notifications:subscription-terminated", std::nullopt, stopTime); ++ REQUIRE(!sub.replayStartTime()); ++ ++ // this notification is not subscribed, sysrepo should filter it ++ CLIENT_SEND_NOTIFICATION(notifications[0]); ++ ++ // wait until stop time and bit more ++ std::this_thread::sleep_until(stopTime + 500ms); ++ ++ REQUIRE_NOTIFICATION(sub, SUBSCRIPTION_TERMINATED(sub)); ++ READ_NOTIFICATION(sub); ++ ++ REQUIRE_PIPE_HANGUP(sub); ++ } ++ ++ DOCTEST_SUBCASE("Replays revise replay start time value") ++ { ++ conn.setModuleReplaySupport("test_module", true); ++ ++ // one notification for replay ++ auto before = std::chrono::system_clock::now(); ++ CLIENT_SEND_NOTIFICATION(notifications[0]); ++ auto after = std::chrono::system_clock::now(); ++ ++ auto sub = sess.subscribeNotifications("/test_module:*", std::nullopt, std::nullopt, std::chrono::system_clock::now() - 666s /* replay everything that happened at most 666s ago */); ++ ++ // replay start time is revised according to the first notification ++ REQUIRE(sub.replayStartTime()); ++ REQUIRE(sub.replayStartTime() >= before); ++ REQUIRE(sub.replayStartTime() <= after); ++ ++ // wait for the replayed notification and replay-completed notification ++ REQUIRE_NOTIFICATION(sub, notifications[0]); ++ READ_NOTIFICATION_BLOCKING(sub); ++ REQUIRE_NOTIFICATION(sub, REPLAY_COMPLETED(sub)); ++ READ_NOTIFICATION_BLOCKING(sub); ++ ++ sub.terminate(); ++ ++ REQUIRE_PIPE_HANGUP(sub); ++ } ++ ++ DOCTEST_SUBCASE("Terminate manually with a reason") ++ { ++ auto sub = sess.subscribeNotifications("/test_module:*", std::nullopt); ++ ++ REQUIRE_NOTIFICATION(sub, R"({ ++ "ietf-subscribed-notifications:subscription-terminated": { ++ "id": )" + std::to_string(sub.subscriptionId()) + R"(, ++ "reason": "filter-unavailable" ++ } ++} ++)"); ++ sub.terminate("ietf-subscribed-notifications:filter-unavailable"); ++ READ_NOTIFICATION(sub); ++ ++ REQUIRE_PIPE_HANGUP(sub); ++ } ++ ++ DOCTEST_SUBCASE("Terminate by destruction") ++ { ++ auto sub = std::make_unique(sess.subscribeNotifications("/test_module:*", std::nullopt)); ++ sub.reset(); ++ ++ // new events can happen but sysrepo is not supposed to send them to a terminated subscription ++ // also the FD is closed so no point in reading from it ++ client.setItem("/test_module:values[.='6']", std::nullopt); ++ client.applyChanges(); ++ } ++ ++ DOCTEST_SUBCASE("Terminate called twice") ++ { ++ auto sub = std::make_unique(sess.subscribeNotifications("/test_module:*", std::nullopt)); ++ sub->terminate(); ++ ++ // if the message is inlined into the macro, doctest<=2.4.8 fails with "REQUIRE_THROWS_WITH_AS( sub->terminate(), "", sysrepo::ErrorWithCode )", ++ // it does work after 8de4cf7e759c55a89761c25900d80e01ae7ac3fd ++ // TODO: Can be simplified after we bump minimal doctest version to doctest>=2.4.9 ++ const auto excMessage = "Couldn't terminate yang-push subscription with id " + std::to_string(sub->subscriptionId()) + ": SR_ERR_NOT_FOUND"; ++ REQUIRE_THROWS_WITH_AS(sub->terminate(), excMessage.c_str(), sysrepo::ErrorWithCode); ++ } ++ } ++ ++ DOCTEST_SUBCASE("YANG Push on change") ++ { ++ /* YANG Push on change sends the whole data tree every time the data tree changes. ++ * We read the notifications from the associated fd manually, so there should be no races ++ * between writing to sysrepo and reading the notifications. ++ */ ++ ++ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes); ++ ++ REQUIRE_YANG_PUSH_UPDATE(sub, R"({ ++ "ietf-yang-push:push-update": { ++ "datastore-contents": { ++ "test_module:values": [ ++ 2, ++ 3 ++ ] ++ } ++ } ++} ++)"); ++ READ_YANG_PUSH_UPDATE(sub); ++ ++ client.setItem("/test_module:leafInt32", "123"); ++ client.setItem("/test_module:values[.='5']", std::nullopt); ++ client.deleteItem("/test_module:values[.='3']"); ++ client.applyChanges(); ++ ++ REQUIRE_YANG_PUSH_UPDATE(sub, R"({ ++ "ietf-yang-push:push-change-update": { ++ "datastore-changes": { ++ "yang-patch": { ++ "patch-id": "patch-1", ++ "edit": [ ++ { ++ "edit-id": "edit-1", ++ "operation": "create", ++ "target": "/test_module:leafInt32", ++ "value": { ++ "test_module:leafInt32": 123 ++ } ++ }, ++ { ++ "edit-id": "edit-2", ++ "operation": "insert", ++ "target": "/test_module:values[.='5']", ++ "point": "/test_module:values[.='5'][.='3']", ++ "where": "after", ++ "value": { ++ "test_module:values": [ ++ 5 ++ ] ++ } ++ }, ++ { ++ "edit-id": "edit-3", ++ "operation": "delete", ++ "target": "/test_module:values[.='3']" ++ } ++ ] ++ } ++ } ++ } ++} ++)"); ++ READ_YANG_PUSH_UPDATE(sub); ++ ++ sub.terminate(); ++ REQUIRE_PIPE_HANGUP(sub); ++ } ++ ++ DOCTEST_SUBCASE("YANG Push periodic") ++ { ++ /* ++ * Periodic yang push sends the whole data tree every interval, we can't read manually, so ++ * we create another thread that modifies the data tree and this thread will read the notifications. ++ * ++ * Also, we don't know the subscription id in advance, so we have to read it from the first notification ++ * and use it in the following notifications. That requires some trompeloeil magic with LR_SIDE_EFFECT and LR_WITH. ++ */ ++ ++ std::invoke_result_t subId; ++ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate(ANY(uint32_t), R"({ ++ "ietf-yang-push:push-update": { ++ "datastore-contents": { ++ "test_module:values": [ ++ 2, ++ 3 ++ ] ++ } ++ } ++} ++)")) ++ .IN_SEQUENCE(seq) ++ .TIMES(AT_LEAST(1)) ++ .LR_SIDE_EFFECT(subId = _1); ++ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate(ANY(uint32_t), R"({ ++ "ietf-yang-push:push-update": { ++ "datastore-contents": { ++ "test_module:leafInt32": 123, ++ "test_module:values": [ ++ 2, ++ 5 ++ ] ++ } ++ } ++} ++)")) ++ .IN_SEQUENCE(seq) ++ .TIMES(AT_LEAST(1)) ++ .LR_WITH(subId == _1); ++ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate(ANY(uint32_t), R"({ ++ "ietf-yang-push:push-update": { ++ "datastore-contents": { ++ "test_module:leafInt32": 123, ++ "test_module:values": [ ++ 2, ++ 5, ++ 6 ++ ] ++ } ++ } ++} ++)")) ++ .IN_SEQUENCE(seq) ++ .TIMES(AT_LEAST(1)) ++ .LR_WITH(subId == _1); ++ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate(ANY(uint32_t), R"({ ++ "ietf-yang-push:push-update": { ++ "datastore-contents": { ++ "test_module:leafInt32": 123, ++ "test_module:values": [ ++ 2, ++ 5, ++ 7 ++ ] ++ } ++ } ++} ++)")) ++ .IN_SEQUENCE(seq) ++ .TIMES(AT_LEAST(1)) ++ .LR_WITH(subId == _1); ++ ++ TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate(ANY(uint32_t), R"({ ++ "ietf-subscribed-notifications:subscription-terminated": { ++ "reason": "no-such-subscription" ++ } ++} ++)")) ++ .IN_SEQUENCE(seq) ++ .LR_WITH(_1 = subId); ++ ++ auto sub = sess.yangPushPeriodic(std::nullopt, std::chrono::milliseconds{66}, std::nullopt, std::chrono::system_clock::now() + 6666ms); ++ ++ std::jthread srDataEditor([&] { ++ auto sess = conn.sessionStart(); ++ ++ std::this_thread::sleep_for(500ms); ++ ++ sess.setItem("/test_module:leafInt32", "123"); ++ sess.setItem("/test_module:values[.='5']", std::nullopt); ++ sess.deleteItem("/test_module:values[.='3']"); ++ sess.applyChanges(); ++ std::this_thread::sleep_for(500ms); ++ ++ sess.setItem("/test_module:values[.='6']", std::nullopt); ++ sess.applyChanges(); ++ std::this_thread::sleep_for(500ms); ++ ++ sess.setItem("/test_module:values[.='7']", std::nullopt); ++ sess.deleteItem("/test_module:values[.='6']"); ++ sess.applyChanges(); ++ }); ++ ++ while (true) { ++ auto status = pipeStatus(sub.fd(), -1 /* block */); ++ if (status == PipeStatus::Hangup) { ++ break; ++ } else if (status == PipeStatus::DataReady) { ++ sub.processEvent(cbYangPushUpdate); ++ } else if (status == PipeStatus::Other) { ++ FAIL("PipeStatus::Other before the subscription was terminated"); ++ } else { ++ FAIL("PipeStatus::NoData but poll() should block until an event is available"); ++ } ++ } ++ ++ REQUIRE(subId == sub.subscriptionId()); ++ REQUIRE_PIPE_HANGUP(sub); ++ } ++} +diff --git a/tests/test_module.yang b/tests/test_module.yang +index fb91fe2..02c467b 100644 +--- a/tests/test_module.yang ++++ b/tests/test_module.yang +@@ -6,6 +6,10 @@ module test_module { + prefix nacm; + } + ++ import ietf-subscribed-notifications { ++ prefix sn; ++ } ++ + leaf leafInt32 { + type int32; + } +diff --git a/tests/yang/iana-if-type@2014-05-08.yang b/tests/yang/iana-if-type@2014-05-08.yang +new file mode 100644 +index 0000000..81b2175 +--- /dev/null ++++ b/tests/yang/iana-if-type@2014-05-08.yang +@@ -0,0 +1,1523 @@ ++module iana-if-type { ++ namespace "urn:ietf:params:xml:ns:yang:iana-if-type"; ++ prefix ianaift; ++ ++ import ietf-interfaces { ++ prefix if; ++ } ++ ++ organization "IANA"; ++ contact ++ " Internet Assigned Numbers Authority ++ ++ Postal: ICANN ++ 4676 Admiralty Way, Suite 330 ++ Marina del Rey, CA 90292 ++ ++ Tel: +1 310 823 9358 ++ "; ++ description ++ "This YANG module defines YANG identities for IANA-registered ++ interface types. ++ ++ This YANG module is maintained by IANA and reflects the ++ 'ifType definitions' registry. ++ ++ The latest revision of this YANG module can be obtained from ++ the IANA web site. ++ ++ Requests for new values should be made to IANA via ++ email (iana@iana.org). ++ ++ Copyright (c) 2014 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (http://trustee.ietf.org/license-info). ++ ++ The initial version of this YANG module is part of RFC 7224; ++ see the RFC itself for full legal notices."; ++ reference ++ "IANA 'ifType definitions' registry. ++ "; ++ ++ revision 2014-05-08 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 7224: IANA Interface Type YANG Module"; ++ } ++ ++ identity iana-interface-type { ++ base if:interface-type; ++ description ++ "This identity is used as a base for all interface types ++ defined in the 'ifType definitions' registry."; ++ } ++ ++ identity other { ++ base iana-interface-type; ++ } ++ identity regular1822 { ++ base iana-interface-type; ++ } ++ identity hdh1822 { ++ base iana-interface-type; ++ } ++ identity ddnX25 { ++ base iana-interface-type; ++ } ++ identity rfc877x25 { ++ base iana-interface-type; ++ reference ++ "RFC 1382 - SNMP MIB Extension for the X.25 Packet Layer"; ++ } ++ identity ethernetCsmacd { ++ base iana-interface-type; ++ description ++ "For all Ethernet-like interfaces, regardless of speed, ++ as per RFC 3635."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity iso88023Csmacd { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Deprecated via RFC 3635. ++ Use ethernetCsmacd(6) instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity iso88024TokenBus { ++ base iana-interface-type; ++ } ++ identity iso88025TokenRing { ++ base iana-interface-type; ++ } ++ identity iso88026Man { ++ base iana-interface-type; ++ } ++ identity starLan { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Deprecated via RFC 3635. ++ Use ethernetCsmacd(6) instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity proteon10Mbit { ++ base iana-interface-type; ++ } ++ identity proteon80Mbit { ++ base iana-interface-type; ++ } ++ identity hyperchannel { ++ base iana-interface-type; ++ } ++ identity fddi { ++ base iana-interface-type; ++ reference ++ "RFC 1512 - FDDI Management Information Base"; ++ } ++ identity lapb { ++ base iana-interface-type; ++ reference ++ "RFC 1381 - SNMP MIB Extension for X.25 LAPB"; ++ } ++ identity sdlc { ++ base iana-interface-type; ++ } ++ identity ds1 { ++ base iana-interface-type; ++ description ++ "DS1-MIB."; ++ reference ++ "RFC 4805 - Definitions of Managed Objects for the ++ DS1, J1, E1, DS2, and E2 Interface Types"; ++ } ++ identity e1 { ++ base iana-interface-type; ++ status obsolete; ++ description ++ "Obsolete; see DS1-MIB."; ++ reference ++ "RFC 4805 - Definitions of Managed Objects for the ++ DS1, J1, E1, DS2, and E2 Interface Types"; ++ } ++ ++ identity basicISDN { ++ base iana-interface-type; ++ description ++ "No longer used. See also RFC 2127."; ++ } ++ identity primaryISDN { ++ base iana-interface-type; ++ description ++ "No longer used. See also RFC 2127."; ++ } ++ identity propPointToPointSerial { ++ base iana-interface-type; ++ description ++ "Proprietary serial."; ++ } ++ identity ppp { ++ base iana-interface-type; ++ } ++ identity softwareLoopback { ++ base iana-interface-type; ++ } ++ identity eon { ++ base iana-interface-type; ++ description ++ "CLNP over IP."; ++ } ++ identity ethernet3Mbit { ++ base iana-interface-type; ++ } ++ identity nsip { ++ base iana-interface-type; ++ description ++ "XNS over IP."; ++ } ++ identity slip { ++ base iana-interface-type; ++ description ++ "Generic SLIP."; ++ } ++ identity ultra { ++ base iana-interface-type; ++ description ++ "Ultra Technologies."; ++ } ++ identity ds3 { ++ base iana-interface-type; ++ description ++ "DS3-MIB."; ++ reference ++ "RFC 3896 - Definitions of Managed Objects for the ++ DS3/E3 Interface Type"; ++ } ++ identity sip { ++ base iana-interface-type; ++ description ++ "SMDS, coffee."; ++ reference ++ "RFC 1694 - Definitions of Managed Objects for SMDS ++ Interfaces using SMIv2"; ++ } ++ identity frameRelay { ++ base iana-interface-type; ++ description ++ "DTE only."; ++ reference ++ "RFC 2115 - Management Information Base for Frame Relay ++ DTEs Using SMIv2"; ++ } ++ identity rs232 { ++ base iana-interface-type; ++ reference ++ "RFC 1659 - Definitions of Managed Objects for RS-232-like ++ Hardware Devices using SMIv2"; ++ } ++ identity para { ++ base iana-interface-type; ++ description ++ "Parallel-port."; ++ reference ++ "RFC 1660 - Definitions of Managed Objects for ++ Parallel-printer-like Hardware Devices using ++ SMIv2"; ++ } ++ identity arcnet { ++ base iana-interface-type; ++ description ++ "ARCnet."; ++ } ++ identity arcnetPlus { ++ base iana-interface-type; ++ description ++ "ARCnet Plus."; ++ } ++ ++ identity atm { ++ base iana-interface-type; ++ description ++ "ATM cells."; ++ } ++ identity miox25 { ++ base iana-interface-type; ++ reference ++ "RFC 1461 - SNMP MIB extension for Multiprotocol ++ Interconnect over X.25"; ++ } ++ identity sonet { ++ base iana-interface-type; ++ description ++ "SONET or SDH."; ++ } ++ identity x25ple { ++ base iana-interface-type; ++ reference ++ "RFC 2127 - ISDN Management Information Base using SMIv2"; ++ } ++ identity iso88022llc { ++ base iana-interface-type; ++ } ++ identity localTalk { ++ base iana-interface-type; ++ } ++ identity smdsDxi { ++ base iana-interface-type; ++ } ++ identity frameRelayService { ++ base iana-interface-type; ++ description ++ "FRNETSERV-MIB."; ++ reference ++ "RFC 2954 - Definitions of Managed Objects for Frame ++ Relay Service"; ++ } ++ identity v35 { ++ base iana-interface-type; ++ } ++ identity hssi { ++ base iana-interface-type; ++ } ++ identity hippi { ++ base iana-interface-type; ++ } ++ ++ identity modem { ++ base iana-interface-type; ++ description ++ "Generic modem."; ++ } ++ identity aal5 { ++ base iana-interface-type; ++ description ++ "AAL5 over ATM."; ++ } ++ identity sonetPath { ++ base iana-interface-type; ++ } ++ identity sonetVT { ++ base iana-interface-type; ++ } ++ identity smdsIcip { ++ base iana-interface-type; ++ description ++ "SMDS InterCarrier Interface."; ++ } ++ identity propVirtual { ++ base iana-interface-type; ++ description ++ "Proprietary virtual/internal."; ++ reference ++ "RFC 2863 - The Interfaces Group MIB"; ++ } ++ identity propMultiplexor { ++ base iana-interface-type; ++ description ++ "Proprietary multiplexing."; ++ reference ++ "RFC 2863 - The Interfaces Group MIB"; ++ } ++ identity ieee80212 { ++ base iana-interface-type; ++ description ++ "100BaseVG."; ++ } ++ identity fibreChannel { ++ base iana-interface-type; ++ description ++ "Fibre Channel."; ++ } ++ ++ identity hippiInterface { ++ base iana-interface-type; ++ description ++ "HIPPI interfaces."; ++ } ++ identity frameRelayInterconnect { ++ base iana-interface-type; ++ status obsolete; ++ description ++ "Obsolete; use either ++ frameRelay(32) or frameRelayService(44)."; ++ } ++ identity aflane8023 { ++ base iana-interface-type; ++ description ++ "ATM Emulated LAN for 802.3."; ++ } ++ identity aflane8025 { ++ base iana-interface-type; ++ description ++ "ATM Emulated LAN for 802.5."; ++ } ++ identity cctEmul { ++ base iana-interface-type; ++ description ++ "ATM Emulated circuit."; ++ } ++ identity fastEther { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Obsoleted via RFC 3635. ++ ethernetCsmacd(6) should be used instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity isdn { ++ base iana-interface-type; ++ description ++ "ISDN and X.25."; ++ reference ++ "RFC 1356 - Multiprotocol Interconnect on X.25 and ISDN ++ in the Packet Mode"; ++ } ++ ++ identity v11 { ++ base iana-interface-type; ++ description ++ "CCITT V.11/X.21."; ++ } ++ identity v36 { ++ base iana-interface-type; ++ description ++ "CCITT V.36."; ++ } ++ identity g703at64k { ++ base iana-interface-type; ++ description ++ "CCITT G703 at 64Kbps."; ++ } ++ identity g703at2mb { ++ base iana-interface-type; ++ status obsolete; ++ description ++ "Obsolete; see DS1-MIB."; ++ } ++ identity qllc { ++ base iana-interface-type; ++ description ++ "SNA QLLC."; ++ } ++ identity fastEtherFX { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Obsoleted via RFC 3635. ++ ethernetCsmacd(6) should be used instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity channel { ++ base iana-interface-type; ++ description ++ "Channel."; ++ } ++ identity ieee80211 { ++ base iana-interface-type; ++ description ++ "Radio spread spectrum."; ++ } ++ identity ibm370parChan { ++ base iana-interface-type; ++ description ++ "IBM System 360/370 OEMI Channel."; ++ } ++ identity escon { ++ base iana-interface-type; ++ description ++ "IBM Enterprise Systems Connection."; ++ } ++ identity dlsw { ++ base iana-interface-type; ++ description ++ "Data Link Switching."; ++ } ++ identity isdns { ++ base iana-interface-type; ++ description ++ "ISDN S/T interface."; ++ } ++ identity isdnu { ++ base iana-interface-type; ++ description ++ "ISDN U interface."; ++ } ++ identity lapd { ++ base iana-interface-type; ++ description ++ "Link Access Protocol D."; ++ } ++ identity ipSwitch { ++ base iana-interface-type; ++ description ++ "IP Switching Objects."; ++ } ++ identity rsrb { ++ base iana-interface-type; ++ description ++ "Remote Source Route Bridging."; ++ } ++ identity atmLogical { ++ base iana-interface-type; ++ description ++ "ATM Logical Port."; ++ reference ++ "RFC 3606 - Definitions of Supplemental Managed Objects ++ for ATM Interface"; ++ } ++ identity ds0 { ++ base iana-interface-type; ++ description ++ "Digital Signal Level 0."; ++ reference ++ "RFC 2494 - Definitions of Managed Objects for the DS0 ++ and DS0 Bundle Interface Type"; ++ } ++ identity ds0Bundle { ++ base iana-interface-type; ++ description ++ "Group of ds0s on the same ds1."; ++ reference ++ "RFC 2494 - Definitions of Managed Objects for the DS0 ++ and DS0 Bundle Interface Type"; ++ } ++ identity bsc { ++ base iana-interface-type; ++ description ++ "Bisynchronous Protocol."; ++ } ++ identity async { ++ base iana-interface-type; ++ description ++ "Asynchronous Protocol."; ++ } ++ identity cnr { ++ base iana-interface-type; ++ description ++ "Combat Net Radio."; ++ } ++ identity iso88025Dtr { ++ base iana-interface-type; ++ description ++ "ISO 802.5r DTR."; ++ } ++ identity eplrs { ++ base iana-interface-type; ++ description ++ "Ext Pos Loc Report Sys."; ++ } ++ identity arap { ++ base iana-interface-type; ++ description ++ "Appletalk Remote Access Protocol."; ++ } ++ identity propCnls { ++ base iana-interface-type; ++ description ++ "Proprietary Connectionless Protocol."; ++ } ++ identity hostPad { ++ base iana-interface-type; ++ description ++ "CCITT-ITU X.29 PAD Protocol."; ++ } ++ identity termPad { ++ base iana-interface-type; ++ description ++ "CCITT-ITU X.3 PAD Facility."; ++ } ++ identity frameRelayMPI { ++ base iana-interface-type; ++ description ++ "Multiproto Interconnect over FR."; ++ } ++ identity x213 { ++ base iana-interface-type; ++ description ++ "CCITT-ITU X213."; ++ } ++ identity adsl { ++ base iana-interface-type; ++ description ++ "Asymmetric Digital Subscriber Loop."; ++ } ++ identity radsl { ++ base iana-interface-type; ++ description ++ "Rate-Adapt. Digital Subscriber Loop."; ++ } ++ identity sdsl { ++ base iana-interface-type; ++ description ++ "Symmetric Digital Subscriber Loop."; ++ } ++ identity vdsl { ++ base iana-interface-type; ++ description ++ "Very H-Speed Digital Subscrib. Loop."; ++ } ++ identity iso88025CRFPInt { ++ base iana-interface-type; ++ description ++ "ISO 802.5 CRFP."; ++ } ++ identity myrinet { ++ base iana-interface-type; ++ description ++ "Myricom Myrinet."; ++ } ++ identity voiceEM { ++ base iana-interface-type; ++ description ++ "Voice recEive and transMit."; ++ } ++ identity voiceFXO { ++ base iana-interface-type; ++ description ++ "Voice Foreign Exchange Office."; ++ } ++ identity voiceFXS { ++ base iana-interface-type; ++ description ++ "Voice Foreign Exchange Station."; ++ } ++ identity voiceEncap { ++ base iana-interface-type; ++ description ++ "Voice encapsulation."; ++ } ++ identity voiceOverIp { ++ base iana-interface-type; ++ description ++ "Voice over IP encapsulation."; ++ } ++ identity atmDxi { ++ base iana-interface-type; ++ description ++ "ATM DXI."; ++ } ++ identity atmFuni { ++ base iana-interface-type; ++ description ++ "ATM FUNI."; ++ } ++ identity atmIma { ++ base iana-interface-type; ++ description ++ "ATM IMA."; ++ } ++ identity pppMultilinkBundle { ++ base iana-interface-type; ++ description ++ "PPP Multilink Bundle."; ++ } ++ identity ipOverCdlc { ++ base iana-interface-type; ++ description ++ "IBM ipOverCdlc."; ++ } ++ identity ipOverClaw { ++ base iana-interface-type; ++ description ++ "IBM Common Link Access to Workstn."; ++ } ++ identity stackToStack { ++ base iana-interface-type; ++ description ++ "IBM stackToStack."; ++ } ++ identity virtualIpAddress { ++ base iana-interface-type; ++ description ++ "IBM VIPA."; ++ } ++ identity mpc { ++ base iana-interface-type; ++ description ++ "IBM multi-protocol channel support."; ++ } ++ identity ipOverAtm { ++ base iana-interface-type; ++ description ++ "IBM ipOverAtm."; ++ reference ++ "RFC 2320 - Definitions of Managed Objects for Classical IP ++ and ARP Over ATM Using SMIv2 (IPOA-MIB)"; ++ } ++ identity iso88025Fiber { ++ base iana-interface-type; ++ description ++ "ISO 802.5j Fiber Token Ring."; ++ } ++ identity tdlc { ++ base iana-interface-type; ++ description ++ "IBM twinaxial data link control."; ++ } ++ identity gigabitEthernet { ++ base iana-interface-type; ++ status deprecated; ++ ++ description ++ "Obsoleted via RFC 3635. ++ ethernetCsmacd(6) should be used instead."; ++ reference ++ "RFC 3635 - Definitions of Managed Objects for the ++ Ethernet-like Interface Types"; ++ } ++ identity hdlc { ++ base iana-interface-type; ++ description ++ "HDLC."; ++ } ++ identity lapf { ++ base iana-interface-type; ++ description ++ "LAP F."; ++ } ++ identity v37 { ++ base iana-interface-type; ++ description ++ "V.37."; ++ } ++ identity x25mlp { ++ base iana-interface-type; ++ description ++ "Multi-Link Protocol."; ++ } ++ identity x25huntGroup { ++ base iana-interface-type; ++ description ++ "X25 Hunt Group."; ++ } ++ identity transpHdlc { ++ base iana-interface-type; ++ description ++ "Transp HDLC."; ++ } ++ identity interleave { ++ base iana-interface-type; ++ description ++ "Interleave channel."; ++ } ++ identity fast { ++ base iana-interface-type; ++ description ++ "Fast channel."; ++ } ++ ++ identity ip { ++ base iana-interface-type; ++ description ++ "IP (for APPN HPR in IP networks)."; ++ } ++ identity docsCableMaclayer { ++ base iana-interface-type; ++ description ++ "CATV Mac Layer."; ++ } ++ identity docsCableDownstream { ++ base iana-interface-type; ++ description ++ "CATV Downstream interface."; ++ } ++ identity docsCableUpstream { ++ base iana-interface-type; ++ description ++ "CATV Upstream interface."; ++ } ++ identity a12MppSwitch { ++ base iana-interface-type; ++ description ++ "Avalon Parallel Processor."; ++ } ++ identity tunnel { ++ base iana-interface-type; ++ description ++ "Encapsulation interface."; ++ } ++ identity coffee { ++ base iana-interface-type; ++ description ++ "Coffee pot."; ++ reference ++ "RFC 2325 - Coffee MIB"; ++ } ++ identity ces { ++ base iana-interface-type; ++ description ++ "Circuit Emulation Service."; ++ } ++ identity atmSubInterface { ++ base iana-interface-type; ++ description ++ "ATM Sub Interface."; ++ } ++ ++ identity l2vlan { ++ base iana-interface-type; ++ description ++ "Layer 2 Virtual LAN using 802.1Q."; ++ } ++ identity l3ipvlan { ++ base iana-interface-type; ++ description ++ "Layer 3 Virtual LAN using IP."; ++ } ++ identity l3ipxvlan { ++ base iana-interface-type; ++ description ++ "Layer 3 Virtual LAN using IPX."; ++ } ++ identity digitalPowerline { ++ base iana-interface-type; ++ description ++ "IP over Power Lines."; ++ } ++ identity mediaMailOverIp { ++ base iana-interface-type; ++ description ++ "Multimedia Mail over IP."; ++ } ++ identity dtm { ++ base iana-interface-type; ++ description ++ "Dynamic synchronous Transfer Mode."; ++ } ++ identity dcn { ++ base iana-interface-type; ++ description ++ "Data Communications Network."; ++ } ++ identity ipForward { ++ base iana-interface-type; ++ description ++ "IP Forwarding Interface."; ++ } ++ identity msdsl { ++ base iana-interface-type; ++ description ++ "Multi-rate Symmetric DSL."; ++ } ++ identity ieee1394 { ++ base iana-interface-type; ++ ++ description ++ "IEEE1394 High Performance Serial Bus."; ++ } ++ identity if-gsn { ++ base iana-interface-type; ++ description ++ "HIPPI-6400."; ++ } ++ identity dvbRccMacLayer { ++ base iana-interface-type; ++ description ++ "DVB-RCC MAC Layer."; ++ } ++ identity dvbRccDownstream { ++ base iana-interface-type; ++ description ++ "DVB-RCC Downstream Channel."; ++ } ++ identity dvbRccUpstream { ++ base iana-interface-type; ++ description ++ "DVB-RCC Upstream Channel."; ++ } ++ identity atmVirtual { ++ base iana-interface-type; ++ description ++ "ATM Virtual Interface."; ++ } ++ identity mplsTunnel { ++ base iana-interface-type; ++ description ++ "MPLS Tunnel Virtual Interface."; ++ } ++ identity srp { ++ base iana-interface-type; ++ description ++ "Spatial Reuse Protocol."; ++ } ++ identity voiceOverAtm { ++ base iana-interface-type; ++ description ++ "Voice over ATM."; ++ } ++ identity voiceOverFrameRelay { ++ base iana-interface-type; ++ description ++ "Voice Over Frame Relay."; ++ } ++ identity idsl { ++ base iana-interface-type; ++ description ++ "Digital Subscriber Loop over ISDN."; ++ } ++ identity compositeLink { ++ base iana-interface-type; ++ description ++ "Avici Composite Link Interface."; ++ } ++ identity ss7SigLink { ++ base iana-interface-type; ++ description ++ "SS7 Signaling Link."; ++ } ++ identity propWirelessP2P { ++ base iana-interface-type; ++ description ++ "Prop. P2P wireless interface."; ++ } ++ identity frForward { ++ base iana-interface-type; ++ description ++ "Frame Forward Interface."; ++ } ++ identity rfc1483 { ++ base iana-interface-type; ++ description ++ "Multiprotocol over ATM AAL5."; ++ reference ++ "RFC 1483 - Multiprotocol Encapsulation over ATM ++ Adaptation Layer 5"; ++ } ++ identity usb { ++ base iana-interface-type; ++ description ++ "USB Interface."; ++ } ++ identity ieee8023adLag { ++ base iana-interface-type; ++ description ++ "IEEE 802.3ad Link Aggregate."; ++ } ++ identity bgppolicyaccounting { ++ base iana-interface-type; ++ description ++ "BGP Policy Accounting."; ++ } ++ identity frf16MfrBundle { ++ base iana-interface-type; ++ description ++ "FRF.16 Multilink Frame Relay."; ++ } ++ identity h323Gatekeeper { ++ base iana-interface-type; ++ description ++ "H323 Gatekeeper."; ++ } ++ identity h323Proxy { ++ base iana-interface-type; ++ description ++ "H323 Voice and Video Proxy."; ++ } ++ identity mpls { ++ base iana-interface-type; ++ description ++ "MPLS."; ++ } ++ identity mfSigLink { ++ base iana-interface-type; ++ description ++ "Multi-frequency signaling link."; ++ } ++ identity hdsl2 { ++ base iana-interface-type; ++ description ++ "High Bit-Rate DSL - 2nd generation."; ++ } ++ identity shdsl { ++ base iana-interface-type; ++ description ++ "Multirate HDSL2."; ++ } ++ identity ds1FDL { ++ base iana-interface-type; ++ description ++ "Facility Data Link (4Kbps) on a DS1."; ++ } ++ identity pos { ++ base iana-interface-type; ++ description ++ "Packet over SONET/SDH Interface."; ++ } ++ ++ identity dvbAsiIn { ++ base iana-interface-type; ++ description ++ "DVB-ASI Input."; ++ } ++ identity dvbAsiOut { ++ base iana-interface-type; ++ description ++ "DVB-ASI Output."; ++ } ++ identity plc { ++ base iana-interface-type; ++ description ++ "Power Line Communications."; ++ } ++ identity nfas { ++ base iana-interface-type; ++ description ++ "Non-Facility Associated Signaling."; ++ } ++ identity tr008 { ++ base iana-interface-type; ++ description ++ "TR008."; ++ } ++ identity gr303RDT { ++ base iana-interface-type; ++ description ++ "Remote Digital Terminal."; ++ } ++ identity gr303IDT { ++ base iana-interface-type; ++ description ++ "Integrated Digital Terminal."; ++ } ++ identity isup { ++ base iana-interface-type; ++ description ++ "ISUP."; ++ } ++ identity propDocsWirelessMaclayer { ++ base iana-interface-type; ++ description ++ "Cisco proprietary Maclayer."; ++ } ++ ++ identity propDocsWirelessDownstream { ++ base iana-interface-type; ++ description ++ "Cisco proprietary Downstream."; ++ } ++ identity propDocsWirelessUpstream { ++ base iana-interface-type; ++ description ++ "Cisco proprietary Upstream."; ++ } ++ identity hiperlan2 { ++ base iana-interface-type; ++ description ++ "HIPERLAN Type 2 Radio Interface."; ++ } ++ identity propBWAp2Mp { ++ base iana-interface-type; ++ description ++ "PropBroadbandWirelessAccesspt2Multipt (use of this value ++ for IEEE 802.16 WMAN interfaces as per IEEE Std 802.16f ++ is deprecated, and ieee80216WMAN(237) should be used ++ instead)."; ++ } ++ identity sonetOverheadChannel { ++ base iana-interface-type; ++ description ++ "SONET Overhead Channel."; ++ } ++ identity digitalWrapperOverheadChannel { ++ base iana-interface-type; ++ description ++ "Digital Wrapper."; ++ } ++ identity aal2 { ++ base iana-interface-type; ++ description ++ "ATM adaptation layer 2."; ++ } ++ identity radioMAC { ++ base iana-interface-type; ++ description ++ "MAC layer over radio links."; ++ } ++ identity atmRadio { ++ base iana-interface-type; ++ description ++ "ATM over radio links."; ++ } ++ identity imt { ++ base iana-interface-type; ++ description ++ "Inter-Machine Trunks."; ++ } ++ identity mvl { ++ base iana-interface-type; ++ description ++ "Multiple Virtual Lines DSL."; ++ } ++ identity reachDSL { ++ base iana-interface-type; ++ description ++ "Long Reach DSL."; ++ } ++ identity frDlciEndPt { ++ base iana-interface-type; ++ description ++ "Frame Relay DLCI End Point."; ++ } ++ identity atmVciEndPt { ++ base iana-interface-type; ++ description ++ "ATM VCI End Point."; ++ } ++ identity opticalChannel { ++ base iana-interface-type; ++ description ++ "Optical Channel."; ++ } ++ identity opticalTransport { ++ base iana-interface-type; ++ description ++ "Optical Transport."; ++ } ++ identity propAtm { ++ base iana-interface-type; ++ description ++ "Proprietary ATM."; ++ } ++ identity voiceOverCable { ++ base iana-interface-type; ++ description ++ "Voice Over Cable Interface."; ++ } ++ ++ identity infiniband { ++ base iana-interface-type; ++ description ++ "Infiniband."; ++ } ++ identity teLink { ++ base iana-interface-type; ++ description ++ "TE Link."; ++ } ++ identity q2931 { ++ base iana-interface-type; ++ description ++ "Q.2931."; ++ } ++ identity virtualTg { ++ base iana-interface-type; ++ description ++ "Virtual Trunk Group."; ++ } ++ identity sipTg { ++ base iana-interface-type; ++ description ++ "SIP Trunk Group."; ++ } ++ identity sipSig { ++ base iana-interface-type; ++ description ++ "SIP Signaling."; ++ } ++ identity docsCableUpstreamChannel { ++ base iana-interface-type; ++ description ++ "CATV Upstream Channel."; ++ } ++ identity econet { ++ base iana-interface-type; ++ description ++ "Acorn Econet."; ++ } ++ identity pon155 { ++ base iana-interface-type; ++ description ++ "FSAN 155Mb Symetrical PON interface."; ++ } ++ ++ identity pon622 { ++ base iana-interface-type; ++ description ++ "FSAN 622Mb Symetrical PON interface."; ++ } ++ identity bridge { ++ base iana-interface-type; ++ description ++ "Transparent bridge interface."; ++ } ++ identity linegroup { ++ base iana-interface-type; ++ description ++ "Interface common to multiple lines."; ++ } ++ identity voiceEMFGD { ++ base iana-interface-type; ++ description ++ "Voice E&M Feature Group D."; ++ } ++ identity voiceFGDEANA { ++ base iana-interface-type; ++ description ++ "Voice FGD Exchange Access North American."; ++ } ++ identity voiceDID { ++ base iana-interface-type; ++ description ++ "Voice Direct Inward Dialing."; ++ } ++ identity mpegTransport { ++ base iana-interface-type; ++ description ++ "MPEG transport interface."; ++ } ++ identity sixToFour { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "6to4 interface (DEPRECATED)."; ++ reference ++ "RFC 4087 - IP Tunnel MIB"; ++ } ++ identity gtp { ++ base iana-interface-type; ++ description ++ "GTP (GPRS Tunneling Protocol)."; ++ } ++ identity pdnEtherLoop1 { ++ base iana-interface-type; ++ description ++ "Paradyne EtherLoop 1."; ++ } ++ identity pdnEtherLoop2 { ++ base iana-interface-type; ++ description ++ "Paradyne EtherLoop 2."; ++ } ++ identity opticalChannelGroup { ++ base iana-interface-type; ++ description ++ "Optical Channel Group."; ++ } ++ identity homepna { ++ base iana-interface-type; ++ description ++ "HomePNA ITU-T G.989."; ++ } ++ identity gfp { ++ base iana-interface-type; ++ description ++ "Generic Framing Procedure (GFP)."; ++ } ++ identity ciscoISLvlan { ++ base iana-interface-type; ++ description ++ "Layer 2 Virtual LAN using Cisco ISL."; ++ } ++ identity actelisMetaLOOP { ++ base iana-interface-type; ++ description ++ "Acteleis proprietary MetaLOOP High Speed Link."; ++ } ++ identity fcipLink { ++ base iana-interface-type; ++ description ++ "FCIP Link."; ++ } ++ identity rpr { ++ base iana-interface-type; ++ description ++ "Resilient Packet Ring Interface Type."; ++ } ++ ++ identity qam { ++ base iana-interface-type; ++ description ++ "RF Qam Interface."; ++ } ++ identity lmp { ++ base iana-interface-type; ++ description ++ "Link Management Protocol."; ++ reference ++ "RFC 4327 - Link Management Protocol (LMP) Management ++ Information Base (MIB)"; ++ } ++ identity cblVectaStar { ++ base iana-interface-type; ++ description ++ "Cambridge Broadband Networks Limited VectaStar."; ++ } ++ identity docsCableMCmtsDownstream { ++ base iana-interface-type; ++ description ++ "CATV Modular CMTS Downstream Interface."; ++ } ++ identity adsl2 { ++ base iana-interface-type; ++ status deprecated; ++ description ++ "Asymmetric Digital Subscriber Loop Version 2 ++ (DEPRECATED/OBSOLETED - please use adsl2plus(238) ++ instead)."; ++ reference ++ "RFC 4706 - Definitions of Managed Objects for Asymmetric ++ Digital Subscriber Line 2 (ADSL2)"; ++ } ++ identity macSecControlledIF { ++ base iana-interface-type; ++ description ++ "MACSecControlled."; ++ } ++ identity macSecUncontrolledIF { ++ base iana-interface-type; ++ description ++ "MACSecUncontrolled."; ++ } ++ identity aviciOpticalEther { ++ base iana-interface-type; ++ description ++ "Avici Optical Ethernet Aggregate."; ++ } ++ identity atmbond { ++ base iana-interface-type; ++ description ++ "atmbond."; ++ } ++ identity voiceFGDOS { ++ base iana-interface-type; ++ description ++ "Voice FGD Operator Services."; ++ } ++ identity mocaVersion1 { ++ base iana-interface-type; ++ description ++ "MultiMedia over Coax Alliance (MoCA) Interface ++ as documented in information provided privately to IANA."; ++ } ++ identity ieee80216WMAN { ++ base iana-interface-type; ++ description ++ "IEEE 802.16 WMAN interface."; ++ } ++ identity adsl2plus { ++ base iana-interface-type; ++ description ++ "Asymmetric Digital Subscriber Loop Version 2 - ++ Version 2 Plus and all variants."; ++ } ++ identity dvbRcsMacLayer { ++ base iana-interface-type; ++ description ++ "DVB-RCS MAC Layer."; ++ reference ++ "RFC 5728 - The SatLabs Group DVB-RCS MIB"; ++ } ++ identity dvbTdm { ++ base iana-interface-type; ++ description ++ "DVB Satellite TDM."; ++ reference ++ "RFC 5728 - The SatLabs Group DVB-RCS MIB"; ++ } ++ identity dvbRcsTdma { ++ base iana-interface-type; ++ description ++ "DVB-RCS TDMA."; ++ reference ++ "RFC 5728 - The SatLabs Group DVB-RCS MIB"; ++ } ++ identity x86Laps { ++ base iana-interface-type; ++ description ++ "LAPS based on ITU-T X.86/Y.1323."; ++ } ++ identity wwanPP { ++ base iana-interface-type; ++ description ++ "3GPP WWAN."; ++ } ++ identity wwanPP2 { ++ base iana-interface-type; ++ description ++ "3GPP2 WWAN."; ++ } ++ identity voiceEBS { ++ base iana-interface-type; ++ description ++ "Voice P-phone EBS physical interface."; ++ } ++ identity ifPwType { ++ base iana-interface-type; ++ description ++ "Pseudowire interface type."; ++ reference ++ "RFC 5601 - Pseudowire (PW) Management Information Base (MIB)"; ++ } ++ identity ilan { ++ base iana-interface-type; ++ description ++ "Internal LAN on a bridge per IEEE 802.1ap."; ++ } ++ identity pip { ++ base iana-interface-type; ++ description ++ "Provider Instance Port on a bridge per IEEE 802.1ah PBB."; ++ } ++ identity aluELP { ++ base iana-interface-type; ++ description ++ "Alcatel-Lucent Ethernet Link Protection."; ++ } ++ identity gpon { ++ base iana-interface-type; ++ description ++ "Gigabit-capable passive optical networks (G-PON) as per ++ ITU-T G.948."; ++ } ++ identity vdsl2 { ++ base iana-interface-type; ++ description ++ "Very high speed digital subscriber line Version 2 ++ (as per ITU-T Recommendation G.993.2)."; ++ reference ++ "RFC 5650 - Definitions of Managed Objects for Very High ++ Speed Digital Subscriber Line 2 (VDSL2)"; ++ } ++ identity capwapDot11Profile { ++ base iana-interface-type; ++ description ++ "WLAN Profile Interface."; ++ reference ++ "RFC 5834 - Control and Provisioning of Wireless Access ++ Points (CAPWAP) Protocol Binding MIB for ++ IEEE 802.11"; ++ } ++ identity capwapDot11Bss { ++ base iana-interface-type; ++ description ++ "WLAN BSS Interface."; ++ reference ++ "RFC 5834 - Control and Provisioning of Wireless Access ++ Points (CAPWAP) Protocol Binding MIB for ++ IEEE 802.11"; ++ } ++ identity capwapWtpVirtualRadio { ++ base iana-interface-type; ++ description ++ "WTP Virtual Radio Interface."; ++ reference ++ "RFC 5833 - Control and Provisioning of Wireless Access ++ Points (CAPWAP) Protocol Base MIB"; ++ } ++ identity bits { ++ base iana-interface-type; ++ description ++ "bitsport."; ++ } ++ identity docsCableUpstreamRfPort { ++ base iana-interface-type; ++ description ++ "DOCSIS CATV Upstream RF Port."; ++ } ++ ++ identity cableDownstreamRfPort { ++ base iana-interface-type; ++ description ++ "CATV downstream RF Port."; ++ } ++ identity vmwareVirtualNic { ++ base iana-interface-type; ++ description ++ "VMware Virtual Network Interface."; ++ } ++ identity ieee802154 { ++ base iana-interface-type; ++ description ++ "IEEE 802.15.4 WPAN interface."; ++ reference ++ "IEEE 802.15.4-2006"; ++ } ++ identity otnOdu { ++ base iana-interface-type; ++ description ++ "OTN Optical Data Unit."; ++ } ++ identity otnOtu { ++ base iana-interface-type; ++ description ++ "OTN Optical channel Transport Unit."; ++ } ++ identity ifVfiType { ++ base iana-interface-type; ++ description ++ "VPLS Forwarding Instance Interface Type."; ++ } ++ identity g9981 { ++ base iana-interface-type; ++ description ++ "G.998.1 bonded interface."; ++ } ++ identity g9982 { ++ base iana-interface-type; ++ description ++ "G.998.2 bonded interface."; ++ } ++ identity g9983 { ++ base iana-interface-type; ++ description ++ "G.998.3 bonded interface."; ++ } ++ ++ identity aluEpon { ++ base iana-interface-type; ++ description ++ "Ethernet Passive Optical Networks (E-PON)."; ++ } ++ identity aluEponOnu { ++ base iana-interface-type; ++ description ++ "EPON Optical Network Unit."; ++ } ++ identity aluEponPhysicalUni { ++ base iana-interface-type; ++ description ++ "EPON physical User to Network interface."; ++ } ++ identity aluEponLogicalLink { ++ base iana-interface-type; ++ description ++ "The emulation of a point-to-point link over the EPON ++ layer."; ++ } ++ identity aluGponOnu { ++ base iana-interface-type; ++ description ++ "GPON Optical Network Unit."; ++ reference ++ "ITU-T G.984.2"; ++ } ++ identity aluGponPhysicalUni { ++ base iana-interface-type; ++ description ++ "GPON physical User to Network interface."; ++ reference ++ "ITU-T G.984.2"; ++ } ++ identity vmwareNicTeam { ++ base iana-interface-type; ++ description ++ "VMware NIC Team."; ++ } ++} +diff --git a/tests/yang/ietf-interfaces@2018-02-20.yang b/tests/yang/ietf-interfaces@2018-02-20.yang +new file mode 100644 +index 0000000..f66c205 +--- /dev/null ++++ b/tests/yang/ietf-interfaces@2018-02-20.yang +@@ -0,0 +1,1123 @@ ++module ietf-interfaces { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; ++ prefix if; ++ ++ import ietf-yang-types { ++ prefix yang; ++ } ++ ++ organization ++ "IETF NETMOD (Network Modeling) Working Group"; ++ ++ contact ++ "WG Web: ++ WG List: ++ ++ Editor: Martin Bjorklund ++ "; ++ ++ description ++ "This module contains a collection of YANG definitions for ++ managing network interfaces. ++ ++ Copyright (c) 2018 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8343; see ++ the RFC itself for full legal notices."; ++ ++ revision 2018-02-20 { ++ description ++ "Updated to support NMDA."; ++ reference ++ "RFC 8343: A YANG Data Model for Interface Management"; ++ } ++ ++ revision 2014-05-08 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 7223: A YANG Data Model for Interface Management"; ++ } ++ ++ /* ++ * Typedefs ++ */ ++ ++ typedef interface-ref { ++ type leafref { ++ path "/if:interfaces/if:interface/if:name"; ++ } ++ description ++ "This type is used by data models that need to reference ++ interfaces."; ++ } ++ ++ /* ++ * Identities ++ */ ++ ++ identity interface-type { ++ description ++ "Base identity from which specific interface types are ++ derived."; ++ } ++ ++ /* ++ * Features ++ */ ++ ++ feature arbitrary-names { ++ description ++ "This feature indicates that the device allows user-controlled ++ interfaces to be named arbitrarily."; ++ } ++ feature pre-provisioning { ++ description ++ "This feature indicates that the device supports ++ pre-provisioning of interface configuration, i.e., it is ++ possible to configure an interface whose physical interface ++ hardware is not present on the device."; ++ } ++ feature if-mib { ++ description ++ "This feature indicates that the device implements ++ the IF-MIB."; ++ reference ++ "RFC 2863: The Interfaces Group MIB"; ++ } ++ ++ /* ++ * Data nodes ++ */ ++ ++ container interfaces { ++ description ++ "Interface parameters."; ++ ++ list interface { ++ key "name"; ++ ++ description ++ "The list of interfaces on the device. ++ ++ The status of an interface is available in this list in the ++ operational state. If the configuration of a ++ system-controlled interface cannot be used by the system ++ (e.g., the interface hardware present does not match the ++ interface type), then the configuration is not applied to ++ the system-controlled interface shown in the operational ++ state. If the configuration of a user-controlled interface ++ cannot be used by the system, the configured interface is ++ not instantiated in the operational state. ++ ++ System-controlled interfaces created by the system are ++ always present in this list in the operational state, ++ whether or not they are configured."; ++ ++ leaf name { ++ type string; ++ description ++ "The name of the interface. ++ ++ A device MAY restrict the allowed values for this leaf, ++ possibly depending on the type of the interface. ++ For system-controlled interfaces, this leaf is the ++ device-specific name of the interface. ++ ++ If a client tries to create configuration for a ++ system-controlled interface that is not present in the ++ operational state, the server MAY reject the request if ++ the implementation does not support pre-provisioning of ++ interfaces or if the name refers to an interface that can ++ never exist in the system. A Network Configuration ++ Protocol (NETCONF) server MUST reply with an rpc-error ++ with the error-tag 'invalid-value' in this case. ++ ++ If the device supports pre-provisioning of interface ++ configuration, the 'pre-provisioning' feature is ++ advertised. ++ ++ If the device allows arbitrarily named user-controlled ++ interfaces, the 'arbitrary-names' feature is advertised. ++ ++ When a configured user-controlled interface is created by ++ the system, it is instantiated with the same name in the ++ operational state. ++ ++ A server implementation MAY map this leaf to the ifName ++ MIB object. Such an implementation needs to use some ++ mechanism to handle the differences in size and characters ++ allowed between this leaf and ifName. The definition of ++ such a mechanism is outside the scope of this document."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifName"; ++ } ++ ++ leaf description { ++ type string; ++ description ++ "A textual description of the interface. ++ ++ A server implementation MAY map this leaf to the ifAlias ++ MIB object. Such an implementation needs to use some ++ mechanism to handle the differences in size and characters ++ allowed between this leaf and ifAlias. The definition of ++ such a mechanism is outside the scope of this document. ++ ++ Since ifAlias is defined to be stored in non-volatile ++ storage, the MIB implementation MUST map ifAlias to the ++ value of 'description' in the persistently stored ++ configuration."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAlias"; ++ } ++ ++ leaf type { ++ type identityref { ++ base interface-type; ++ } ++ mandatory true; ++ description ++ "The type of the interface. ++ ++ When an interface entry is created, a server MAY ++ initialize the type leaf with a valid value, e.g., if it ++ is possible to derive the type from the name of the ++ interface. ++ ++ If a client tries to set the type of an interface to a ++ value that can never be used by the system, e.g., if the ++ type is not supported or if the type does not match the ++ name of the interface, the server MUST reject the request. ++ A NETCONF server MUST reply with an rpc-error with the ++ error-tag 'invalid-value' in this case."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifType"; ++ } ++ ++ leaf enabled { ++ type boolean; ++ default "true"; ++ description ++ "This leaf contains the configured, desired state of the ++ interface. ++ ++ Systems that implement the IF-MIB use the value of this ++ leaf in the intended configuration to set ++ IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry ++ has been initialized, as described in RFC 2863. ++ ++ Changes in this leaf in the intended configuration are ++ reflected in ifAdminStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; ++ } ++ ++ leaf link-up-down-trap-enable { ++ if-feature if-mib; ++ type enumeration { ++ enum enabled { ++ value 1; ++ description ++ "The device will generate linkUp/linkDown SNMP ++ notifications for this interface."; ++ } ++ enum disabled { ++ value 2; ++ description ++ "The device will not generate linkUp/linkDown SNMP ++ notifications for this interface."; ++ } ++ } ++ description ++ "Controls whether linkUp/linkDown SNMP notifications ++ should be generated for this interface. ++ ++ If this node is not configured, the value 'enabled' is ++ operationally used by the server for interfaces that do ++ not operate on top of any other interface (i.e., there are ++ no 'lower-layer-if' entries), and 'disabled' otherwise."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifLinkUpDownTrapEnable"; ++ } ++ ++ leaf admin-status { ++ if-feature if-mib; ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ description ++ "Not ready to pass packets and not in some test mode."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode."; ++ } ++ } ++ config false; ++ mandatory true; ++ description ++ "The desired state of the interface. ++ ++ This leaf has the same read semantics as ifAdminStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; ++ } ++ ++ leaf oper-status { ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ ++ description ++ "The interface does not pass any packets."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode. No operational packets can ++ be passed."; ++ } ++ enum unknown { ++ value 4; ++ description ++ "Status cannot be determined for some reason."; ++ } ++ enum dormant { ++ value 5; ++ description ++ "Waiting for some external event."; ++ } ++ enum not-present { ++ value 6; ++ description ++ "Some component (typically hardware) is missing."; ++ } ++ enum lower-layer-down { ++ value 7; ++ description ++ "Down due to state of lower-layer interface(s)."; ++ } ++ } ++ config false; ++ mandatory true; ++ description ++ "The current operational state of the interface. ++ ++ This leaf has the same semantics as ifOperStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOperStatus"; ++ } ++ ++ leaf last-change { ++ type yang:date-and-time; ++ config false; ++ description ++ "The time the interface entered its current operational ++ state. If the current state was entered prior to the ++ last re-initialization of the local network management ++ subsystem, then this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifLastChange"; ++ } ++ ++ leaf if-index { ++ if-feature if-mib; ++ type int32 { ++ range "1..2147483647"; ++ } ++ config false; ++ mandatory true; ++ description ++ "The ifIndex value for the ifEntry represented by this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifIndex"; ++ } ++ ++ leaf phys-address { ++ type yang:phys-address; ++ config false; ++ description ++ "The interface's address at its protocol sub-layer. For ++ example, for an 802.x interface, this object normally ++ contains a Media Access Control (MAC) address. The ++ interface's media-specific modules must define the bit ++ and byte ordering and the format of the value of this ++ object. For interfaces that do not have such an address ++ (e.g., a serial line), this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; ++ } ++ ++ leaf-list higher-layer-if { ++ type interface-ref; ++ config false; ++ description ++ "A list of references to interfaces layered on top of this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf-list lower-layer-if { ++ type interface-ref; ++ config false; ++ ++ description ++ "A list of references to interfaces layered underneath this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf speed { ++ type yang:gauge64; ++ units "bits/second"; ++ config false; ++ description ++ "An estimate of the interface's current bandwidth in bits ++ per second. For interfaces that do not vary in ++ bandwidth or for those where no accurate estimation can ++ be made, this node should contain the nominal bandwidth. ++ For interfaces that have no concept of bandwidth, this ++ node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifSpeed, ifHighSpeed"; ++ } ++ ++ container statistics { ++ config false; ++ description ++ "A collection of interface-related statistics objects."; ++ ++ leaf discontinuity-time { ++ type yang:date-and-time; ++ mandatory true; ++ description ++ "The time on the most recent occasion at which any one or ++ more of this interface's counters suffered a ++ discontinuity. If no such discontinuities have occurred ++ since the last re-initialization of the local management ++ subsystem, then this node contains the time the local ++ management subsystem re-initialized itself."; ++ } ++ ++ leaf in-octets { ++ type yang:counter64; ++ description ++ "The total number of octets received on the interface, ++ including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; ++ } ++ ++ leaf in-unicast-pkts { ++ type yang:counter64; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were not addressed to a ++ multicast or broadcast address at this sub-layer. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; ++ } ++ ++ leaf in-broadcast-pkts { ++ type yang:counter64; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a broadcast ++ address at this sub-layer. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInBroadcastPkts"; ++ } ++ ++ leaf in-multicast-pkts { ++ type yang:counter64; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a multicast ++ address at this sub-layer. For a MAC-layer protocol, ++ this includes both Group and Functional addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInMulticastPkts"; ++ } ++ ++ leaf in-discards { ++ type yang:counter32; ++ description ++ "The number of inbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being deliverable to a higher-layer ++ protocol. One possible reason for discarding such a ++ packet could be to free up buffer space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInDiscards"; ++ } ++ ++ leaf in-errors { ++ type yang:counter32; ++ description ++ "For packet-oriented interfaces, the number of inbound ++ packets that contained errors preventing them from being ++ deliverable to a higher-layer protocol. For character- ++ oriented or fixed-length interfaces, the number of ++ inbound transmission units that contained errors ++ preventing them from being deliverable to a higher-layer ++ protocol. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInErrors"; ++ } ++ ++ leaf in-unknown-protos { ++ type yang:counter32; ++ ++ description ++ "For packet-oriented interfaces, the number of packets ++ received via the interface that were discarded because ++ of an unknown or unsupported protocol. For ++ character-oriented or fixed-length interfaces that ++ support protocol multiplexing, the number of ++ transmission units received via the interface that were ++ discarded because of an unknown or unsupported protocol. ++ For any interface that does not support protocol ++ multiplexing, this counter is not present. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; ++ } ++ ++ leaf out-octets { ++ type yang:counter64; ++ description ++ "The total number of octets transmitted out of the ++ interface, including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; ++ } ++ ++ leaf out-unicast-pkts { ++ type yang:counter64; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were not addressed ++ to a multicast or broadcast address at this sub-layer, ++ including those that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; ++ } ++ ++ leaf out-broadcast-pkts { ++ type yang:counter64; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ broadcast address at this sub-layer, including those ++ that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutBroadcastPkts"; ++ } ++ ++ leaf out-multicast-pkts { ++ type yang:counter64; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ multicast address at this sub-layer, including those ++ that were discarded or not sent. For a MAC-layer ++ protocol, this includes both Group and Functional ++ addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutMulticastPkts"; ++ } ++ ++ leaf out-discards { ++ type yang:counter32; ++ description ++ "The number of outbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being transmitted. One possible reason ++ for discarding such a packet could be to free up buffer ++ space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; ++ } ++ ++ leaf out-errors { ++ type yang:counter32; ++ description ++ "For packet-oriented interfaces, the number of outbound ++ packets that could not be transmitted because of errors. ++ For character-oriented or fixed-length interfaces, the ++ number of outbound transmission units that could not be ++ transmitted because of errors. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutErrors"; ++ } ++ } ++ ++ } ++ } ++ ++ /* ++ * Legacy typedefs ++ */ ++ ++ typedef interface-state-ref { ++ type leafref { ++ path "/if:interfaces-state/if:interface/if:name"; ++ } ++ status deprecated; ++ description ++ "This type is used by data models that need to reference ++ the operationally present interfaces."; ++ } ++ ++ /* ++ * Legacy operational state data nodes ++ */ ++ ++ container interfaces-state { ++ config false; ++ status deprecated; ++ description ++ "Data nodes for the operational state of interfaces."; ++ ++ list interface { ++ key "name"; ++ status deprecated; ++ ++ description ++ "The list of interfaces on the device. ++ ++ System-controlled interfaces created by the system are ++ always present in this list, whether or not they are ++ configured."; ++ ++ leaf name { ++ type string; ++ status deprecated; ++ description ++ "The name of the interface. ++ ++ A server implementation MAY map this leaf to the ifName ++ MIB object. Such an implementation needs to use some ++ mechanism to handle the differences in size and characters ++ allowed between this leaf and ifName. The definition of ++ such a mechanism is outside the scope of this document."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifName"; ++ } ++ ++ leaf type { ++ type identityref { ++ base interface-type; ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The type of the interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifType"; ++ } ++ ++ leaf admin-status { ++ if-feature if-mib; ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ description ++ "Not ready to pass packets and not in some test mode."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode."; ++ } ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The desired state of the interface. ++ ++ This leaf has the same read semantics as ifAdminStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; ++ } ++ ++ leaf oper-status { ++ type enumeration { ++ enum up { ++ value 1; ++ description ++ "Ready to pass packets."; ++ } ++ enum down { ++ value 2; ++ description ++ "The interface does not pass any packets."; ++ } ++ enum testing { ++ value 3; ++ description ++ "In some test mode. No operational packets can ++ be passed."; ++ } ++ enum unknown { ++ value 4; ++ description ++ "Status cannot be determined for some reason."; ++ } ++ enum dormant { ++ value 5; ++ description ++ "Waiting for some external event."; ++ } ++ enum not-present { ++ value 6; ++ description ++ "Some component (typically hardware) is missing."; ++ } ++ enum lower-layer-down { ++ value 7; ++ description ++ "Down due to state of lower-layer interface(s)."; ++ } ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The current operational state of the interface. ++ ++ This leaf has the same semantics as ifOperStatus."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOperStatus"; ++ } ++ ++ leaf last-change { ++ type yang:date-and-time; ++ status deprecated; ++ description ++ "The time the interface entered its current operational ++ state. If the current state was entered prior to the ++ last re-initialization of the local network management ++ subsystem, then this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifLastChange"; ++ } ++ ++ leaf if-index { ++ if-feature if-mib; ++ type int32 { ++ range "1..2147483647"; ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The ifIndex value for the ifEntry represented by this ++ interface."; ++ ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifIndex"; ++ } ++ ++ leaf phys-address { ++ type yang:phys-address; ++ status deprecated; ++ description ++ "The interface's address at its protocol sub-layer. For ++ example, for an 802.x interface, this object normally ++ contains a Media Access Control (MAC) address. The ++ interface's media-specific modules must define the bit ++ and byte ordering and the format of the value of this ++ object. For interfaces that do not have such an address ++ (e.g., a serial line), this node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; ++ } ++ ++ leaf-list higher-layer-if { ++ type interface-state-ref; ++ status deprecated; ++ description ++ "A list of references to interfaces layered on top of this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf-list lower-layer-if { ++ type interface-state-ref; ++ status deprecated; ++ description ++ "A list of references to interfaces layered underneath this ++ interface."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifStackTable"; ++ } ++ ++ leaf speed { ++ type yang:gauge64; ++ units "bits/second"; ++ status deprecated; ++ description ++ "An estimate of the interface's current bandwidth in bits ++ per second. For interfaces that do not vary in ++ bandwidth or for those where no accurate estimation can ++ ++ be made, this node should contain the nominal bandwidth. ++ For interfaces that have no concept of bandwidth, this ++ node is not present."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifSpeed, ifHighSpeed"; ++ } ++ ++ container statistics { ++ status deprecated; ++ description ++ "A collection of interface-related statistics objects."; ++ ++ leaf discontinuity-time { ++ type yang:date-and-time; ++ mandatory true; ++ status deprecated; ++ description ++ "The time on the most recent occasion at which any one or ++ more of this interface's counters suffered a ++ discontinuity. If no such discontinuities have occurred ++ since the last re-initialization of the local management ++ subsystem, then this node contains the time the local ++ management subsystem re-initialized itself."; ++ } ++ ++ leaf in-octets { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of octets received on the interface, ++ including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; ++ } ++ ++ leaf in-unicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were not addressed to a ++ multicast or broadcast address at this sub-layer. ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; ++ } ++ ++ leaf in-broadcast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a broadcast ++ address at this sub-layer. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInBroadcastPkts"; ++ } ++ ++ leaf in-multicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The number of packets, delivered by this sub-layer to a ++ higher (sub-)layer, that were addressed to a multicast ++ address at this sub-layer. For a MAC-layer protocol, ++ this includes both Group and Functional addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCInMulticastPkts"; ++ } ++ ++ leaf in-discards { ++ type yang:counter32; ++ status deprecated; ++ ++ description ++ "The number of inbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being deliverable to a higher-layer ++ protocol. One possible reason for discarding such a ++ packet could be to free up buffer space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInDiscards"; ++ } ++ ++ leaf in-errors { ++ type yang:counter32; ++ status deprecated; ++ description ++ "For packet-oriented interfaces, the number of inbound ++ packets that contained errors preventing them from being ++ deliverable to a higher-layer protocol. For character- ++ oriented or fixed-length interfaces, the number of ++ inbound transmission units that contained errors ++ preventing them from being deliverable to a higher-layer ++ protocol. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInErrors"; ++ } ++ ++ leaf in-unknown-protos { ++ type yang:counter32; ++ status deprecated; ++ description ++ "For packet-oriented interfaces, the number of packets ++ received via the interface that were discarded because ++ of an unknown or unsupported protocol. For ++ character-oriented or fixed-length interfaces that ++ support protocol multiplexing, the number of ++ transmission units received via the interface that were ++ discarded because of an unknown or unsupported protocol. ++ For any interface that does not support protocol ++ multiplexing, this counter is not present. ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; ++ } ++ ++ leaf out-octets { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of octets transmitted out of the ++ interface, including framing characters. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; ++ } ++ ++ leaf out-unicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were not addressed ++ to a multicast or broadcast address at this sub-layer, ++ including those that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; ++ } ++ ++ leaf out-broadcast-pkts { ++ type yang:counter64; ++ status deprecated; ++ ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ broadcast address at this sub-layer, including those ++ that were discarded or not sent. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutBroadcastPkts"; ++ } ++ ++ leaf out-multicast-pkts { ++ type yang:counter64; ++ status deprecated; ++ description ++ "The total number of packets that higher-level protocols ++ requested be transmitted and that were addressed to a ++ multicast address at this sub-layer, including those ++ that were discarded or not sent. For a MAC-layer ++ protocol, this includes both Group and Functional ++ addresses. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ++ ifHCOutMulticastPkts"; ++ } ++ ++ leaf out-discards { ++ type yang:counter32; ++ status deprecated; ++ description ++ "The number of outbound packets that were chosen to be ++ discarded even though no errors had been detected to ++ prevent their being transmitted. One possible reason ++ for discarding such a packet could be to free up buffer ++ space. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; ++ } ++ ++ leaf out-errors { ++ type yang:counter32; ++ status deprecated; ++ description ++ "For packet-oriented interfaces, the number of outbound ++ packets that could not be transmitted because of errors. ++ For character-oriented or fixed-length interfaces, the ++ number of outbound transmission units that could not be ++ transmitted because of errors. ++ ++ Discontinuities in the value of this counter can occur ++ at re-initialization of the management system and at ++ other times as indicated by the value of ++ 'discontinuity-time'."; ++ reference ++ "RFC 2863: The Interfaces Group MIB - ifOutErrors"; ++ } ++ } ++ } ++ } ++} +diff --git a/tests/yang/ietf-ip@2018-02-22.yang b/tests/yang/ietf-ip@2018-02-22.yang +new file mode 100644 +index 0000000..a270f67 +--- /dev/null ++++ b/tests/yang/ietf-ip@2018-02-22.yang +@@ -0,0 +1,876 @@ ++module ietf-ip { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-ip"; ++ prefix ip; ++ ++ import ietf-interfaces { ++ prefix if; ++ } ++ import ietf-inet-types { ++ prefix inet; ++ } ++ import ietf-yang-types { ++ prefix yang; ++ } ++ ++ organization ++ "IETF NETMOD (Network Modeling) Working Group"; ++ ++ contact ++ "WG Web: ++ WG List: ++ ++ Editor: Martin Bjorklund ++ "; ++ description ++ "This module contains a collection of YANG definitions for ++ managing IP implementations. ++ ++ Copyright (c) 2018 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8344; see ++ the RFC itself for full legal notices."; ++ ++ revision 2018-02-22 { ++ description ++ "Updated to support NMDA."; ++ reference ++ "RFC 8344: A YANG Data Model for IP Management"; ++ } ++ ++ revision 2014-06-16 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 7277: A YANG Data Model for IP Management"; ++ } ++ ++ /* ++ * Features ++ */ ++ ++ feature ipv4-non-contiguous-netmasks { ++ description ++ "Indicates support for configuring non-contiguous ++ subnet masks."; ++ } ++ ++ feature ipv6-privacy-autoconf { ++ description ++ "Indicates support for privacy extensions for stateless address ++ autoconfiguration in IPv6."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6"; ++ } ++ ++ /* ++ * Typedefs ++ */ ++ ++ typedef ip-address-origin { ++ type enumeration { ++ enum other { ++ description ++ "None of the following."; ++ } ++ ++ enum static { ++ description ++ "Indicates that the address has been statically ++ configured -- for example, using the Network Configuration ++ Protocol (NETCONF) or a command line interface."; ++ } ++ enum dhcp { ++ description ++ "Indicates an address that has been assigned to this ++ system by a DHCP server."; ++ } ++ enum link-layer { ++ description ++ "Indicates an address created by IPv6 stateless ++ autoconfiguration that embeds a link-layer address in its ++ interface identifier."; ++ } ++ enum random { ++ description ++ "Indicates an address chosen by the system at ++ random, e.g., an IPv4 address within 169.254/16, a ++ temporary address as described in RFC 4941, or a ++ semantically opaque address as described in RFC 7217."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6 ++ RFC 7217: A Method for Generating Semantically Opaque ++ Interface Identifiers with IPv6 Stateless ++ Address Autoconfiguration (SLAAC)"; ++ } ++ } ++ description ++ "The origin of an address."; ++ } ++ ++ typedef neighbor-origin { ++ type enumeration { ++ enum other { ++ description ++ "None of the following."; ++ } ++ enum static { ++ description ++ "Indicates that the mapping has been statically ++ configured -- for example, using NETCONF or a command line ++ interface."; ++ } ++ ++ enum dynamic { ++ description ++ "Indicates that the mapping has been dynamically resolved ++ using, for example, IPv4 ARP or the IPv6 Neighbor ++ Discovery protocol."; ++ } ++ } ++ description ++ "The origin of a neighbor entry."; ++ } ++ ++ /* ++ * Data nodes ++ */ ++ ++ augment "/if:interfaces/if:interface" { ++ description ++ "IP parameters on interfaces. ++ ++ If an interface is not capable of running IP, the server ++ must not allow the client to configure these parameters."; ++ ++ container ipv4 { ++ presence ++ "Enables IPv4 unless the 'enabled' leaf ++ (which defaults to 'true') is set to 'false'"; ++ description ++ "Parameters for the IPv4 address family."; ++ ++ leaf enabled { ++ type boolean; ++ default true; ++ description ++ "Controls whether IPv4 is enabled or disabled on this ++ interface. When IPv4 is enabled, this interface is ++ connected to an IPv4 stack, and the interface can send ++ and receive IPv4 packets."; ++ } ++ leaf forwarding { ++ type boolean; ++ default false; ++ description ++ "Controls IPv4 packet forwarding of datagrams received by, ++ but not addressed to, this interface. IPv4 routers ++ forward datagrams. IPv4 hosts do not (except those ++ source-routed via the host)."; ++ } ++ ++ leaf mtu { ++ type uint16 { ++ range "68..max"; ++ } ++ units "octets"; ++ description ++ "The size, in octets, of the largest IPv4 packet that the ++ interface will send and receive. ++ ++ The server may restrict the allowed values for this leaf, ++ depending on the interface's type. ++ ++ If this leaf is not configured, the operationally used MTU ++ depends on the interface's type."; ++ reference ++ "RFC 791: Internet Protocol"; ++ } ++ list address { ++ key "ip"; ++ description ++ "The list of IPv4 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ description ++ "The IPv4 address on the interface."; ++ } ++ choice subnet { ++ mandatory true; ++ description ++ "The subnet can be specified as a prefix length or, ++ if the server supports non-contiguous netmasks, as ++ a netmask."; ++ leaf prefix-length { ++ type uint8 { ++ range "0..32"; ++ } ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf netmask { ++ if-feature ipv4-non-contiguous-netmasks; ++ type yang:dotted-quad; ++ description ++ "The subnet specified as a netmask."; ++ } ++ } ++ ++ leaf origin { ++ type ip-address-origin; ++ config false; ++ description ++ "The origin of this address."; ++ } ++ } ++ list neighbor { ++ key "ip"; ++ description ++ "A list of mappings from IPv4 addresses to ++ link-layer addresses. ++ ++ Entries in this list in the intended configuration are ++ used as static entries in the ARP Cache. ++ ++ In the operational state, this list represents the ARP ++ Cache."; ++ reference ++ "RFC 826: An Ethernet Address Resolution Protocol"; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ description ++ "The IPv4 address of the neighbor node."; ++ } ++ leaf link-layer-address { ++ type yang:phys-address; ++ mandatory true; ++ description ++ "The link-layer address of the neighbor node."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ config false; ++ description ++ "The origin of this neighbor entry."; ++ } ++ } ++ } ++ ++ container ipv6 { ++ presence ++ "Enables IPv6 unless the 'enabled' leaf ++ (which defaults to 'true') is set to 'false'"; ++ description ++ "Parameters for the IPv6 address family."; ++ ++ leaf enabled { ++ type boolean; ++ default true; ++ description ++ "Controls whether IPv6 is enabled or disabled on this ++ interface. When IPv6 is enabled, this interface is ++ connected to an IPv6 stack, and the interface can send ++ and receive IPv6 packets."; ++ } ++ leaf forwarding { ++ type boolean; ++ default false; ++ description ++ "Controls IPv6 packet forwarding of datagrams received by, ++ but not addressed to, this interface. IPv6 routers ++ forward datagrams. IPv6 hosts do not (except those ++ source-routed via the host)."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 6.2.1, IsRouter"; ++ } ++ leaf mtu { ++ type uint32 { ++ range "1280..max"; ++ } ++ units "octets"; ++ description ++ "The size, in octets, of the largest IPv6 packet that the ++ interface will send and receive. ++ ++ The server may restrict the allowed values for this leaf, ++ depending on the interface's type. ++ ++ If this leaf is not configured, the operationally used MTU ++ depends on the interface's type."; ++ reference ++ "RFC 8200: Internet Protocol, Version 6 (IPv6) ++ Specification ++ Section 5"; ++ } ++ ++ list address { ++ key "ip"; ++ description ++ "The list of IPv6 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ description ++ "The IPv6 address on the interface."; ++ } ++ leaf prefix-length { ++ type uint8 { ++ range "0..128"; ++ } ++ mandatory true; ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf origin { ++ type ip-address-origin; ++ config false; ++ description ++ "The origin of this address."; ++ } ++ leaf status { ++ type enumeration { ++ enum preferred { ++ description ++ "This is a valid address that can appear as the ++ destination or source address of a packet."; ++ } ++ enum deprecated { ++ description ++ "This is a valid but deprecated address that should ++ no longer be used as a source address in new ++ communications, but packets addressed to such an ++ address are processed as expected."; ++ } ++ enum invalid { ++ description ++ "This isn't a valid address, and it shouldn't appear ++ as the destination or source address of a packet."; ++ } ++ ++ enum inaccessible { ++ description ++ "The address is not accessible because the interface ++ to which this address is assigned is not ++ operational."; ++ } ++ enum unknown { ++ description ++ "The status cannot be determined for some reason."; ++ } ++ enum tentative { ++ description ++ "The uniqueness of the address on the link is being ++ verified. Addresses in this state should not be ++ used for general communication and should only be ++ used to determine the uniqueness of the address."; ++ } ++ enum duplicate { ++ description ++ "The address has been determined to be non-unique on ++ the link and so must not be used."; ++ } ++ enum optimistic { ++ description ++ "The address is available for use, subject to ++ restrictions, while its uniqueness on a link is ++ being verified."; ++ } ++ } ++ config false; ++ description ++ "The status of an address. Most of the states correspond ++ to states from the IPv6 Stateless Address ++ Autoconfiguration protocol."; ++ reference ++ "RFC 4293: Management Information Base for the ++ Internet Protocol (IP) ++ - IpAddressStatusTC ++ RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ } ++ } ++ ++ list neighbor { ++ key "ip"; ++ description ++ "A list of mappings from IPv6 addresses to ++ link-layer addresses. ++ ++ Entries in this list in the intended configuration are ++ used as static entries in the Neighbor Cache. ++ ++ In the operational state, this list represents the ++ Neighbor Cache."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ description ++ "The IPv6 address of the neighbor node."; ++ } ++ leaf link-layer-address { ++ type yang:phys-address; ++ mandatory true; ++ description ++ "The link-layer address of the neighbor node. ++ ++ In the operational state, if the neighbor's 'state' leaf ++ is 'incomplete', this leaf is not instantiated."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ config false; ++ description ++ "The origin of this neighbor entry."; ++ } ++ leaf is-router { ++ type empty; ++ config false; ++ description ++ "Indicates that the neighbor node acts as a router."; ++ } ++ ++ leaf state { ++ type enumeration { ++ enum incomplete { ++ description ++ "Address resolution is in progress, and the ++ link-layer address of the neighbor has not yet been ++ determined."; ++ } ++ enum reachable { ++ description ++ "Roughly speaking, the neighbor is known to have been ++ reachable recently (within tens of seconds ago)."; ++ } ++ enum stale { ++ description ++ "The neighbor is no longer known to be reachable, but ++ until traffic is sent to the neighbor no attempt ++ should be made to verify its reachability."; ++ } ++ enum delay { ++ description ++ "The neighbor is no longer known to be reachable, and ++ traffic has recently been sent to the neighbor. ++ Rather than probe the neighbor immediately, however, ++ delay sending probes for a short while in order to ++ give upper-layer protocols a chance to provide ++ reachability confirmation."; ++ } ++ enum probe { ++ description ++ "The neighbor is no longer known to be reachable, and ++ unicast Neighbor Solicitation probes are being sent ++ to verify reachability."; ++ } ++ } ++ config false; ++ description ++ "The Neighbor Unreachability Detection state of this ++ entry."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 7.3.2"; ++ } ++ } ++ ++ leaf dup-addr-detect-transmits { ++ type uint32; ++ default 1; ++ description ++ "The number of consecutive Neighbor Solicitation messages ++ sent while performing Duplicate Address Detection on a ++ tentative address. A value of zero indicates that ++ Duplicate Address Detection is not performed on ++ tentative addresses. A value of one indicates a single ++ transmission with no follow-up retransmissions."; ++ reference ++ "RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ } ++ container autoconf { ++ description ++ "Parameters to control the autoconfiguration of IPv6 ++ addresses, as described in RFC 4862."; ++ reference ++ "RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ ++ leaf create-global-addresses { ++ type boolean; ++ default true; ++ description ++ "If enabled, the host creates global addresses as ++ described in RFC 4862."; ++ reference ++ "RFC 4862: IPv6 Stateless Address Autoconfiguration ++ Section 5.5"; ++ } ++ leaf create-temporary-addresses { ++ if-feature ipv6-privacy-autoconf; ++ type boolean; ++ default false; ++ description ++ "If enabled, the host creates temporary addresses as ++ described in RFC 4941."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6"; ++ } ++ ++ leaf temporary-valid-lifetime { ++ if-feature ipv6-privacy-autoconf; ++ type uint32; ++ units "seconds"; ++ default 604800; ++ description ++ "The time period during which the temporary address ++ is valid."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6 ++ - TEMP_VALID_LIFETIME"; ++ } ++ leaf temporary-preferred-lifetime { ++ if-feature ipv6-privacy-autoconf; ++ type uint32; ++ units "seconds"; ++ default 86400; ++ description ++ "The time period during which the temporary address is ++ preferred."; ++ reference ++ "RFC 4941: Privacy Extensions for Stateless Address ++ Autoconfiguration in IPv6 ++ - TEMP_PREFERRED_LIFETIME"; ++ } ++ } ++ } ++ } ++ ++ /* ++ * Legacy operational state data nodes ++ */ ++ ++ augment "/if:interfaces-state/if:interface" { ++ status deprecated; ++ description ++ "Data nodes for the operational state of IP on interfaces."; ++ ++ container ipv4 { ++ presence ++ "Present if IPv4 is enabled on this interface"; ++ config false; ++ status deprecated; ++ description ++ "Interface-specific parameters for the IPv4 address family."; ++ ++ leaf forwarding { ++ type boolean; ++ status deprecated; ++ description ++ "Indicates whether IPv4 packet forwarding is enabled or ++ disabled on this interface."; ++ } ++ leaf mtu { ++ type uint16 { ++ range "68..max"; ++ } ++ units "octets"; ++ status deprecated; ++ description ++ "The size, in octets, of the largest IPv4 packet that the ++ interface will send and receive."; ++ reference ++ "RFC 791: Internet Protocol"; ++ } ++ list address { ++ key "ip"; ++ status deprecated; ++ description ++ "The list of IPv4 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ status deprecated; ++ description ++ "The IPv4 address on the interface."; ++ } ++ choice subnet { ++ status deprecated; ++ description ++ "The subnet can be specified as a prefix length or, ++ if the server supports non-contiguous netmasks, as ++ a netmask."; ++ leaf prefix-length { ++ type uint8 { ++ range "0..32"; ++ } ++ status deprecated; ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf netmask { ++ if-feature ipv4-non-contiguous-netmasks; ++ type yang:dotted-quad; ++ status deprecated; ++ description ++ "The subnet specified as a netmask."; ++ } ++ } ++ leaf origin { ++ type ip-address-origin; ++ status deprecated; ++ description ++ "The origin of this address."; ++ } ++ } ++ list neighbor { ++ key "ip"; ++ status deprecated; ++ description ++ "A list of mappings from IPv4 addresses to ++ link-layer addresses. ++ ++ This list represents the ARP Cache."; ++ reference ++ "RFC 826: An Ethernet Address Resolution Protocol"; ++ ++ leaf ip { ++ type inet:ipv4-address-no-zone; ++ status deprecated; ++ description ++ "The IPv4 address of the neighbor node."; ++ } ++ ++ leaf link-layer-address { ++ type yang:phys-address; ++ status deprecated; ++ description ++ "The link-layer address of the neighbor node."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ status deprecated; ++ description ++ "The origin of this neighbor entry."; ++ } ++ } ++ } ++ ++ container ipv6 { ++ presence ++ "Present if IPv6 is enabled on this interface"; ++ config false; ++ status deprecated; ++ description ++ "Parameters for the IPv6 address family."; ++ ++ leaf forwarding { ++ type boolean; ++ default false; ++ status deprecated; ++ description ++ "Indicates whether IPv6 packet forwarding is enabled or ++ disabled on this interface."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 6.2.1, IsRouter"; ++ } ++ leaf mtu { ++ type uint32 { ++ range "1280..max"; ++ } ++ units "octets"; ++ status deprecated; ++ description ++ "The size, in octets, of the largest IPv6 packet that the ++ interface will send and receive."; ++ reference ++ "RFC 8200: Internet Protocol, Version 6 (IPv6) ++ Specification ++ Section 5"; ++ } ++ list address { ++ key "ip"; ++ status deprecated; ++ description ++ "The list of IPv6 addresses on the interface."; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ status deprecated; ++ description ++ "The IPv6 address on the interface."; ++ } ++ leaf prefix-length { ++ type uint8 { ++ range "0..128"; ++ } ++ mandatory true; ++ status deprecated; ++ description ++ "The length of the subnet prefix."; ++ } ++ leaf origin { ++ type ip-address-origin; ++ status deprecated; ++ description ++ "The origin of this address."; ++ } ++ leaf status { ++ type enumeration { ++ enum preferred { ++ description ++ "This is a valid address that can appear as the ++ destination or source address of a packet."; ++ } ++ enum deprecated { ++ description ++ "This is a valid but deprecated address that should ++ no longer be used as a source address in new ++ communications, but packets addressed to such an ++ address are processed as expected."; ++ } ++ enum invalid { ++ description ++ "This isn't a valid address, and it shouldn't appear ++ as the destination or source address of a packet."; ++ } ++ ++ enum inaccessible { ++ description ++ "The address is not accessible because the interface ++ to which this address is assigned is not ++ operational."; ++ } ++ enum unknown { ++ description ++ "The status cannot be determined for some reason."; ++ } ++ enum tentative { ++ description ++ "The uniqueness of the address on the link is being ++ verified. Addresses in this state should not be ++ used for general communication and should only be ++ used to determine the uniqueness of the address."; ++ } ++ enum duplicate { ++ description ++ "The address has been determined to be non-unique on ++ the link and so must not be used."; ++ } ++ enum optimistic { ++ description ++ "The address is available for use, subject to ++ restrictions, while its uniqueness on a link is ++ being verified."; ++ } ++ } ++ status deprecated; ++ description ++ "The status of an address. Most of the states correspond ++ to states from the IPv6 Stateless Address ++ Autoconfiguration protocol."; ++ reference ++ "RFC 4293: Management Information Base for the ++ Internet Protocol (IP) ++ - IpAddressStatusTC ++ RFC 4862: IPv6 Stateless Address Autoconfiguration"; ++ } ++ } ++ ++ list neighbor { ++ key "ip"; ++ status deprecated; ++ description ++ "A list of mappings from IPv6 addresses to ++ link-layer addresses. ++ ++ This list represents the Neighbor Cache."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; ++ ++ leaf ip { ++ type inet:ipv6-address-no-zone; ++ status deprecated; ++ description ++ "The IPv6 address of the neighbor node."; ++ } ++ leaf link-layer-address { ++ type yang:phys-address; ++ status deprecated; ++ description ++ "The link-layer address of the neighbor node."; ++ } ++ leaf origin { ++ type neighbor-origin; ++ status deprecated; ++ description ++ "The origin of this neighbor entry."; ++ } ++ leaf is-router { ++ type empty; ++ status deprecated; ++ description ++ "Indicates that the neighbor node acts as a router."; ++ } ++ leaf state { ++ type enumeration { ++ enum incomplete { ++ description ++ "Address resolution is in progress, and the ++ link-layer address of the neighbor has not yet been ++ determined."; ++ } ++ enum reachable { ++ description ++ "Roughly speaking, the neighbor is known to have been ++ reachable recently (within tens of seconds ago)."; ++ } ++ enum stale { ++ description ++ "The neighbor is no longer known to be reachable, but ++ until traffic is sent to the neighbor no attempt ++ should be made to verify its reachability."; ++ } ++ enum delay { ++ description ++ "The neighbor is no longer known to be reachable, and ++ traffic has recently been sent to the neighbor. ++ Rather than probe the neighbor immediately, however, ++ delay sending probes for a short while in order to ++ give upper-layer protocols a chance to provide ++ reachability confirmation."; ++ } ++ enum probe { ++ description ++ "The neighbor is no longer known to be reachable, and ++ unicast Neighbor Solicitation probes are being sent ++ to verify reachability."; ++ } ++ } ++ status deprecated; ++ description ++ "The Neighbor Unreachability Detection state of this ++ entry."; ++ reference ++ "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) ++ Section 7.3.2"; ++ } ++ } ++ } ++ } ++} +diff --git a/tests/yang/ietf-network-instance@2019-01-21.yang b/tests/yang/ietf-network-instance@2019-01-21.yang +new file mode 100644 +index 0000000..dfde7fb +--- /dev/null ++++ b/tests/yang/ietf-network-instance@2019-01-21.yang +@@ -0,0 +1,282 @@ ++module ietf-network-instance { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-network-instance"; ++ prefix ni; ++ ++ // import some basic types ++ ++ import ietf-interfaces { ++ prefix if; ++ reference ++ "RFC 8343: A YANG Data Model for Interface Management"; ++ } ++ import ietf-ip { ++ prefix ip; ++ reference ++ "RFC 8344: A YANG Data Model for IP Management"; ++ } ++ import ietf-yang-schema-mount { ++ prefix yangmnt; ++ reference ++ "RFC 8528: YANG Schema Mount"; ++ } ++ ++ organization ++ "IETF Routing Area (rtgwg) Working Group"; ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Lou Berger ++ ++ Author: Christian Hopps ++ ++ Author: Acee Lindem ++ ++ Author: Dean Bogdanovic ++ "; ++ description ++ "This module is used to support multiple network instances ++ within a single physical or virtual device. Network ++ instances are commonly known as VRFs (VPN Routing and ++ Forwarding) and VSIs (Virtual Switching Instances). ++ The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', ++ 'SHALL NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', ++ 'NOT RECOMMENDED', 'MAY', and 'OPTIONAL' in this document ++ are to be interpreted as described in BCP 14 (RFC 2119) ++ (RFC 8174) when, and only when, they appear in all capitals, ++ as shown here. ++ ++ Copyright (c) 2019 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD ++ License set forth in Section 4.c of the IETF Trust's Legal ++ Provisions Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8529; see ++ the RFC itself for full legal notices."; ++ ++ revision 2019-01-21 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 8529"; ++ } ++ ++ // top-level device definition statements ++ ++ container network-instances { ++ description ++ "Network instances, each of which consists of ++ VRFs and/or VSIs."; ++ reference ++ "RFC 8349: A YANG Data Model for Routing Management"; ++ list network-instance { ++ key "name"; ++ description ++ "List of network instances."; ++ leaf name { ++ type string; ++ mandatory true; ++ description ++ "device-scoped identifier for the network ++ instance."; ++ } ++ leaf enabled { ++ type boolean; ++ default "true"; ++ description ++ "Flag indicating whether or not the network ++ instance is enabled."; ++ } ++ leaf description { ++ type string; ++ description ++ "Description of the network instance ++ and its intended purpose."; ++ } ++ choice ni-type { ++ description ++ "This node serves as an anchor point for different types ++ of network instances. Each 'case' is expected to ++ differ in terms of the information needed in the ++ parent/core to support the NI and may differ in their ++ mounted-schema definition. When the mounted schema is ++ not expected to be the same for a specific type of NI, ++ a mount point should be defined."; ++ } ++ choice root-type { ++ mandatory true; ++ description ++ "Well-known mount points."; ++ container vrf-root { ++ description ++ "Container for mount point."; ++ yangmnt:mount-point "vrf-root" { ++ description ++ "Root for L3VPN-type models. This will typically ++ not be an inline-type mount point."; ++ } ++ } ++ container vsi-root { ++ description ++ "Container for mount point."; ++ yangmnt:mount-point "vsi-root" { ++ description ++ "Root for L2VPN-type models. This will typically ++ not be an inline-type mount point."; ++ } ++ } ++ container vv-root { ++ description ++ "Container for mount point."; ++ yangmnt:mount-point "vv-root" { ++ description ++ "Root models that support both L2VPN-type bridging ++ and L3VPN-type routing. This will typically ++ not be an inline-type mount point."; ++ } ++ } ++ } ++ } ++ } ++ ++ // augment statements ++ ++ augment "/if:interfaces/if:interface" { ++ description ++ "Add a node for the identification of the network ++ instance associated with the information configured ++ on a interface. ++ ++ Note that a standard error will be returned if the ++ identified leafref isn't present. If an interface cannot ++ be assigned for any other reason, the operation SHALL fail ++ with an error-tag of 'operation-failed' and an ++ error-app-tag of 'ni-assignment-failed'. A meaningful ++ error-info that indicates the source of the assignment ++ failure SHOULD also be provided."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/network-instances/network-instance/name"; ++ } ++ description ++ "Network instance to which an interface is bound."; ++ } ++ } ++ augment "/if:interfaces/if:interface/ip:ipv4" { ++ description ++ "Add a node for the identification of the network ++ instance associated with the information configured ++ on an IPv4 interface. ++ ++ Note that a standard error will be returned if the ++ identified leafref isn't present. If an interface cannot ++ be assigned for any other reason, the operation SHALL fail ++ with an error-tag of 'operation-failed' and an ++ error-app-tag of 'ni-assignment-failed'. A meaningful ++ error-info that indicates the source of the assignment ++ failure SHOULD also be provided."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/network-instances/network-instance/name"; ++ } ++ description ++ "Network instance to which IPv4 interface is bound."; ++ } ++ } ++ augment "/if:interfaces/if:interface/ip:ipv6" { ++ description ++ "Add a node for the identification of the network ++ instance associated with the information configured ++ on an IPv6 interface. ++ ++ Note that a standard error will be returned if the ++ identified leafref isn't present. If an interface cannot ++ be assigned for any other reason, the operation SHALL fail ++ with an error-tag of 'operation-failed' and an ++ error-app-tag of 'ni-assignment-failed'. A meaningful ++ error-info that indicates the source of the assignment ++ failure SHOULD also be provided."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/network-instances/network-instance/name"; ++ } ++ description ++ "Network instance to which IPv6 interface is bound."; ++ } ++ } ++ ++ // notification statements ++ ++ notification bind-ni-name-failed { ++ description ++ "Indicates an error in the association of an interface to an ++ NI. Only generated after success is initially returned when ++ bind-ni-name is set. ++ ++ Note: Some errors may need to be reported for multiple ++ associations, e.g., a single error may need to be reported ++ for an IPv4 and an IPv6 bind-ni-name. ++ ++ At least one container with a bind-ni-name leaf MUST be ++ included in this notification."; ++ leaf name { ++ type leafref { ++ path "/if:interfaces/if:interface/if:name"; ++ } ++ mandatory true; ++ description ++ "Contains the interface name associated with the ++ failure."; ++ } ++ container interface { ++ description ++ "Generic interface type."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/if:interfaces/if:interface" ++ + "/ni:bind-ni-name"; ++ } ++ description ++ "Contains the bind-ni-name associated with the ++ failure."; ++ } ++ } ++ container ipv4 { ++ description ++ "IPv4 interface type."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/if:interfaces/if:interface/ip:ipv4/ni:bind-ni-name"; ++ } ++ description ++ "Contains the bind-ni-name associated with the ++ failure."; ++ } ++ } ++ container ipv6 { ++ description ++ "IPv6 interface type."; ++ leaf bind-ni-name { ++ type leafref { ++ path "/if:interfaces/if:interface/ip:ipv6" ++ + "/ni:bind-ni-name"; ++ } ++ description ++ "Contains the bind-ni-name associated with the ++ failure."; ++ } ++ } ++ leaf error-info { ++ type string; ++ description ++ "Optionally, indicates the source of the assignment ++ failure."; ++ } ++ } ++} +diff --git a/tests/yang/ietf-restconf@2017-01-26.yang b/tests/yang/ietf-restconf@2017-01-26.yang +new file mode 100644 +index 0000000..b47455b +--- /dev/null ++++ b/tests/yang/ietf-restconf@2017-01-26.yang +@@ -0,0 +1,278 @@ ++module ietf-restconf { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-restconf"; ++ prefix "rc"; ++ ++ organization ++ "IETF NETCONF (Network Configuration) Working Group"; ++ ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Andy Bierman ++ ++ ++ Author: Martin Bjorklund ++ ++ ++ Author: Kent Watsen ++ "; ++ ++ description ++ "This module contains conceptual YANG specifications ++ for basic RESTCONF media type definitions used in ++ RESTCONF protocol messages. ++ ++ Note that the YANG definitions within this module do not ++ represent configuration data of any kind. ++ The 'restconf-media-type' YANG extension statement ++ provides a normative syntax for XML and JSON ++ message-encoding purposes. ++ ++ Copyright (c) 2017 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (http://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8040; see ++ the RFC itself for full legal notices."; ++ ++ revision 2017-01-26 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 8040: RESTCONF Protocol."; ++ } ++ ++ extension yang-data { ++ argument name { ++ yin-element true; ++ } ++ description ++ "This extension is used to specify a YANG data template that ++ represents conceptual data defined in YANG. It is ++ intended to describe hierarchical data independent of ++ protocol context or specific message-encoding format. ++ Data definition statements within a yang-data extension ++ specify the generic syntax for the specific YANG data ++ template, whose name is the argument of the 'yang-data' ++ extension statement. ++ ++ Note that this extension does not define a media type. ++ A specification using this extension MUST specify the ++ message-encoding rules, including the content media type. ++ ++ The mandatory 'name' parameter value identifies the YANG ++ data template that is being defined. It contains the ++ template name. ++ ++ This extension is ignored unless it appears as a top-level ++ statement. It MUST contain data definition statements ++ that result in exactly one container data node definition. ++ An instance of a YANG data template can thus be translated ++ into an XML instance document, whose top-level element ++ corresponds to the top-level container. ++ The module name and namespace values for the YANG module using ++ the extension statement are assigned to instance document data ++ conforming to the data definition statements within ++ this extension. ++ ++ The substatements of this extension MUST follow the ++ 'data-def-stmt' rule in the YANG ABNF. ++ ++ The XPath document root is the extension statement itself, ++ such that the child nodes of the document root are ++ represented by the data-def-stmt substatements within ++ this extension. This conceptual document is the context ++ for the following YANG statements: ++ ++ - must-stmt ++ - when-stmt ++ - path-stmt ++ - min-elements-stmt ++ - max-elements-stmt ++ - mandatory-stmt ++ - unique-stmt ++ - ordered-by ++ - instance-identifier data type ++ ++ The following data-def-stmt substatements are constrained ++ when used within a 'yang-data' extension statement. ++ ++ - The list-stmt is not required to have a key-stmt defined. ++ - The if-feature-stmt is ignored if present. ++ - The config-stmt is ignored if present. ++ - The available identity values for any 'identityref' ++ leaf or leaf-list nodes are limited to the module ++ containing this extension statement and the modules ++ imported into that module. ++ "; ++ } ++ ++ rc:yang-data yang-errors { ++ uses errors; ++ } ++ ++ rc:yang-data yang-api { ++ uses restconf; ++ } ++ ++ grouping errors { ++ description ++ "A grouping that contains a YANG container ++ representing the syntax and semantics of a ++ YANG Patch error report within a response message."; ++ ++ container errors { ++ description ++ "Represents an error report returned by the server if ++ a request results in an error."; ++ ++ list error { ++ description ++ "An entry containing information about one ++ specific error that occurred while processing ++ a RESTCONF request."; ++ reference ++ "RFC 6241, Section 4.3."; ++ ++ leaf error-type { ++ type enumeration { ++ enum transport { ++ description ++ "The transport layer."; ++ } ++ enum rpc { ++ description ++ "The rpc or notification layer."; ++ } ++ enum protocol { ++ description ++ "The protocol operation layer."; ++ } ++ enum application { ++ description ++ "The server application layer."; ++ } ++ } ++ mandatory true; ++ description ++ "The protocol layer where the error occurred."; ++ } ++ ++ leaf error-tag { ++ type string; ++ mandatory true; ++ description ++ "The enumerated error-tag."; ++ } ++ ++ leaf error-app-tag { ++ type string; ++ description ++ "The application-specific error-tag."; ++ } ++ ++ leaf error-path { ++ type instance-identifier; ++ description ++ "The YANG instance identifier associated ++ with the error node."; ++ } ++ ++ leaf error-message { ++ type string; ++ description ++ "A message describing the error."; ++ } ++ ++ anydata error-info { ++ description ++ "This anydata value MUST represent a container with ++ zero or more data nodes representing additional ++ error information."; ++ } ++ } ++ } ++ } ++ ++ grouping restconf { ++ description ++ "Conceptual grouping representing the RESTCONF ++ root resource."; ++ ++ container restconf { ++ description ++ "Conceptual container representing the RESTCONF ++ root resource."; ++ ++ container data { ++ description ++ "Container representing the datastore resource. ++ Represents the conceptual root of all state data ++ and configuration data supported by the server. ++ The child nodes of this container can be any data ++ resources that are defined as top-level data nodes ++ from the YANG modules advertised by the server in ++ the 'ietf-yang-library' module."; ++ } ++ ++ container operations { ++ description ++ "Container for all operation resources. ++ ++ Each resource is represented as an empty leaf with the ++ name of the RPC operation from the YANG 'rpc' statement. ++ ++ For example, the 'system-restart' RPC operation defined ++ in the 'ietf-system' module would be represented as ++ an empty leaf in the 'ietf-system' namespace. This is ++ a conceptual leaf and will not actually be found in ++ the module: ++ ++ module ietf-system { ++ leaf system-reset { ++ type empty; ++ } ++ } ++ ++ To invoke the 'system-restart' RPC operation: ++ ++ POST /restconf/operations/ietf-system:system-restart ++ ++ To discover the RPC operations supported by the server: ++ ++ GET /restconf/operations ++ ++ In XML, the YANG module namespace identifies the module: ++ ++ ++ ++ In JSON, the YANG module name identifies the module: ++ ++ { 'ietf-system:system-restart' : [null] } ++ "; ++ } ++ leaf yang-library-version { ++ type string { ++ pattern '\d{4}-\d{2}-\d{2}'; ++ } ++ config false; ++ mandatory true; ++ description ++ "Identifies the revision date of the 'ietf-yang-library' ++ module that is implemented by this RESTCONF server. ++ Indicates the year, month, and day in YYYY-MM-DD ++ numeric format."; ++ } ++ } ++ } ++ ++} +diff --git a/tests/yang/ietf-subscribed-notifications@2019-09-09.yang b/tests/yang/ietf-subscribed-notifications@2019-09-09.yang +new file mode 100644 +index 0000000..e04593c +--- /dev/null ++++ b/tests/yang/ietf-subscribed-notifications@2019-09-09.yang +@@ -0,0 +1,1350 @@ ++module ietf-subscribed-notifications { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications"; ++ prefix sn; ++ ++ import ietf-inet-types { ++ prefix inet; ++ reference ++ "RFC 6991: Common YANG Data Types"; ++ } ++ import ietf-interfaces { ++ prefix if; ++ reference ++ "RFC 8343: A YANG Data Model for Interface Management"; ++ } ++ import ietf-netconf-acm { ++ prefix nacm; ++ reference ++ "RFC 8341: Network Configuration Access Control Model"; ++ } ++ import ietf-network-instance { ++ prefix ni; ++ reference ++ "RFC 8529: YANG Data Model for Network Instances"; ++ } ++ import ietf-restconf { ++ prefix rc; ++ reference ++ "RFC 8040: RESTCONF Protocol"; ++ } ++ import ietf-yang-types { ++ prefix yang; ++ reference ++ "RFC 6991: Common YANG Data Types"; ++ } ++ ++ organization ++ "IETF NETCONF (Network Configuration) Working Group"; ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Alexander Clemm ++ ++ ++ Author: Eric Voit ++ ++ ++ Author: Alberto Gonzalez Prieto ++ ++ ++ Author: Einar Nilsen-Nygaard ++ ++ ++ Author: Ambika Prasad Tripathy ++ "; ++ description ++ "This module defines a YANG data model for subscribing to event ++ records and receiving matching content in notification messages. ++ ++ The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL ++ NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', ++ 'MAY', and 'OPTIONAL' in this document are to be interpreted as ++ described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, ++ they appear in all capitals, as shown here. ++ ++ Copyright (c) 2019 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject to ++ the license terms contained in, the Simplified BSD License set ++ forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8639; see the ++ RFC itself for full legal notices."; ++ ++ revision 2019-09-09 { ++ description ++ "Initial version."; ++ reference ++ "RFC 8639: A YANG Data Model for Subscriptions to ++ Event Notifications"; ++ } ++ ++ /* ++ * FEATURES ++ */ ++ ++ feature configured { ++ description ++ "This feature indicates that configuration of subscriptions is ++ supported."; ++ } ++ ++ feature dscp { ++ description ++ "This feature indicates that a publisher supports the ability ++ to set the Differentiated Services Code Point (DSCP) value in ++ outgoing packets."; ++ } ++ ++ feature encode-json { ++ description ++ "This feature indicates that JSON encoding of notification ++ messages is supported."; ++ } ++ ++ feature encode-xml { ++ description ++ "This feature indicates that XML encoding of notification ++ messages is supported."; ++ } ++ ++ feature interface-designation { ++ description ++ "This feature indicates that a publisher supports sourcing all ++ receiver interactions for a configured subscription from a ++ single designated egress interface."; ++ } ++ ++ feature qos { ++ description ++ "This feature indicates that a publisher supports absolute ++ dependencies of one subscription's traffic over another ++ as well as weighted bandwidth sharing between subscriptions. ++ Both of these are Quality of Service (QoS) features that allow ++ differentiated treatment of notification messages between a ++ publisher and a specific receiver."; ++ } ++ ++ feature replay { ++ description ++ "This feature indicates that historical event record replay is ++ supported. With replay, it is possible for past event records ++ to be streamed in chronological order."; ++ } ++ ++ feature subtree { ++ description ++ "This feature indicates support for YANG subtree filtering."; ++ reference ++ "RFC 6241: Network Configuration Protocol (NETCONF), ++ Section 6"; ++ } ++ ++ feature supports-vrf { ++ description ++ "This feature indicates that a publisher supports VRF ++ configuration for configured subscriptions. VRF support for ++ dynamic subscriptions does not require this feature."; ++ reference ++ "RFC 8529: YANG Data Model for Network Instances, ++ Section 6"; ++ } ++ ++ feature xpath { ++ description ++ "This feature indicates support for XPath filtering."; ++ reference ++ "XML Path Language (XPath) Version 1.0 ++ (https://www.w3.org/TR/1999/REC-xpath-19991116)"; ++ } ++ ++ /* ++ * EXTENSIONS ++ */ ++ ++ extension subscription-state-notification { ++ description ++ "This statement applies only to notifications. It indicates ++ that the notification is a subscription state change ++ notification. Therefore, it does not participate in a regular ++ event stream and does not need to be specifically subscribed ++ to in order to be received. This statement can only occur as ++ a substatement of the YANG 'notification' statement. This ++ statement is not for use outside of this YANG module."; ++ } ++ ++ /* ++ * IDENTITIES ++ */ ++ /* Identities for RPC and notification errors */ ++ ++ identity delete-subscription-error { ++ description ++ "Base identity for the problem found while attempting to ++ fulfill either a 'delete-subscription' RPC request or a ++ 'kill-subscription' RPC request."; ++ } ++ ++ identity establish-subscription-error { ++ description ++ "Base identity for the problem found while attempting to ++ fulfill an 'establish-subscription' RPC request."; ++ } ++ ++ identity modify-subscription-error { ++ description ++ "Base identity for the problem found while attempting to ++ fulfill a 'modify-subscription' RPC request."; ++ } ++ ++ identity subscription-suspended-reason { ++ description ++ "Base identity for the problem condition communicated to a ++ receiver as part of a 'subscription-suspended' ++ notification."; ++ } ++ ++ identity subscription-terminated-reason { ++ description ++ "Base identity for the problem condition communicated to a ++ receiver as part of a 'subscription-terminated' ++ notification."; ++ } ++ ++ identity dscp-unavailable { ++ base establish-subscription-error; ++ if-feature "dscp"; ++ description ++ "The publisher is unable to mark notification messages with ++ prioritization information in a way that will be respected ++ during network transit."; ++ } ++ ++ identity encoding-unsupported { ++ base establish-subscription-error; ++ description ++ "Unable to encode notification messages in the desired ++ format."; ++ } ++ ++ identity filter-unavailable { ++ base subscription-terminated-reason; ++ description ++ "Referenced filter does not exist. This means a receiver is ++ referencing a filter that doesn't exist or to which it ++ does not have access permissions."; ++ } ++ ++ identity filter-unsupported { ++ base establish-subscription-error; ++ base modify-subscription-error; ++ description ++ "Cannot parse syntax in the filter. This failure can be from ++ a syntax error or a syntax too complex to be processed by the ++ publisher."; ++ } ++ ++ identity insufficient-resources { ++ base establish-subscription-error; ++ base modify-subscription-error; ++ base subscription-suspended-reason; ++ description ++ "The publisher does not have sufficient resources to support ++ the requested subscription. An example might be that ++ allocated CPU is too limited to generate the desired set of ++ notification messages."; ++ } ++ ++ identity no-such-subscription { ++ base modify-subscription-error; ++ base delete-subscription-error; ++ base subscription-terminated-reason; ++ description ++ "Referenced subscription doesn't exist. This may be as a ++ result of a nonexistent subscription ID, an ID that belongs to ++ another subscriber, or an ID for a configured subscription."; ++ } ++ ++ identity replay-unsupported { ++ base establish-subscription-error; ++ if-feature "replay"; ++ description ++ "Replay cannot be performed for this subscription. This means ++ the publisher will not provide the requested historic ++ information from the event stream via replay to this ++ receiver."; ++ } ++ ++ identity stream-unavailable { ++ base subscription-terminated-reason; ++ description ++ "Not a subscribable event stream. This means the referenced ++ event stream is not available for subscription by the ++ receiver."; ++ } ++ ++ identity suspension-timeout { ++ base subscription-terminated-reason; ++ description ++ "Termination of a previously suspended subscription. The ++ publisher has eliminated the subscription, as it exceeded a ++ time limit for suspension."; ++ } ++ ++ identity unsupportable-volume { ++ base subscription-suspended-reason; ++ description ++ "The publisher does not have the network bandwidth needed to ++ get the volume of generated information intended for a ++ receiver."; ++ } ++ ++ /* Identities for encodings */ ++ ++ identity configurable-encoding { ++ description ++ "If a transport identity derives from this identity, it means ++ that it supports configurable encodings. An example of a ++ configurable encoding might be a new identity such as ++ 'encode-cbor'. Such an identity could use ++ 'configurable-encoding' as its base. This would allow a ++ dynamic subscription encoded in JSON (RFC 8259) to request ++ that notification messages be encoded via the Concise Binary ++ Object Representation (CBOR) (RFC 7049). Further details for ++ any specific configurable encoding would be explored in a ++ transport document based on this specification."; ++ reference ++ "RFC 8259: The JavaScript Object Notation (JSON) Data ++ Interchange Format ++ RFC 7049: Concise Binary Object Representation (CBOR)"; ++ } ++ ++ identity encoding { ++ description ++ "Base identity to represent data encodings."; ++ } ++ ++ identity encode-xml { ++ base encoding; ++ if-feature "encode-xml"; ++ description ++ "Encode data using XML as described in RFC 7950."; ++ reference ++ "RFC 7950: The YANG 1.1 Data Modeling Language"; ++ } ++ ++ identity encode-json { ++ base encoding; ++ if-feature "encode-json"; ++ description ++ "Encode data using JSON as described in RFC 7951."; ++ reference ++ "RFC 7951: JSON Encoding of Data Modeled with YANG"; ++ } ++ ++ /* Identities for transports */ ++ ++ identity transport { ++ description ++ "An identity that represents the underlying mechanism for ++ passing notification messages."; ++ } ++ ++ /* ++ * TYPEDEFs ++ */ ++ ++ typedef encoding { ++ type identityref { ++ base encoding; ++ } ++ description ++ "Specifies a data encoding, e.g., for a data subscription."; ++ } ++ ++ typedef stream-filter-ref { ++ type leafref { ++ path "/sn:filters/sn:stream-filter/sn:name"; ++ } ++ description ++ "This type is used to reference an event stream filter."; ++ } ++ ++ typedef stream-ref { ++ type leafref { ++ path "/sn:streams/sn:stream/sn:name"; ++ } ++ description ++ "This type is used to reference a system-provided ++ event stream."; ++ } ++ ++ typedef subscription-id { ++ type uint32; ++ description ++ "A type for subscription identifiers."; ++ } ++ ++ typedef transport { ++ type identityref { ++ base transport; ++ } ++ description ++ "Specifies the transport used to send notification messages ++ to a receiver."; ++ } ++ ++ /* ++ * GROUPINGS ++ */ ++ ++ grouping stream-filter-elements { ++ description ++ "This grouping defines the base for filters applied to event ++ streams."; ++ choice filter-spec { ++ description ++ "The content filter specification for this request."; ++ anydata stream-subtree-filter { ++ if-feature "subtree"; ++ description ++ "Event stream evaluation criteria encoded in the syntax of ++ a subtree filter as defined in RFC 6241, Section 6. ++ ++ The subtree filter is applied to the representation of ++ individual, delineated event records as contained in the ++ event stream. ++ ++ If the subtree filter returns a non-empty node set, the ++ filter matches the event record, and the event record is ++ included in the notification message sent to the ++ receivers."; ++ reference ++ "RFC 6241: Network Configuration Protocol (NETCONF), ++ Section 6"; ++ } ++ leaf stream-xpath-filter { ++ if-feature "xpath"; ++ type yang:xpath1.0; ++ description ++ "Event stream evaluation criteria encoded in the syntax of ++ an XPath 1.0 expression. ++ ++ The XPath expression is evaluated on the representation of ++ individual, delineated event records as contained in ++ the event stream. ++ ++ The result of the XPath expression is converted to a ++ boolean value using the standard XPath 1.0 rules. If the ++ boolean value is 'true', the filter matches the event ++ record, and the event record is included in the ++ notification message sent to the receivers. ++ ++ The expression is evaluated in the following XPath ++ context: ++ ++ o The set of namespace declarations is the set of ++ prefix and namespace pairs for all YANG modules ++ implemented by the server, where the prefix is the ++ YANG module name and the namespace is as defined by ++ the 'namespace' statement in the YANG module. ++ ++ If the leaf is encoded in XML, all namespace ++ declarations in scope on the 'stream-xpath-filter' ++ leaf element are added to the set of namespace ++ declarations. If a prefix found in the XML is ++ already present in the set of namespace ++ declarations, the namespace in the XML is used. ++ ++ o The set of variable bindings is empty. ++ ++ o The function library is comprised of the core ++ function library and the XPath functions defined in ++ Section 10 in RFC 7950. ++ ++ o The context node is the root node."; ++ reference ++ "XML Path Language (XPath) Version 1.0 ++ (https://www.w3.org/TR/1999/REC-xpath-19991116) ++ RFC 7950: The YANG 1.1 Data Modeling Language, ++ Section 10"; ++ } ++ } ++ } ++ ++ grouping update-qos { ++ description ++ "This grouping describes QoS information concerning a ++ subscription. This information is passed to lower layers ++ for transport prioritization and treatment."; ++ leaf dscp { ++ if-feature "dscp"; ++ type inet:dscp; ++ default "0"; ++ description ++ "The desired network transport priority level. This is the ++ priority set on notification messages encapsulating the ++ results of the subscription. This transport priority is ++ shared for all receivers of a given subscription."; ++ } ++ leaf weighting { ++ if-feature "qos"; ++ type uint8 { ++ range "0 .. 255"; ++ } ++ description ++ "Relative weighting for a subscription. Larger weights get ++ more resources. Allows an underlying transport layer to ++ perform informed load-balance allocations between various ++ subscriptions."; ++ reference ++ "RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2), ++ Section 5.3.2"; ++ } ++ leaf dependency { ++ if-feature "qos"; ++ type subscription-id; ++ description ++ "Provides the 'subscription-id' of a parent subscription. ++ The parent subscription has absolute precedence should ++ that parent have push updates ready to egress the publisher. ++ In other words, there should be no streaming of objects from ++ the current subscription if the parent has something ready ++ to push. ++ ++ If a dependency is asserted via configuration or via an RPC ++ but the referenced 'subscription-id' does not exist, the ++ dependency is silently discarded. If a referenced ++ subscription is deleted, this dependency is removed."; ++ reference ++ "RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2), ++ Section 5.3.1"; ++ } ++ } ++ ++ grouping subscription-policy-modifiable { ++ description ++ "This grouping describes all objects that may be changed ++ in a subscription."; ++ choice target { ++ mandatory true; ++ description ++ "Identifies the source of information against which a ++ subscription is being applied as well as specifics on the ++ subset of information desired from that source."; ++ case stream { ++ choice stream-filter { ++ description ++ "An event stream filter can be applied to a subscription. ++ That filter will either come referenced from a global ++ list or be provided in the subscription itself."; ++ case by-reference { ++ description ++ "Apply a filter that has been configured separately."; ++ leaf stream-filter-name { ++ type stream-filter-ref; ++ mandatory true; ++ description ++ "References an existing event stream filter that is ++ to be applied to an event stream for the ++ subscription."; ++ } ++ } ++ case within-subscription { ++ description ++ "A local definition allows a filter to have the same ++ lifecycle as the subscription."; ++ uses stream-filter-elements; ++ } ++ } ++ } ++ } ++ leaf stop-time { ++ type yang:date-and-time; ++ description ++ "Identifies a time after which notification messages for a ++ subscription should not be sent. If 'stop-time' is not ++ present, the notification messages will continue until the ++ subscription is terminated. If 'replay-start-time' exists, ++ 'stop-time' must be for a subsequent time. If ++ 'replay-start-time' doesn't exist, 'stop-time', when ++ established, must be for a future time."; ++ } ++ } ++ ++ grouping subscription-policy-dynamic { ++ description ++ "This grouping describes the only information concerning a ++ subscription that can be passed over the RPCs defined in this ++ data model."; ++ uses subscription-policy-modifiable { ++ augment "target/stream" { ++ description ++ "Adds additional objects that can be modified by an RPC."; ++ leaf stream { ++ type stream-ref { ++ require-instance false; ++ } ++ mandatory true; ++ description ++ "Indicates the event stream to be considered for ++ this subscription."; ++ } ++ leaf replay-start-time { ++ if-feature "replay"; ++ type yang:date-and-time; ++ config false; ++ description ++ "Used to trigger the 'replay' feature for a dynamic ++ subscription, where event records that are selected ++ need to be at or after the specified starting time. If ++ 'replay-start-time' is not present, this is not a replay ++ subscription and event record push should start ++ immediately. It is never valid to specify start times ++ that are later than or equal to the current time."; ++ } ++ } ++ } ++ uses update-qos; ++ } ++ ++ grouping subscription-policy { ++ description ++ "This grouping describes the full set of policy information ++ concerning both dynamic and configured subscriptions, with the ++ exclusion of both receivers and networking information ++ specific to the publisher, such as what interface should be ++ used to transmit notification messages."; ++ uses subscription-policy-dynamic; ++ leaf transport { ++ if-feature "configured"; ++ type transport; ++ description ++ "For a configured subscription, this leaf specifies the ++ transport used to deliver messages destined for all ++ receivers of that subscription."; ++ } ++ leaf encoding { ++ when 'not(../transport) or derived-from(../transport, ++ "sn:configurable-encoding")'; ++ type encoding; ++ description ++ "The type of encoding for notification messages. For a ++ dynamic subscription, if not included as part of an ++ 'establish-subscription' RPC, the encoding will be populated ++ with the encoding used by that RPC. For a configured ++ subscription, if not explicitly configured, the encoding ++ will be the default encoding for an underlying transport."; ++ } ++ leaf purpose { ++ if-feature "configured"; ++ type string; ++ description ++ "Open text allowing a configuring entity to embed the ++ originator or other specifics of this subscription."; ++ } ++ } ++ ++ /* ++ * RPCs ++ */ ++ ++ rpc establish-subscription { ++ description ++ "This RPC allows a subscriber to create (and possibly ++ negotiate) a subscription on its own behalf. If successful, ++ the subscription remains in effect for the duration of the ++ subscriber's association with the publisher or until the ++ subscription is terminated. If an error occurs or the ++ publisher cannot meet the terms of a subscription, an RPC ++ error is returned, and the subscription is not created. ++ In that case, the RPC reply's 'error-info' MAY include ++ suggested parameter settings that would have a higher ++ likelihood of succeeding in a subsequent ++ 'establish-subscription' request."; ++ input { ++ uses subscription-policy-dynamic; ++ leaf encoding { ++ type encoding; ++ description ++ "The type of encoding for the subscribed data. If not ++ included as part of the RPC, the encoding MUST be set by ++ the publisher to be the encoding used by this RPC."; ++ } ++ } ++ output { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier used for this subscription."; ++ } ++ leaf replay-start-time-revision { ++ if-feature "replay"; ++ type yang:date-and-time; ++ description ++ "If a replay has been requested, this object represents ++ the earliest time covered by the event buffer for the ++ requested event stream. The value of this object is the ++ 'replay-log-aged-time' if it exists. Otherwise, it is ++ the 'replay-log-creation-time'. All buffered event ++ records after this time will be replayed to a receiver. ++ This object will only be sent if the starting time has ++ been revised to be later than the time requested by the ++ subscriber."; ++ } ++ } ++ } ++ ++ rc:yang-data establish-subscription-stream-error-info { ++ container establish-subscription-stream-error-info { ++ description ++ "If any 'establish-subscription' RPC parameters are ++ unsupportable against the event stream, a subscription ++ is not created and the RPC error response MUST indicate the ++ reason why the subscription failed to be created. This ++ yang-data MAY be inserted as structured data in a ++ subscription's RPC error response to indicate the reason for ++ the failure. This yang-data MUST be inserted if hints are ++ to be provided back to the subscriber."; ++ leaf reason { ++ type identityref { ++ base establish-subscription-error; ++ } ++ description ++ "Indicates the reason why the subscription has failed to ++ be created to a targeted event stream."; ++ } ++ leaf filter-failure-hint { ++ type string; ++ description ++ "Information describing where and/or why a provided ++ filter was unsupportable for a subscription. The ++ syntax and semantics of this hint are ++ implementation specific."; ++ } ++ } ++ } ++ ++ rpc modify-subscription { ++ description ++ "This RPC allows a subscriber to modify a dynamic ++ subscription's parameters. If successful, the changed ++ subscription parameters remain in effect for the duration of ++ the subscription, until the subscription is again modified, or ++ until the subscription is terminated. In the case of an error ++ or an inability to meet the modified parameters, the ++ subscription is not modified and the original subscription ++ parameters remain in effect. In that case, the RPC error MAY ++ include 'error-info' suggested parameter hints that would have ++ a high likelihood of succeeding in a subsequent ++ 'modify-subscription' request. A successful ++ 'modify-subscription' will return a suspended subscription to ++ the 'active' state."; ++ input { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier to use for this subscription."; ++ } ++ uses subscription-policy-modifiable; ++ } ++ } ++ ++ rc:yang-data modify-subscription-stream-error-info { ++ container modify-subscription-stream-error-info { ++ description ++ "This yang-data MAY be provided as part of a subscription's ++ RPC error response when there is a failure of a ++ 'modify-subscription' RPC that has been made against an ++ event stream. This yang-data MUST be used if hints are to ++ be provided back to the subscriber."; ++ leaf reason { ++ type identityref { ++ base modify-subscription-error; ++ } ++ description ++ "Information in a 'modify-subscription' RPC error response ++ that indicates the reason why the subscription to an event ++ stream has failed to be modified."; ++ } ++ leaf filter-failure-hint { ++ type string; ++ description ++ "Information describing where and/or why a provided ++ filter was unsupportable for a subscription. The syntax ++ and semantics of this hint are ++ implementation specific."; ++ } ++ } ++ } ++ ++ rpc delete-subscription { ++ description ++ "This RPC allows a subscriber to delete a subscription that ++ was previously created by that same subscriber using the ++ 'establish-subscription' RPC. ++ ++ If an error occurs, the server replies with an 'rpc-error' ++ where the 'error-info' field MAY contain a ++ 'delete-subscription-error-info' structure."; ++ input { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier of the subscription that is to be deleted. ++ Only subscriptions that were created using ++ 'establish-subscription' from the same origin as this RPC ++ can be deleted via this RPC."; ++ } ++ } ++ } ++ ++ rpc kill-subscription { ++ nacm:default-deny-all; ++ description ++ "This RPC allows an operator to delete a dynamic subscription ++ without restrictions on the originating subscriber or ++ underlying transport session. ++ ++ If an error occurs, the server replies with an 'rpc-error' ++ where the 'error-info' field MAY contain a ++ 'delete-subscription-error-info' structure."; ++ input { ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "Identifier of the subscription that is to be deleted. ++ Only subscriptions that were created using ++ 'establish-subscription' can be deleted via this RPC."; ++ } ++ } ++ } ++ ++ rc:yang-data delete-subscription-error-info { ++ container delete-subscription-error-info { ++ description ++ "If a 'delete-subscription' RPC or a 'kill-subscription' RPC ++ fails, the subscription is not deleted and the RPC error ++ response MUST indicate the reason for this failure. This ++ yang-data MAY be inserted as structured data in a ++ subscription's RPC error response to indicate the reason ++ for the failure."; ++ leaf reason { ++ type identityref { ++ base delete-subscription-error; ++ } ++ mandatory true; ++ description ++ "Indicates the reason why the subscription has failed to be ++ deleted."; ++ } ++ } ++ } ++ ++ /* ++ * NOTIFICATIONS ++ */ ++ ++ notification replay-completed { ++ sn:subscription-state-notification; ++ if-feature "replay"; ++ description ++ "This notification is sent to indicate that all of the replay ++ notifications have been sent."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ } ++ ++ notification subscription-completed { ++ sn:subscription-state-notification; ++ if-feature "configured"; ++ description ++ "This notification is sent to indicate that a subscription has ++ finished passing event records, as the 'stop-time' has been ++ reached."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the gracefully completed subscription."; ++ } ++ } ++ ++ notification subscription-modified { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a subscription has been ++ modified. Notification messages sent from this point on will ++ conform to the modified terms of the subscription. For ++ completeness, this subscription state change notification ++ includes both modified and unmodified aspects of a ++ subscription."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ uses subscription-policy { ++ refine "target/stream/stream-filter/within-subscription" { ++ description ++ "Filter applied to the subscription. If the ++ 'stream-filter-name' is populated, the filter in the ++ subscription came from the 'filters' container. ++ Otherwise, it is populated in-line as part of the ++ subscription."; ++ } ++ } ++ } ++ ++ notification subscription-resumed { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a subscription that had ++ previously been suspended has resumed. Notifications will ++ once again be sent. In addition, a 'subscription-resumed' ++ indicates that no modification of parameters has occurred ++ since the last time event records have been sent."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ } ++ ++ notification subscription-started { ++ sn:subscription-state-notification; ++ if-feature "configured"; ++ description ++ "This notification indicates that a subscription has started ++ and notifications will now be sent."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ uses subscription-policy { ++ refine "target/stream/replay-start-time" { ++ description ++ "Indicates the time that a replay is using for the ++ streaming of buffered event records. This will be ++ populated with the most recent of the following: ++ the event time of the previous event record sent to a ++ receiver, the 'replay-log-creation-time', the ++ 'replay-log-aged-time', or the most recent publisher ++ boot time."; ++ } ++ refine "target/stream/stream-filter/within-subscription" { ++ description ++ "Filter applied to the subscription. If the ++ 'stream-filter-name' is populated, the filter in the ++ subscription came from the 'filters' container. ++ Otherwise, it is populated in-line as part of the ++ subscription."; ++ } ++ augment "target/stream" { ++ description ++ "This augmentation adds additional parameters specific to a ++ 'subscription-started' notification."; ++ leaf replay-previous-event-time { ++ when '../replay-start-time'; ++ if-feature "replay"; ++ type yang:date-and-time; ++ description ++ "If there is at least one event in the replay buffer ++ prior to 'replay-start-time', this gives the time of ++ the event generated immediately prior to the ++ 'replay-start-time'. ++ ++ If a receiver previously received event records for ++ this configured subscription, it can compare this time ++ to the last event record previously received. If the ++ two are not the same (perhaps due to a reboot), then a ++ dynamic replay can be initiated to acquire any missing ++ event records."; ++ } ++ } ++ } ++ } ++ ++ notification subscription-suspended { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a suspension of the ++ subscription by the publisher has occurred. No further ++ notifications will be sent until the subscription resumes. ++ This notification shall only be sent to receivers of a ++ subscription; it does not constitute a general-purpose ++ notification."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ leaf reason { ++ type identityref { ++ base subscription-suspended-reason; ++ } ++ mandatory true; ++ description ++ "Identifies the condition that resulted in the suspension."; ++ } ++ } ++ ++ notification subscription-terminated { ++ sn:subscription-state-notification; ++ description ++ "This notification indicates that a subscription has been ++ terminated."; ++ leaf id { ++ type subscription-id; ++ mandatory true; ++ description ++ "This references the affected subscription."; ++ } ++ leaf reason { ++ type identityref { ++ base subscription-terminated-reason; ++ } ++ mandatory true; ++ description ++ "Identifies the condition that resulted in the termination."; ++ } ++ } ++ ++ /* ++ * DATA NODES ++ */ ++ ++ container streams { ++ config false; ++ description ++ "Contains information on the built-in event streams provided by ++ the publisher."; ++ list stream { ++ key "name"; ++ description ++ "Identifies the built-in event streams that are supported by ++ the publisher."; ++ leaf name { ++ type string; ++ description ++ "A handle for a system-provided event stream made up of a ++ sequential set of event records, each of which is ++ characterized by its own domain and semantics."; ++ } ++ leaf description { ++ type string; ++ description ++ "A description of the event stream, including such ++ information as the type of event records that are ++ available in this event stream."; ++ } ++ leaf replay-support { ++ if-feature "replay"; ++ type empty; ++ description ++ "Indicates that event record replay is available on this ++ event stream."; ++ } ++ leaf replay-log-creation-time { ++ when '../replay-support'; ++ if-feature "replay"; ++ type yang:date-and-time; ++ mandatory true; ++ description ++ "The timestamp of the creation of the log used to support ++ the replay function on this event stream. This time ++ might be earlier than the earliest available information ++ contained in the log. This object is updated if the log ++ resets for some reason."; ++ } ++ leaf replay-log-aged-time { ++ when '../replay-support'; ++ if-feature "replay"; ++ type yang:date-and-time; ++ description ++ "The timestamp associated with the last event record that ++ has been aged out of the log. This timestamp identifies ++ how far back in history this replay log extends, if it ++ doesn't extend back to the 'replay-log-creation-time'. ++ This object MUST be present if replay is supported and any ++ event records have been aged out of the log."; ++ } ++ } ++ } ++ container filters { ++ description ++ "Contains a list of configurable filters that can be applied to ++ subscriptions. This facilitates the reuse of complex filters ++ once defined."; ++ list stream-filter { ++ key "name"; ++ description ++ "A list of preconfigured filters that can be applied to ++ subscriptions."; ++ leaf name { ++ type string; ++ description ++ "A name to differentiate between filters."; ++ } ++ uses stream-filter-elements; ++ } ++ } ++ container subscriptions { ++ description ++ "Contains the list of currently active subscriptions, i.e., ++ subscriptions that are currently in effect, used for ++ subscription management and monitoring purposes. This ++ includes subscriptions that have been set up via ++ RPC primitives as well as subscriptions that have been ++ established via configuration."; ++ list subscription { ++ key "id"; ++ description ++ "The identity and specific parameters of a subscription. ++ Subscriptions in this list can be created using a control ++ channel or RPC or can be established through configuration. ++ ++ If the 'kill-subscription' RPC or configuration operations ++ are used to delete a subscription, a ++ 'subscription-terminated' message is sent to any active or ++ suspended receivers."; ++ leaf id { ++ type subscription-id; ++ description ++ "Identifier of a subscription; unique in a given ++ publisher."; ++ } ++ uses subscription-policy { ++ refine "target/stream/stream" { ++ description ++ "Indicates the event stream to be considered for this ++ subscription. If an event stream has been removed ++ and can no longer be referenced by an active ++ subscription, send a 'subscription-terminated' ++ notification with 'stream-unavailable' as the reason. ++ If a configured subscription refers to a nonexistent ++ event stream, move that subscription to the ++ 'invalid' state."; ++ } ++ refine "transport" { ++ description ++ "For a configured subscription, this leaf specifies the ++ transport used to deliver messages destined for all ++ receivers of that subscription. This object is ++ mandatory for subscriptions in the configuration ++ datastore. This object (1) is not mandatory for dynamic ++ subscriptions in the operational state datastore and ++ (2) should not be present for other types of dynamic ++ subscriptions."; ++ } ++ augment "target/stream" { ++ description ++ "Enables objects to be added to a configured stream ++ subscription."; ++ leaf configured-replay { ++ if-feature "configured"; ++ if-feature "replay"; ++ type empty; ++ description ++ "The presence of this leaf indicates that replay for ++ the configured subscription should start at the ++ earliest time in the event log or at the publisher ++ boot time, whichever is later."; ++ } ++ } ++ } ++ choice notification-message-origin { ++ if-feature "configured"; ++ description ++ "Identifies the egress interface on the publisher ++ from which notification messages are to be sent."; ++ case interface-originated { ++ description ++ "When notification messages are to egress a specific, ++ designated interface on the publisher."; ++ leaf source-interface { ++ if-feature "interface-designation"; ++ type if:interface-ref; ++ description ++ "References the interface for notification messages."; ++ } ++ } ++ case address-originated { ++ description ++ "When notification messages are to depart from a ++ publisher using a specific originating address and/or ++ routing context information."; ++ leaf source-vrf { ++ if-feature "supports-vrf"; ++ type leafref { ++ path "/ni:network-instances/ni:network-instance/ni:name"; ++ } ++ description ++ "VRF from which notification messages should egress a ++ publisher."; ++ } ++ leaf source-address { ++ type inet:ip-address-no-zone; ++ description ++ "The source address for the notification messages. ++ If a source VRF exists but this object doesn't, a ++ publisher's default address for that VRF must ++ be used."; ++ } ++ } ++ } ++ leaf configured-subscription-state { ++ if-feature "configured"; ++ type enumeration { ++ enum valid { ++ value 1; ++ description ++ "The subscription is supportable with its current ++ parameters."; ++ } ++ enum invalid { ++ value 2; ++ description ++ "The subscription as a whole is unsupportable with its ++ current parameters."; ++ } ++ enum concluded { ++ value 3; ++ description ++ "A subscription is inactive, as it has hit a ++ stop time. It no longer has receivers in the ++ 'active' or 'suspended' state, but the subscription ++ has not yet been removed from configuration."; ++ } ++ } ++ config false; ++ description ++ "The presence of this leaf indicates that the subscription ++ originated from configuration, not through a control ++ channel or RPC. The value indicates the state of the ++ subscription as established by the publisher."; ++ } ++ container receivers { ++ description ++ "Set of receivers in a subscription."; ++ list receiver { ++ key "name"; ++ min-elements 1; ++ description ++ "A host intended as a recipient for the notification ++ messages of a subscription. For configured ++ subscriptions, transport-specific network parameters ++ (or a leafref to those parameters) may be augmented to a ++ specific receiver in this list."; ++ leaf name { ++ type string; ++ description ++ "Identifies a unique receiver for a subscription."; ++ } ++ leaf sent-event-records { ++ type yang:zero-based-counter64; ++ config false; ++ description ++ "The number of event records sent to the receiver. The ++ count is initialized when a dynamic subscription is ++ established or when a configured receiver ++ transitions to the 'valid' state."; ++ } ++ leaf excluded-event-records { ++ type yang:zero-based-counter64; ++ config false; ++ description ++ "The number of event records explicitly removed via ++ either an event stream filter or an access control ++ filter so that they are not passed to a receiver. ++ This count is set to zero each time ++ 'sent-event-records' is initialized."; ++ } ++ leaf state { ++ type enumeration { ++ enum active { ++ value 1; ++ description ++ "The receiver is currently being sent any ++ applicable notification messages for the ++ subscription."; ++ } ++ enum suspended { ++ value 2; ++ description ++ "The receiver state is 'suspended', so the ++ publisher is currently unable to provide ++ notification messages for the subscription."; ++ } ++ enum connecting { ++ value 3; ++ if-feature "configured"; ++ description ++ "A subscription has been configured, but a ++ 'subscription-started' subscription state change ++ notification needs to be successfully received ++ before notification messages are sent. ++ ++ If the 'reset' action is invoked for a receiver of ++ an active configured subscription, the state ++ must be moved to 'connecting'."; ++ } ++ enum disconnected { ++ value 4; ++ if-feature "configured"; ++ description ++ "A subscription has failed to send a ++ 'subscription-started' state change to the ++ receiver. Additional connection attempts are not ++ currently being made."; ++ } ++ } ++ config false; ++ mandatory true; ++ description ++ "Specifies the state of a subscription from the ++ perspective of a particular receiver. With this ++ information, it is possible to determine whether a ++ publisher is currently generating notification ++ messages intended for that receiver."; ++ } ++ action reset { ++ if-feature "configured"; ++ description ++ "Allows the reset of this configured subscription's ++ receiver to the 'connecting' state. This enables the ++ connection process to be reinitiated."; ++ output { ++ leaf time { ++ type yang:date-and-time; ++ mandatory true; ++ description ++ "Time at which a publisher returned the receiver to ++ the 'connecting' state."; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++} +diff --git a/tests/yang/ietf-yang-patch@2017-02-22.yang b/tests/yang/ietf-yang-patch@2017-02-22.yang +new file mode 100644 +index 0000000..d0029ed +--- /dev/null ++++ b/tests/yang/ietf-yang-patch@2017-02-22.yang +@@ -0,0 +1,390 @@ ++module ietf-yang-patch { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-yang-patch"; ++ prefix "ypatch"; ++ ++ import ietf-restconf { prefix rc; } ++ ++ organization ++ "IETF NETCONF (Network Configuration) Working Group"; ++ ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Andy Bierman ++ ++ ++ Author: Martin Bjorklund ++ ++ ++ Author: Kent Watsen ++ "; ++ ++ description ++ "This module contains conceptual YANG specifications ++ for the YANG Patch and YANG Patch Status data structures. ++ ++ Note that the YANG definitions within this module do not ++ represent configuration data of any kind. ++ The YANG grouping statements provide a normative syntax ++ for XML and JSON message-encoding purposes. ++ ++ Copyright (c) 2017 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject ++ to the license terms contained in, the Simplified BSD License ++ set forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (http://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8072; see ++ the RFC itself for full legal notices."; ++ ++ revision 2017-02-22 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 8072: YANG Patch Media Type."; ++ } ++ ++ typedef target-resource-offset { ++ type string; ++ description ++ "Contains a data resource identifier string representing ++ a sub-resource within the target resource. ++ The document root for this expression is the ++ target resource that is specified in the ++ protocol operation (e.g., the URI for the PATCH request). ++ ++ This string is encoded according to the same rules as those ++ for a data resource identifier in a RESTCONF request URI."; ++ reference ++ "RFC 8040, Section 3.5.3."; ++ } ++ ++ rc:yang-data "yang-patch" { ++ uses yang-patch; ++ } ++ ++ rc:yang-data "yang-patch-status" { ++ uses yang-patch-status; ++ } ++ ++ grouping yang-patch { ++ ++ description ++ "A grouping that contains a YANG container representing the ++ syntax and semantics of a YANG Patch edit request message."; ++ ++ container yang-patch { ++ description ++ "Represents a conceptual sequence of datastore edits, ++ called a patch. Each patch is given a client-assigned ++ patch identifier. Each edit MUST be applied ++ in ascending order, and all edits MUST be applied. ++ If any errors occur, then the target datastore MUST NOT ++ be changed by the YANG Patch operation. ++ ++ It is possible for a datastore constraint violation to occur ++ due to any node in the datastore, including nodes not ++ included in the 'edit' list. Any validation errors MUST ++ be reported in the reply message."; ++ ++ reference ++ "RFC 7950, Section 8.3."; ++ ++ leaf patch-id { ++ type string; ++ mandatory true; ++ description ++ "An arbitrary string provided by the client to identify ++ the entire patch. Error messages returned by the server ++ that pertain to this patch will be identified by this ++ 'patch-id' value. A client SHOULD attempt to generate ++ unique 'patch-id' values to distinguish between ++ transactions from multiple clients in any audit logs ++ maintained by the server."; ++ } ++ ++ leaf comment { ++ type string; ++ description ++ "An arbitrary string provided by the client to describe ++ the entire patch. This value SHOULD be present in any ++ audit logging records generated by the server for the ++ patch."; ++ } ++ ++ list edit { ++ key edit-id; ++ ordered-by user; ++ ++ description ++ "Represents one edit within the YANG Patch request message. ++ The 'edit' list is applied in the following manner: ++ ++ - The first edit is conceptually applied to a copy ++ of the existing target datastore, e.g., the ++ running configuration datastore. ++ - Each ascending edit is conceptually applied to ++ the result of the previous edit(s). ++ - After all edits have been successfully processed, ++ the result is validated according to YANG constraints. ++ - If successful, the server will attempt to apply ++ the result to the target datastore."; ++ ++ leaf edit-id { ++ type string; ++ description ++ "Arbitrary string index for the edit. ++ Error messages returned by the server that pertain ++ to a specific edit will be identified by this value."; ++ } ++ ++ leaf operation { ++ type enumeration { ++ enum create { ++ description ++ "The target data node is created using the supplied ++ value, only if it does not already exist. The ++ 'target' leaf identifies the data node to be ++ created, not the parent data node."; ++ } ++ enum delete { ++ description ++ "Delete the target node, only if the data resource ++ currently exists; otherwise, return an error."; ++ } ++ ++ enum insert { ++ description ++ "Insert the supplied value into a user-ordered ++ list or leaf-list entry. The target node must ++ represent a new data resource. If the 'where' ++ parameter is set to 'before' or 'after', then ++ the 'point' parameter identifies the insertion ++ point for the target node."; ++ } ++ enum merge { ++ description ++ "The supplied value is merged with the target data ++ node."; ++ } ++ enum move { ++ description ++ "Move the target node. Reorder a user-ordered ++ list or leaf-list. The target node must represent ++ an existing data resource. If the 'where' parameter ++ is set to 'before' or 'after', then the 'point' ++ parameter identifies the insertion point to move ++ the target node."; ++ } ++ enum replace { ++ description ++ "The supplied value is used to replace the target ++ data node."; ++ } ++ enum remove { ++ description ++ "Delete the target node if it currently exists."; ++ } ++ } ++ mandatory true; ++ description ++ "The datastore operation requested for the associated ++ 'edit' entry."; ++ } ++ ++ leaf target { ++ type target-resource-offset; ++ mandatory true; ++ description ++ "Identifies the target data node for the edit ++ operation. If the target has the value '/', then ++ the target data node is the target resource. ++ The target node MUST identify a data resource, ++ not the datastore resource."; ++ } ++ ++ leaf point { ++ when "(../operation = 'insert' or ../operation = 'move')" ++ + "and (../where = 'before' or ../where = 'after')" { ++ description ++ "This leaf only applies for 'insert' or 'move' ++ operations, before or after an existing entry."; ++ } ++ type target-resource-offset; ++ description ++ "The absolute URL path for the data node that is being ++ used as the insertion point or move point for the ++ target of this 'edit' entry."; ++ } ++ ++ leaf where { ++ when "../operation = 'insert' or ../operation = 'move'" { ++ description ++ "This leaf only applies for 'insert' or 'move' ++ operations."; ++ } ++ type enumeration { ++ enum before { ++ description ++ "Insert or move a data node before the data resource ++ identified by the 'point' parameter."; ++ } ++ enum after { ++ description ++ "Insert or move a data node after the data resource ++ identified by the 'point' parameter."; ++ } ++ ++ enum first { ++ description ++ "Insert or move a data node so it becomes ordered ++ as the first entry."; ++ } ++ enum last { ++ description ++ "Insert or move a data node so it becomes ordered ++ as the last entry."; ++ } ++ } ++ default last; ++ description ++ "Identifies where a data resource will be inserted ++ or moved. YANG only allows these operations for ++ list and leaf-list data nodes that are ++ 'ordered-by user'."; ++ } ++ ++ anydata value { ++ when "../operation = 'create' " ++ + "or ../operation = 'merge' " ++ + "or ../operation = 'replace' " ++ + "or ../operation = 'insert'" { ++ description ++ "The anydata 'value' is only used for 'create', ++ 'merge', 'replace', and 'insert' operations."; ++ } ++ description ++ "Value used for this edit operation. The anydata 'value' ++ contains the target resource associated with the ++ 'target' leaf. ++ ++ For example, suppose the target node is a YANG container ++ named foo: ++ ++ container foo { ++ leaf a { type string; } ++ leaf b { type int32; } ++ } ++ ++ The 'value' node contains one instance of foo: ++ ++ ++ ++ some value ++ 42 ++ ++ ++ "; ++ } ++ } ++ } ++ ++ } // grouping yang-patch ++ ++ grouping yang-patch-status { ++ ++ description ++ "A grouping that contains a YANG container representing the ++ syntax and semantics of a YANG Patch Status response ++ message."; ++ ++ container yang-patch-status { ++ description ++ "A container representing the response message sent by the ++ server after a YANG Patch edit request message has been ++ processed."; ++ ++ leaf patch-id { ++ type string; ++ mandatory true; ++ description ++ "The 'patch-id' value used in the request."; ++ } ++ ++ choice global-status { ++ description ++ "Report global errors or complete success. ++ If there is no case selected, then errors ++ are reported in the 'edit-status' container."; ++ ++ case global-errors { ++ uses rc:errors; ++ description ++ "This container will be present if global errors that ++ are unrelated to a specific edit occurred."; ++ } ++ leaf ok { ++ type empty; ++ description ++ "This leaf will be present if the request succeeded ++ and there are no errors reported in the 'edit-status' ++ container."; ++ } ++ } ++ ++ container edit-status { ++ description ++ "This container will be present if there are ++ edit-specific status responses to report. ++ If all edits succeeded and the 'global-status' ++ returned is 'ok', then a server MAY omit this ++ container."; ++ ++ list edit { ++ key edit-id; ++ ++ description ++ "Represents a list of status responses, ++ corresponding to edits in the YANG Patch ++ request message. If an 'edit' entry was ++ skipped or not reached by the server, ++ then this list will not contain a corresponding ++ entry for that edit."; ++ ++ leaf edit-id { ++ type string; ++ description ++ "Response status is for the 'edit' list entry ++ with this 'edit-id' value."; ++ } ++ ++ choice edit-status-choice { ++ description ++ "A choice between different types of status ++ responses for each 'edit' entry."; ++ leaf ok { ++ type empty; ++ description ++ "This 'edit' entry was invoked without any ++ errors detected by the server associated ++ with this edit."; ++ } ++ case errors { ++ uses rc:errors; ++ description ++ "The server detected errors associated with the ++ edit identified by the same 'edit-id' value."; ++ } ++ } ++ } ++ } ++ } ++ } // grouping yang-patch-status ++ ++} +diff --git a/tests/yang/ietf-yang-push@2019-09-09.yang b/tests/yang/ietf-yang-push@2019-09-09.yang +new file mode 100644 +index 0000000..ea38fb3 +--- /dev/null ++++ b/tests/yang/ietf-yang-push@2019-09-09.yang +@@ -0,0 +1,797 @@ ++module ietf-yang-push { ++ yang-version 1.1; ++ namespace "urn:ietf:params:xml:ns:yang:ietf-yang-push"; ++ prefix yp; ++ ++ import ietf-yang-types { ++ prefix yang; ++ reference ++ "RFC 6991: Common YANG Data Types"; ++ } ++ import ietf-subscribed-notifications { ++ prefix sn; ++ reference ++ "RFC 8639: Subscription to YANG Notifications"; ++ } ++ import ietf-datastores { ++ prefix ds; ++ reference ++ "RFC 8342: Network Management Datastore Architecture (NMDA)"; ++ } ++ import ietf-restconf { ++ prefix rc; ++ reference ++ "RFC 8040: RESTCONF Protocol"; ++ } ++ import ietf-yang-patch { ++ prefix ypatch; ++ reference ++ "RFC 8072: YANG Patch Media Type"; ++ } ++ ++ organization ++ "IETF NETCONF (Network Configuration) Working Group"; ++ contact ++ "WG Web: ++ WG List: ++ ++ Author: Alexander Clemm ++ ++ ++ Author: Eric Voit ++ "; ++ ++ description ++ "This module contains YANG specifications for YANG-Push. ++ ++ The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL ++ NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', ++ 'MAY', and 'OPTIONAL' in this document are to be interpreted as ++ described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, ++ they appear in all capitals, as shown here. ++ ++ Copyright (c) 2019 IETF Trust and the persons identified as ++ authors of the code. All rights reserved. ++ ++ Redistribution and use in source and binary forms, with or ++ without modification, is permitted pursuant to, and subject to ++ the license terms contained in, the Simplified BSD License set ++ forth in Section 4.c of the IETF Trust's Legal Provisions ++ Relating to IETF Documents ++ (https://trustee.ietf.org/license-info). ++ ++ This version of this YANG module is part of RFC 8641; see the ++ RFC itself for full legal notices."; ++ ++ revision 2019-09-09 { ++ description ++ "Initial revision."; ++ reference ++ "RFC 8641: Subscriptions to YANG Datastores"; ++ } ++ ++ /* ++ * FEATURES ++ */ ++ ++ feature on-change { ++ description ++ "This feature indicates that on-change triggered subscriptions ++ are supported."; ++ } ++ ++ /* ++ * IDENTITIES ++ */ ++ ++ /* Error type identities for datastore subscription */ ++ ++ identity resync-subscription-error { ++ description ++ "Problem found while attempting to fulfill a ++ 'resync-subscription' RPC request."; ++ } ++ ++ identity cant-exclude { ++ base sn:establish-subscription-error; ++ description ++ "Unable to remove the set of 'excluded-change' parameters. ++ This means that the publisher is unable to restrict ++ 'push-change-update' notifications to just the change types ++ requested for this subscription."; ++ } ++ ++ identity datastore-not-subscribable { ++ base sn:establish-subscription-error; ++ base sn:subscription-terminated-reason; ++ description ++ "This is not a subscribable datastore."; ++ } ++ ++ identity no-such-subscription-resync { ++ base resync-subscription-error; ++ description ++ "The referenced subscription doesn't exist. This may be as a ++ result of a nonexistent subscription ID, an ID that belongs to ++ another subscriber, or an ID for a configured subscription."; ++ } ++ ++ identity on-change-unsupported { ++ base sn:establish-subscription-error; ++ description ++ "On-change is not supported for any objects that are ++ selectable by this filter."; ++ } ++ ++ identity on-change-sync-unsupported { ++ base sn:establish-subscription-error; ++ description ++ "Neither 'sync-on-start' nor resynchronization is supported for ++ this subscription. This error will be used for two reasons: ++ (1) if an 'establish-subscription' RPC includes ++ 'sync-on-start' but the publisher can't support sending a ++ 'push-update' for this subscription for reasons other than ++ 'on-change-unsupported' or 'sync-too-big' ++ (2) if the 'resync-subscription' RPC is invoked for either an ++ existing periodic subscription or an on-change subscription ++ that can't support resynchronization."; ++ } ++ ++ identity period-unsupported { ++ base sn:establish-subscription-error; ++ base sn:modify-subscription-error; ++ base sn:subscription-suspended-reason; ++ description ++ "The requested time period or 'dampening-period' is too short. ++ This can be for both periodic and on-change subscriptions ++ (with or without dampening). Hints suggesting alternative ++ periods may be returned as supplemental information."; ++ } ++ ++ identity update-too-big { ++ base sn:establish-subscription-error; ++ base sn:modify-subscription-error; ++ base sn:subscription-suspended-reason; ++ description ++ "Periodic or on-change push update data trees exceed a maximum ++ size limit. Hints on the estimated size of what was too big ++ may be returned as supplemental information."; ++ } ++ ++ identity sync-too-big { ++ base sn:establish-subscription-error; ++ base sn:modify-subscription-error; ++ base resync-subscription-error; ++ base sn:subscription-suspended-reason; ++ description ++ "The 'sync-on-start' or resynchronization data tree exceeds a ++ maximum size limit. Hints on the estimated size of what was ++ too big may be returned as supplemental information."; ++ } ++ ++ identity unchanging-selection { ++ base sn:establish-subscription-error; ++ base sn:modify-subscription-error; ++ base sn:subscription-terminated-reason; ++ description ++ "The selection filter is unlikely to ever select data tree ++ nodes. This means that based on the subscriber's current ++ access rights, the publisher recognizes that the selection ++ filter is unlikely to ever select data tree nodes that change. ++ Examples for this might be that the node or subtree doesn't ++ exist, read access is not permitted for a receiver, or static ++ objects that only change at reboot have been chosen."; ++ } ++ ++ /* ++ * TYPE DEFINITIONS ++ */ ++ ++ typedef change-type { ++ type enumeration { ++ enum create { ++ description ++ "A change that refers to the creation of a new ++ datastore node."; ++ } ++ enum delete { ++ description ++ "A change that refers to the deletion of a ++ datastore node."; ++ } ++ enum insert { ++ description ++ "A change that refers to the insertion of a new ++ user-ordered datastore node."; ++ } ++ enum move { ++ description ++ "A change that refers to a reordering of the target ++ datastore node."; ++ } ++ enum replace { ++ description ++ "A change that refers to a replacement of the target ++ datastore node's value."; ++ } ++ } ++ description ++ "Specifies different types of datastore changes. ++ ++ This type is based on the edit operations defined for ++ YANG Patch, with the difference that it is valid for a ++ receiver to process an update record that performs a ++ 'create' operation on a datastore node the receiver believes ++ exists or to process a delete on a datastore node the ++ receiver believes is missing."; ++ reference ++ "RFC 8072: YANG Patch Media Type, Section 2.5"; ++ } ++ ++ typedef selection-filter-ref { ++ type leafref { ++ path "/sn:filters/yp:selection-filter/yp:filter-id"; ++ } ++ description ++ "This type is used to reference a selection filter."; ++ } ++ ++ typedef centiseconds { ++ type uint32; ++ description ++ "A period of time, measured in units of 0.01 seconds."; ++ } ++ ++ /* ++ * GROUP DEFINITIONS ++ */ ++ ++ grouping datastore-criteria { ++ description ++ "A grouping to define criteria for which selected objects from ++ a targeted datastore should be included in push updates."; ++ leaf datastore { ++ type identityref { ++ base ds:datastore; ++ } ++ mandatory true; ++ description ++ "Datastore from which to retrieve data."; ++ } ++ uses selection-filter-objects; ++ } ++ ++ grouping selection-filter-types { ++ description ++ "This grouping defines the types of selectors for objects ++ from a datastore."; ++ choice filter-spec { ++ description ++ "The content filter specification for this request."; ++ anydata datastore-subtree-filter { ++ if-feature "sn:subtree"; ++ description ++ "This parameter identifies the portions of the ++ target datastore to retrieve."; ++ reference ++ "RFC 6241: Network Configuration Protocol (NETCONF), ++ Section 6"; ++ } ++ leaf datastore-xpath-filter { ++ if-feature "sn:xpath"; ++ type yang:xpath1.0; ++ description ++ "This parameter contains an XPath expression identifying ++ the portions of the target datastore to retrieve. ++ ++ If the expression returns a node set, all nodes in the ++ node set are selected by the filter. Otherwise, if the ++ expression does not return a node set, the filter ++ doesn't select any nodes. ++ ++ The expression is evaluated in the following XPath ++ context: ++ ++ o The set of namespace declarations is the set of prefix ++ and namespace pairs for all YANG modules implemented ++ by the server, where the prefix is the YANG module ++ name and the namespace is as defined by the ++ 'namespace' statement in the YANG module. ++ ++ If the leaf is encoded in XML, all namespace ++ declarations in scope on the 'stream-xpath-filter' ++ leaf element are added to the set of namespace ++ declarations. If a prefix found in the XML is ++ already present in the set of namespace declarations, ++ the namespace in the XML is used. ++ ++ o The set of variable bindings is empty. ++ ++ o The function library is comprised of the core ++ function library and the XPath functions defined in ++ Section 10 in RFC 7950. ++ ++ o The context node is the root node of the target ++ datastore."; ++ reference ++ "XML Path Language (XPath) Version 1.0 ++ (https://www.w3.org/TR/1999/REC-xpath-19991116) ++ RFC 7950: The YANG 1.1 Data Modeling Language, ++ Section 10"; ++ } ++ } ++ } ++ ++ grouping selection-filter-objects { ++ description ++ "This grouping defines a selector for objects from a ++ datastore."; ++ choice selection-filter { ++ description ++ "The source of the selection filter applied to the ++ subscription. This will either (1) come referenced from a ++ global list or (2) be provided in the subscription itself."; ++ case by-reference { ++ description ++ "Incorporates a filter that has been configured ++ separately."; ++ leaf selection-filter-ref { ++ type selection-filter-ref; ++ mandatory true; ++ description ++ "References an existing selection filter that is to be ++ applied to the subscription."; ++ } ++ } ++ case within-subscription { ++ description ++ "A local definition allows a filter to have the same ++ lifecycle as the subscription."; ++ uses selection-filter-types; ++ } ++ } ++ } ++ ++ grouping update-policy-modifiable { ++ description ++ "This grouping describes the datastore-specific subscription ++ conditions that can be changed during the lifetime of the ++ subscription."; ++ choice update-trigger { ++ description ++ "Defines necessary conditions for sending an event record to ++ the subscriber."; ++ case periodic { ++ container periodic { ++ presence "indicates a periodic subscription"; ++ description ++ "The publisher is requested to periodically notify the ++ receiver regarding the current values of the datastore ++ as defined by the selection filter."; ++ leaf period { ++ type centiseconds; ++ mandatory true; ++ description ++ "Duration of time that should occur between periodic ++ push updates, in units of 0.01 seconds."; ++ } ++ leaf anchor-time { ++ type yang:date-and-time; ++ description ++ "Designates a timestamp before or after which a series ++ of periodic push updates are determined. The next ++ update will take place at a point in time that is a ++ multiple of a period from the 'anchor-time'. ++ For example, for an 'anchor-time' that is set for the ++ top of a particular minute and a period interval of a ++ minute, updates will be sent at the top of every ++ minute that this subscription is active."; ++ } ++ } ++ } ++ case on-change { ++ if-feature "on-change"; ++ container on-change { ++ presence "indicates an on-change subscription"; ++ description ++ "The publisher is requested to notify the receiver ++ regarding changes in values in the datastore subset as ++ defined by a selection filter."; ++ leaf dampening-period { ++ type centiseconds; ++ default "0"; ++ description ++ "Specifies the minimum interval between the assembly of ++ successive update records for a single receiver of a ++ subscription. Whenever subscribed objects change and ++ a dampening-period interval (which may be zero) has ++ elapsed since the previous update record creation for ++ a receiver, any subscribed objects and properties ++ that have changed since the previous update record ++ will have their current values marshalled and placed ++ in a new update record."; ++ } ++ } ++ } ++ } ++ } ++ ++ grouping update-policy { ++ description ++ "This grouping describes the datastore-specific subscription ++ conditions of a subscription."; ++ uses update-policy-modifiable { ++ augment "update-trigger/on-change/on-change" { ++ description ++ "Includes objects that are not modifiable once a ++ subscription is established."; ++ leaf sync-on-start { ++ type boolean; ++ default "true"; ++ description ++ "When this object is set to 'false', (1) it restricts an ++ on-change subscription from sending 'push-update' ++ notifications and (2) pushing a full selection per the ++ terms of the selection filter MUST NOT be done for ++ this subscription. Only updates about changes ++ (i.e., only 'push-change-update' notifications) ++ are sent. When set to 'true' (the default behavior), ++ in order to facilitate a receiver's synchronization, ++ a full update is sent, via a 'push-update' notification, ++ when the subscription starts. After that, ++ 'push-change-update' notifications are exclusively sent, ++ unless the publisher chooses to resync the subscription ++ via a new 'push-update' notification."; ++ } ++ leaf-list excluded-change { ++ type change-type; ++ description ++ "Used to restrict which changes trigger an update. For ++ example, if a 'replace' operation is excluded, only the ++ creation and deletion of objects are reported."; ++ } ++ } ++ } ++ } ++ ++ grouping hints { ++ description ++ "Parameters associated with an error for a subscription ++ made upon a datastore."; ++ leaf period-hint { ++ type centiseconds; ++ description ++ "Returned when the requested time period is too short. This ++ hint can assert a viable period for either a periodic push ++ cadence or an on-change dampening interval."; ++ } ++ leaf filter-failure-hint { ++ type string; ++ description ++ "Information describing where and/or why a provided filter ++ was unsupportable for a subscription."; ++ } ++ leaf object-count-estimate { ++ type uint32; ++ description ++ "If there are too many objects that could potentially be ++ returned by the selection filter, this identifies the ++ estimate of the number of objects that the filter would ++ potentially pass."; ++ } ++ leaf object-count-limit { ++ type uint32; ++ description ++ "If there are too many objects that could be returned by ++ the selection filter, this identifies the upper limit of ++ the publisher's ability to service this subscription."; ++ } ++ leaf kilobytes-estimate { ++ type uint32; ++ description ++ "If the returned information could be beyond the capacity ++ of the publisher, this would identify the estimated ++ data size that could result from this selection filter."; ++ } ++ leaf kilobytes-limit { ++ type uint32; ++ description ++ "If the returned information would be beyond the capacity ++ of the publisher, this identifies the upper limit of the ++ publisher's ability to service this subscription."; ++ } ++ } ++ ++ /* ++ * RPCs ++ */ ++ ++ rpc resync-subscription { ++ if-feature "on-change"; ++ description ++ "This RPC allows a subscriber of an active on-change ++ subscription to request a full push of objects. ++ ++ A successful invocation results in a 'push-update' of all ++ datastore nodes that the subscriber is permitted to access. ++ This RPC can only be invoked on the same session on which the ++ subscription is currently active. In the case of an error, a ++ 'resync-subscription-error' is sent as part of an error ++ response."; ++ input { ++ leaf id { ++ type sn:subscription-id; ++ mandatory true; ++ description ++ "Identifier of the subscription that is to be resynced."; ++ } ++ } ++ } ++ ++ rc:yang-data resync-subscription-error { ++ container resync-subscription-error { ++ description ++ "If a 'resync-subscription' RPC fails, the subscription is ++ not resynced and the RPC error response MUST indicate the ++ reason for this failure. This yang-data MAY be inserted as ++ structured data in a subscription's RPC error response ++ to indicate the reason for the failure."; ++ leaf reason { ++ type identityref { ++ base resync-subscription-error; ++ } ++ mandatory true; ++ description ++ "Indicates the reason why the publisher has declined a ++ request for subscription resynchronization."; ++ } ++ uses hints; ++ } ++ } ++ ++ augment "/sn:establish-subscription/sn:input" { ++ description ++ "This augmentation adds additional subscription parameters ++ that apply specifically to datastore updates to RPC input."; ++ uses update-policy; ++ } ++ ++ augment "/sn:establish-subscription/sn:input/sn:target" { ++ description ++ "This augmentation adds the datastore as a valid target ++ for the subscription to RPC input."; ++ case datastore { ++ description ++ "Information specifying the parameters of a request for a ++ datastore subscription."; ++ uses datastore-criteria; ++ } ++ } ++ ++ rc:yang-data establish-subscription-datastore-error-info { ++ container establish-subscription-datastore-error-info { ++ description ++ "If any 'establish-subscription' RPC parameters are ++ unsupportable against the datastore, a subscription is not ++ created and the RPC error response MUST indicate the reason ++ why the subscription failed to be created. This yang-data ++ MAY be inserted as structured data in a subscription's ++ RPC error response to indicate the reason for the failure. ++ This yang-data MUST be inserted if hints are to be provided ++ back to the subscriber."; ++ leaf reason { ++ type identityref { ++ base sn:establish-subscription-error; ++ } ++ description ++ "Indicates the reason why the subscription has failed to ++ be created to a targeted datastore."; ++ } ++ uses hints; ++ } ++ } ++ ++ augment "/sn:modify-subscription/sn:input" { ++ description ++ "This augmentation adds additional subscription parameters ++ specific to datastore updates."; ++ uses update-policy-modifiable; ++ } ++ ++ augment "/sn:modify-subscription/sn:input/sn:target" { ++ description ++ "This augmentation adds the datastore as a valid target ++ for the subscription to RPC input."; ++ case datastore { ++ description ++ "Information specifying the parameters of a request for a ++ datastore subscription."; ++ uses datastore-criteria; ++ } ++ } ++ ++ rc:yang-data modify-subscription-datastore-error-info { ++ container modify-subscription-datastore-error-info { ++ description ++ "This yang-data MAY be provided as part of a subscription's ++ RPC error response when there is a failure of a ++ 'modify-subscription' RPC that has been made against a ++ datastore. This yang-data MUST be used if hints are to be ++ provided back to the subscriber."; ++ leaf reason { ++ type identityref { ++ base sn:modify-subscription-error; ++ } ++ description ++ "Indicates the reason why the subscription has failed to ++ be modified."; ++ } ++ uses hints; ++ } ++ } ++ ++ /* ++ * NOTIFICATIONS ++ */ ++ ++ notification push-update { ++ description ++ "This notification contains a push update that in turn contains ++ data subscribed to via a subscription. In the case of a ++ periodic subscription, this notification is sent for periodic ++ updates. It can also be used for synchronization updates of ++ an on-change subscription. This notification shall only be ++ sent to receivers of a subscription. It does not constitute ++ a general-purpose notification that would be subscribable as ++ part of the NETCONF event stream by any receiver."; ++ leaf id { ++ type sn:subscription-id; ++ description ++ "This references the subscription that drove the ++ notification to be sent."; ++ } ++ anydata datastore-contents { ++ description ++ "This contains the updated data. It constitutes a snapshot ++ at the time of update of the set of data that has been ++ subscribed to. The snapshot corresponds to the same ++ snapshot that would be returned in a corresponding 'get' ++ operation with the same selection filter parameters ++ applied."; ++ } ++ leaf incomplete-update { ++ type empty; ++ description ++ "This is a flag that indicates that not all datastore ++ nodes subscribed to are included with this update. In ++ other words, the publisher has failed to fulfill its full ++ subscription obligations and, despite its best efforts, is ++ providing an incomplete set of objects."; ++ } ++ } ++ ++ notification push-change-update { ++ if-feature "on-change"; ++ description ++ "This notification contains an on-change push update. This ++ notification shall only be sent to the receivers of a ++ subscription. It does not constitute a general-purpose ++ notification that would be subscribable as part of the ++ NETCONF event stream by any receiver."; ++ leaf id { ++ type sn:subscription-id; ++ description ++ "This references the subscription that drove the ++ notification to be sent."; ++ } ++ container datastore-changes { ++ description ++ "This contains the set of datastore changes of the target ++ datastore, starting at the time of the previous update, per ++ the terms of the subscription."; ++ uses ypatch:yang-patch; ++ } ++ leaf incomplete-update { ++ type empty; ++ description ++ "The presence of this object indicates that not all changes ++ that have occurred since the last update are included with ++ this update. In other words, the publisher has failed to ++ fulfill its full subscription obligations -- for example, ++ in cases where it was not able to keep up with a burst of ++ changes."; ++ } ++ } ++ ++ augment "/sn:subscription-started" { ++ description ++ "This augmentation adds datastore-specific objects to ++ the notification that a subscription has started."; ++ uses update-policy; ++ } ++ ++ augment "/sn:subscription-started/sn:target" { ++ description ++ "This augmentation allows the datastore to be included as ++ part of the notification that a subscription has started."; ++ case datastore { ++ uses datastore-criteria { ++ refine "selection-filter/within-subscription" { ++ description ++ "Specifies the selection filter and where it originated ++ from. If the 'selection-filter-ref' is populated, the ++ filter in the subscription came from the 'filters' ++ container. Otherwise, it is populated in-line as part ++ of the subscription itself."; ++ } ++ } ++ } ++ } ++ ++ augment "/sn:subscription-modified" { ++ description ++ "This augmentation adds datastore-specific objects to ++ the notification that a subscription has been modified."; ++ uses update-policy; ++ } ++ ++ augment "/sn:subscription-modified/sn:target" { ++ description ++ "This augmentation allows the datastore to be included as ++ part of the notification that a subscription has been ++ modified."; ++ case datastore { ++ uses datastore-criteria { ++ refine "selection-filter/within-subscription" { ++ description ++ "Specifies the selection filter and where it originated ++ from. If the 'selection-filter-ref' is populated, the ++ filter in the subscription came from the 'filters' ++ container. Otherwise, it is populated in-line as part ++ of the subscription itself."; ++ } ++ } ++ } ++ } ++ ++ /* ++ * DATA NODES ++ */ ++ ++ augment "/sn:filters" { ++ description ++ "This augmentation allows the datastore to be included as part ++ of the selection-filtering criteria for a subscription."; ++ list selection-filter { ++ key "filter-id"; ++ description ++ "A list of preconfigured filters that can be applied ++ to datastore subscriptions."; ++ leaf filter-id { ++ type string; ++ description ++ "An identifier to differentiate between selection ++ filters."; ++ } ++ uses selection-filter-types; ++ } ++ } ++ ++ augment "/sn:subscriptions/sn:subscription" { ++ when 'yp:datastore'; ++ description ++ "This augmentation adds objects to a subscription that are ++ specific to a datastore subscription, i.e., a subscription to ++ a stream of datastore node updates."; ++ uses update-policy; ++ } ++ ++ augment "/sn:subscriptions/sn:subscription/sn:target" { ++ description ++ "This augmentation allows the datastore to be included as ++ part of the selection-filtering criteria for a subscription."; ++ case datastore { ++ uses datastore-criteria; ++ } ++ } ++} +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0013-add-subtree-filtering-to-notifications-subscription.patch b/package/sysrepo-cpp/0013-add-subtree-filtering-to-notifications-subscription.patch new file mode 100644 index 00000000..b3924b69 --- /dev/null +++ b/package/sysrepo-cpp/0013-add-subtree-filtering-to-notifications-subscription.patch @@ -0,0 +1,414 @@ +From 9d812203b3c0550995be8d60e7b11761bdbac04d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Tue, 1 Apr 2025 21:22:04 +0200 +Subject: [PATCH 13/20] add subtree filtering to notifications subscription +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: Ie547b5f478cc8e3b09ea2f324b62854576787e1b +Signed-off-by: Mattias Walström +--- + CMakeLists.txt | 2 +- + include/sysrepo-cpp/Session.hpp | 6 +- + src/Session.cpp | 58 +++++++++-- + tests/subscriptions-dynamic.cpp | 173 ++++++++++++++++++++++++++++++-- + 4 files changed, 213 insertions(+), 26 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 01ec967..47472d2 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -82,7 +82,7 @@ if(BUILD_TESTING) + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-interfaces@2018-02-20.yang + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-ip@2018-02-22.yang + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-network-instance@2019-01-21.yang +- --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay ++ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay -e subtree + --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-yang-push@2019-09-09.yang -e on-change + ) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 255f861..0781890 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -137,17 +137,17 @@ public: + const std::optional& callbacks = std::nullopt); + + [[nodiscard]] DynamicSubscription yangPushPeriodic( +- const std::optional& xpathFilter, ++ const std::optional>& filter, + std::chrono::milliseconds periodTime, + const std::optional& anchorTime = std::nullopt, + const std::optional& stopTime = std::nullopt); + [[nodiscard]] DynamicSubscription yangPushOnChange( +- const std::optional& xpathFilter, ++ const std::optional>& filter, + const std::optional& dampeningPeriod = std::nullopt, + SyncOnStart syncOnStart = SyncOnStart::No, + const std::optional& stopTime = std::nullopt); + [[nodiscard]] DynamicSubscription subscribeNotifications( +- const std::optional& xpathFilter, ++ const std::optional>& filter, + const std::optional& stream = std::nullopt, + const std::optional& stopTime = std::nullopt, + const std::optional& startTime = std::nullopt); +diff --git a/src/Session.cpp b/src/Session.cpp +index 2c27c92..1d65d70 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -42,6 +42,37 @@ libyang::DataNode wrapSrData(std::shared_ptr sess, sr_data_t* + sr_release_data(data); + })); + } ++ ++std::optional constructXPathFilter(const std::optional>& filter) ++{ ++ if (!filter) { ++ return std::nullopt; ++ } ++ ++ if (std::holds_alternative(*filter)) { ++ return std::get(*filter); ++ } ++ ++ auto node = std::get(*filter); ++ auto value = node.releaseValue(); ++ ++ if (!value) { ++ return "/"; // select nothing, RFC 6241, 6.4.2 ++ } ++ ++ if (std::holds_alternative(*value)) { ++ char* str; ++ ++ auto filterTree = std::get(*value); ++ auto res = srsn_filter_subtree2xpath(libyang::getRawNode(filterTree), nullptr, &str); ++ std::unique_ptr strDeleter(str); // pass ownership of c-string to the deleter ++ ++ throwIfError(res, "Unable to convert subtree filter to xpath"); ++ return str; ++ } ++ ++ throw Error("Subtree filter anydata node must contain (possibly empty) libyang tree"); ++} + } + + /** +@@ -526,9 +557,9 @@ Subscription Session::onNotification( + /** + * Subscribe for receiving notifications according to 'ietf-yang-push' YANG periodic subscriptions. + * +- * Wraps `srsn_yang_push_periodic`. ++ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters. + * +- * @param xpathFilter Optional XPath that filters received notification. ++ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny + * @param periodTime Notification period. + * @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period. + * @param stopTime Optional stop time ending the notification subscription. +@@ -536,7 +567,7 @@ Subscription Session::onNotification( + * @return A YangPushSubscription handle. + */ + DynamicSubscription Session::yangPushPeriodic( +- const std::optional& xpathFilter, ++ const std::optional>& filter, + std::chrono::milliseconds periodTime, + const std::optional& anchorTime, + const std::optional& stopTime) +@@ -545,6 +576,8 @@ DynamicSubscription Session::yangPushPeriodic( + uint32_t subId; + auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; + auto anchorSpec = anchorTime ? std::optional{toTimespec(*anchorTime)} : std::nullopt; ++ auto xpathFilter = constructXPathFilter(filter); ++ + auto res = srsn_yang_push_periodic(m_sess.get(), + toDatastore(activeDatastore()), + xpathFilter ? xpathFilter->c_str() : nullptr, +@@ -561,9 +594,9 @@ DynamicSubscription Session::yangPushPeriodic( + /** + * Subscribe for receiving notifications according to 'ietf-yang-push' YANG on-change subscriptions. + * +- * Wraps `srsn_yang_push_on_change`. ++ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters. + * +- * @param xpathFilter Optional XPath that filters received notification. ++ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny + * @param dampeningPeriod Optional dampening period. + * @param syncOnStart Whether to start with a notification of the current state. + * @param stopTime Optional stop time ending the notification subscription. +@@ -571,7 +604,7 @@ DynamicSubscription Session::yangPushPeriodic( + * @return A YangPushSubscription handle. + */ + DynamicSubscription Session::yangPushOnChange( +- const std::optional& xpathFilter, ++ const std::optional>& filter, + const std::optional& dampeningPeriod, + SyncOnStart syncOnStart, + const std::optional& stopTime) +@@ -579,6 +612,8 @@ DynamicSubscription Session::yangPushOnChange( + int fd; + uint32_t subId; + auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; ++ auto xpathFilter = constructXPathFilter(filter); ++ + auto res = srsn_yang_push_on_change(m_sess.get(), + toDatastore(activeDatastore()), + xpathFilter ? xpathFilter->c_str() : nullptr, +@@ -596,11 +631,11 @@ DynamicSubscription Session::yangPushOnChange( + } + + /** +- * Subscribe for receiving notifications according to 'ietf-subscribed-notifications' ++ * Subscribe for receiving notifications according to 'ietf-subscribed-notifications'. + * +- * Wraps `srsn_subscribe. ++ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters. + * +- * @param xpathFilter Optional XPath that filters received notification. ++ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny + * @param stream Optional stream to subscribe to. + * @param stopTime Optional stop time ending the subscription. + * @param startTime Optional start time of the subscription, used for replaying stored notifications. +@@ -608,7 +643,7 @@ DynamicSubscription Session::yangPushOnChange( + * @return A YangPushSubscription handle. + */ + DynamicSubscription Session::subscribeNotifications( +- const std::optional& xpathFilter, ++ const std::optional>& filter, + const std::optional& stream, + const std::optional& stopTime, + const std::optional& startTime) +@@ -618,10 +653,11 @@ DynamicSubscription Session::subscribeNotifications( + auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; + auto startSpec = startTime ? std::optional{toTimespec(*startTime)} : std::nullopt; + struct timespec replayStartSpec; ++ auto xpathFilter = constructXPathFilter(filter); + + auto res = srsn_subscribe(m_sess.get(), + stream ? stream->c_str() : nullptr, +- xpathFilter ? xpathFilter->c_str() : nullptr, ++ xpathFilter ? xpathFilter->data() : nullptr, + stopSpec ? &stopSpec.value() : nullptr, + startSpec ? &startSpec.value() : nullptr, + false, +diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp +index a44bfba..84a0880 100644 +--- a/tests/subscriptions-dynamic.cpp ++++ b/tests/subscriptions-dynamic.cpp +@@ -28,6 +28,9 @@ + #define REQUIRE_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \ + TROMPELOEIL_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq); + ++#define REQUIRE_NAMED_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \ ++ expectations.emplace_back(TROMPELOEIL_NAMED_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq)); ++ + #define READ_NOTIFICATION(SUBSCRIPTION) \ + REQUIRE(pipeStatus((SUBSCRIPTION).fd()) == PipeStatus::DataReady); \ + (SUBSCRIPTION).processEvent(cbNotif); +@@ -276,6 +279,89 @@ TEST_CASE("Dynamic subscriptions") + const auto excMessage = "Couldn't terminate yang-push subscription with id " + std::to_string(sub->subscriptionId()) + ": SR_ERR_NOT_FOUND"; + REQUIRE_THROWS_WITH_AS(sub->terminate(), excMessage.c_str(), sysrepo::ErrorWithCode); + } ++ ++ DOCTEST_SUBCASE("Filtering") ++ { ++ std::optional sub; ++ std::vector> expectations; ++ ++ DOCTEST_SUBCASE("xpath filter") ++ { ++ sub = sess.subscribeNotifications("/test_module:ping"); ++ ++ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]); ++ } ++ ++ DOCTEST_SUBCASE("subtree filter") ++ { ++ libyang::CreatedNodes createdNodes; ++ ++ DOCTEST_SUBCASE("filter a node") ++ { ++ DOCTEST_SUBCASE("XML") ++ { ++ createdNodes = sess.getContext().newPath2( ++ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter", ++ libyang::XML{""}); ++ } ++ ++ DOCTEST_SUBCASE("JSON") ++ { ++ createdNodes = sess.getContext().newPath2( ++ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter", ++ libyang::JSON{R"({"test_module:ping": {}})"}); ++ } ++ ++ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]); ++ } ++ ++ DOCTEST_SUBCASE("filter more top level nodes") ++ { ++ DOCTEST_SUBCASE("XML") ++ { ++ createdNodes = sess.getContext().newPath2( ++ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter", ++ libyang::XML{"" ++ ""}); ++ } ++ ++ DOCTEST_SUBCASE("JSON") ++ { ++ createdNodes = sess.getContext().newPath2( ++ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter", ++ libyang::JSON{R"({ ++ "test_module:ping": {}, ++ "test_module:silent-ping": {} ++ })"}); ++ } ++ ++ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]); ++ REQUIRE_NAMED_NOTIFICATION(sub, notifications[1]); ++ } ++ ++ DOCTEST_SUBCASE("empty filter selects nothing") ++ { ++ createdNodes = sess.getContext().newPath2( ++ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter", ++ std::nullopt); ++ } ++ ++ sub = sess.subscribeNotifications(createdNodes.createdNode->asAny()); ++ } ++ ++ CLIENT_SEND_NOTIFICATION(notifications[0]); ++ CLIENT_SEND_NOTIFICATION(notifications[1]); ++ ++ // read as many notifications as we expect ++ for (size_t i = 0; i < expectations.size(); ++i) { ++ READ_NOTIFICATION_BLOCKING(*sub); ++ } ++ ++ sub->terminate(); ++ ++ // ensure no more notifications were sent ++ REQUIRE_PIPE_HANGUP(*sub); ++ } + } + + DOCTEST_SUBCASE("YANG Push on change") +@@ -285,9 +371,73 @@ TEST_CASE("Dynamic subscriptions") + * between writing to sysrepo and reading the notifications. + */ + +- auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes); ++ DOCTEST_SUBCASE("Filters") ++ { ++ std::optional sub; ++ ++ DOCTEST_SUBCASE("XPath filter") ++ { ++ sub = sess.yangPushOnChange("/test_module:leafInt32 | /test_module:popelnice/content/trash[name='asd']"); ++ } ++ ++ DOCTEST_SUBCASE("Subtree filter") ++ { ++ auto createdNodes = sess.getContext().newPath2( ++ "/ietf-subscribed-notifications:establish-subscription/ietf-yang-push:datastore-subtree-filter", ++ libyang::XML{"" ++ "asd"}); ++ sub = sess.yangPushOnChange(createdNodes.createdNode->asAny()); ++ } ++ ++ client.setItem("/test_module:leafInt32", "42"); ++ client.setItem("/test_module:popelnice/s", "asd"); ++ client.setItem("/test_module:popelnice/content/trash[name='asd']", std::nullopt); ++ client.applyChanges(); + +- REQUIRE_YANG_PUSH_UPDATE(sub, R"({ ++ client.deleteItem("/test_module:popelnice/s"); ++ client.applyChanges(); ++ ++ REQUIRE_YANG_PUSH_UPDATE(*sub, R"({ ++ "ietf-yang-push:push-change-update": { ++ "datastore-changes": { ++ "yang-patch": { ++ "patch-id": "patch-1", ++ "edit": [ ++ { ++ "edit-id": "edit-1", ++ "operation": "create", ++ "target": "/test_module:leafInt32", ++ "value": { ++ "test_module:leafInt32": 42 ++ } ++ }, ++ { ++ "edit-id": "edit-2", ++ "operation": "create", ++ "target": "/test_module:popelnice/content/trash[name='asd']", ++ "value": { ++ "test_module:trash": { ++ "name": "asd" ++ } ++ } ++ } ++ ] ++ } ++ } ++ } ++} ++)"); ++ READ_YANG_PUSH_UPDATE(*sub); ++ ++ sub->terminate(); ++ REQUIRE_PIPE_HANGUP(*sub); ++ } ++ ++ DOCTEST_SUBCASE("Sync on start") ++ { ++ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes); ++ ++ REQUIRE_YANG_PUSH_UPDATE(sub, R"({ + "ietf-yang-push:push-update": { + "datastore-contents": { + "test_module:values": [ +@@ -298,14 +448,14 @@ TEST_CASE("Dynamic subscriptions") + } + } + )"); +- READ_YANG_PUSH_UPDATE(sub); ++ READ_YANG_PUSH_UPDATE(sub); + +- client.setItem("/test_module:leafInt32", "123"); +- client.setItem("/test_module:values[.='5']", std::nullopt); +- client.deleteItem("/test_module:values[.='3']"); +- client.applyChanges(); ++ client.setItem("/test_module:leafInt32", "123"); ++ client.setItem("/test_module:values[.='5']", std::nullopt); ++ client.deleteItem("/test_module:values[.='3']"); ++ client.applyChanges(); + +- REQUIRE_YANG_PUSH_UPDATE(sub, R"({ ++ REQUIRE_YANG_PUSH_UPDATE(sub, R"({ + "ietf-yang-push:push-change-update": { + "datastore-changes": { + "yang-patch": { +@@ -342,10 +492,11 @@ TEST_CASE("Dynamic subscriptions") + } + } + )"); +- READ_YANG_PUSH_UPDATE(sub); ++ READ_YANG_PUSH_UPDATE(sub); + +- sub.terminate(); +- REQUIRE_PIPE_HANGUP(sub); ++ sub.terminate(); ++ REQUIRE_PIPE_HANGUP(sub); ++ } + } + + DOCTEST_SUBCASE("YANG Push periodic") +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0014-add-excluded-changes-to-YANG-push-on-change-wrapper.patch b/package/sysrepo-cpp/0014-add-excluded-changes-to-YANG-push-on-change-wrapper.patch new file mode 100644 index 00000000..b8378efb --- /dev/null +++ b/package/sysrepo-cpp/0014-add-excluded-changes-to-YANG-push-on-change-wrapper.patch @@ -0,0 +1,187 @@ +From b960058e37c471084538bb5db8b3b5e9e992c46c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 2 Apr 2025 21:47:21 +0200 +Subject: [PATCH 14/20] add excluded changes to YANG push on-change wrapper +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: Idb7bb48a3c60ff70286441cd4d0a5c1c28acee15 +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Enum.hpp | 12 +++++++ + include/sysrepo-cpp/Session.hpp | 1 + + src/Session.cpp | 12 ++++++- + src/utils/enum.hpp | 8 +++++ + tests/subscriptions-dynamic.cpp | 60 +++++++++++++++++++++++++++++++++ + 5 files changed, 92 insertions(+), 1 deletion(-) + +diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp +index cac1f2e..48467c6 100644 +--- a/include/sysrepo-cpp/Enum.hpp ++++ b/include/sysrepo-cpp/Enum.hpp +@@ -209,6 +209,18 @@ constexpr GetOptions operator|(const GetOptions a, const GetOptions b) + return implEnumBitOr(a, b); + } + ++/** ++ * @brief Wraps `srsn_yang_push_change_t` ++ */ ++enum class YangPushChange : uint32_t { ++ Create = 0, /**< SRSN_YP_CHANGE_CREATE */ ++ Delete = 1, /**< SRSN_YP_CHANGE_DELETE */ ++ Insert = 2, /**< SRSN_YP_CHANGE_INSERT */ ++ Move = 3, /**< SRSN_YP_CHANGE_MOVE */ ++ Replace = 4, /**< SRSN_YP_CHANGE_REPLACE */ ++ EnumCount = 5, /**< SRSN_COUNT_YP_CHANGE */ ++}; ++ + std::ostream& operator<<(std::ostream& os, const NotificationType& type); + std::ostream& operator<<(std::ostream& os, const Event& event); + std::ostream& operator<<(std::ostream& os, const ChangeOperation& changeOp); +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 0781890..b7cad72 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -145,6 +145,7 @@ public: + const std::optional>& filter, + const std::optional& dampeningPeriod = std::nullopt, + SyncOnStart syncOnStart = SyncOnStart::No, ++ const std::set& excludedChanges = {}, + const std::optional& stopTime = std::nullopt); + [[nodiscard]] DynamicSubscription subscribeNotifications( + const std::optional>& filter, +diff --git a/src/Session.cpp b/src/Session.cpp +index 1d65d70..40c3df3 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -607,6 +607,7 @@ DynamicSubscription Session::yangPushOnChange( + const std::optional>& filter, + const std::optional& dampeningPeriod, + SyncOnStart syncOnStart, ++ const std::set& excludedChanges, + const std::optional& stopTime) + { + int fd; +@@ -614,12 +615,21 @@ DynamicSubscription Session::yangPushOnChange( + auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt; + auto xpathFilter = constructXPathFilter(filter); + ++ /* The enum values are not used as the other enum flags in sysrepo-cpp. ++ * srsn_yang_push_on_change expects an integer array of size EnumCount with 0 or 1 values. ++ */ ++ using YangPushChangeUnderlying = std::underlying_type_t; ++ std::array(YangPushChange::EnumCount)> excludedChangesArray{}; ++ for (const auto& change: excludedChanges) { ++ excludedChangesArray[static_cast(change)] = 1; ++ } ++ + auto res = srsn_yang_push_on_change(m_sess.get(), + toDatastore(activeDatastore()), + xpathFilter ? xpathFilter->c_str() : nullptr, + dampeningPeriod ? dampeningPeriod->count() : 0, + syncOnStart == SyncOnStart::Yes, +- nullptr, ++ excludedChangesArray.data(), + stopSpec ? &stopSpec.value() : nullptr, + 0, + nullptr, +diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp +index a2dcf7a..2d3a298 100644 +--- a/src/utils/enum.hpp ++++ b/src/utils/enum.hpp +@@ -9,6 +9,7 @@ + #include + extern "C" { + #include ++#include + } + #include + +@@ -171,4 +172,11 @@ constexpr sr_get_options_t toGetOptions(const GetOptions opts) + { + return static_cast(opts); + } ++ ++static_assert(static_cast(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create); ++static_assert(static_cast(SRSN_YP_CHANGE_DELETE) == YangPushChange::Delete); ++static_assert(static_cast(SRSN_YP_CHANGE_INSERT) == YangPushChange::Insert); ++static_assert(static_cast(SRSN_YP_CHANGE_MOVE) == YangPushChange::Move); ++static_assert(static_cast(SRSN_YP_CHANGE_REPLACE) == YangPushChange::Replace); ++static_assert(static_cast(SRSN_COUNT_YP_CHANGE) == YangPushChange::EnumCount); + } +diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp +index 84a0880..5ed173b 100644 +--- a/tests/subscriptions-dynamic.cpp ++++ b/tests/subscriptions-dynamic.cpp +@@ -497,6 +497,66 @@ TEST_CASE("Dynamic subscriptions") + sub.terminate(); + REQUIRE_PIPE_HANGUP(sub); + } ++ ++ DOCTEST_SUBCASE("Excluded changes") ++ { ++ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::No, {sysrepo::YangPushChange::Create}); ++ ++ client.setItem("/test_module:leafInt32", "123"); ++ client.applyChanges(); // excluded (create) ++ client.setItem("/test_module:leafInt32", "124"); ++ client.applyChanges(); ++ client.setItem("/test_module:leafInt32", "125"); ++ client.applyChanges(); ++ ++ REQUIRE_YANG_PUSH_UPDATE(sub, R"({ ++ "ietf-yang-push:push-change-update": { ++ "datastore-changes": { ++ "yang-patch": { ++ "patch-id": "patch-1", ++ "edit": [ ++ { ++ "edit-id": "edit-1", ++ "operation": "replace", ++ "target": "/test_module:leafInt32", ++ "value": { ++ "test_module:leafInt32": 124 ++ } ++ } ++ ] ++ } ++ } ++ } ++} ++)"); ++ ++ READ_YANG_PUSH_UPDATE(sub); ++ ++ REQUIRE_YANG_PUSH_UPDATE(sub, R"({ ++ "ietf-yang-push:push-change-update": { ++ "datastore-changes": { ++ "yang-patch": { ++ "patch-id": "patch-2", ++ "edit": [ ++ { ++ "edit-id": "edit-1", ++ "operation": "replace", ++ "target": "/test_module:leafInt32", ++ "value": { ++ "test_module:leafInt32": 125 ++ } ++ } ++ ] ++ } ++ } ++ } ++} ++)"); ++ READ_YANG_PUSH_UPDATE(sub); ++ ++ sub.terminate(); ++ REQUIRE_PIPE_HANGUP(sub); ++ } + } + + DOCTEST_SUBCASE("YANG Push periodic") +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0015-wrap-sr_nacm_get_user.patch b/package/sysrepo-cpp/0015-wrap-sr_nacm_get_user.patch new file mode 100644 index 00000000..3e40cad4 --- /dev/null +++ b/package/sysrepo-cpp/0015-wrap-sr_nacm_get_user.patch @@ -0,0 +1,84 @@ +From 3407f98d147ffbf031725102181e1fe3cd874363 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Tue, 8 Apr 2025 12:52:00 +0200 +Subject: [PATCH 15/20] wrap sr_nacm_get_user +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +This adds a counterpart to setting NACM user. Sometimes, it might be +useful to get it as well. + +Change-Id: I534a229eb536d63091904ab4f6268cb9da6dd3db +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Session.hpp | 1 + + src/Session.cpp | 9 +++++++++ + tests/session.cpp | 7 +++++++ + 3 files changed, 17 insertions(+) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index b7cad72..1c409b8 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -100,6 +100,7 @@ public: + void replaceConfig(std::optional config, const std::optional& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}); + + void setNacmUser(const std::string& user); ++ std::optional getNacmUser() const; + [[nodiscard]] Subscription initNacm( + SubscribeOptions opts = SubscribeOptions::Default, + ExceptionHandler handler = nullptr, +diff --git a/src/Session.cpp b/src/Session.cpp +index 40c3df3..2273c79 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -706,6 +706,15 @@ void Session::setNacmUser(const std::string& user) + throwIfError(res, "Couldn't set NACM user", m_sess.get()); + } + ++/** ++ * @brief Get the NACM user for this session. ++ */ ++std::optional Session::getNacmUser() const ++{ ++ auto* username = sr_nacm_get_user(m_sess.get()); ++ return username ? std::make_optional(username) : std::nullopt; ++} ++ + /** + * @brief Initializes NACM callbacks. + * +diff --git a/tests/session.cpp b/tests/session.cpp +index 9a419a1..548b371 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -390,10 +390,14 @@ TEST_CASE("session") + auto data = sess.getData("/test_module:denyAllLeaf"); + REQUIRE(data.value().findPath("/test_module:denyAllLeaf").value().asTerm().valueStr() == "AHOJ"); + ++ REQUIRE(!sess.getNacmUser()); ++ + // check that repeated NACM initialization still works + for (int i = 0; i < 3; ++i) { + auto nacmSub = sess.initNacm(); + sess.setNacmUser("nobody"); ++ REQUIRE(sess.getNacmUser() == "nobody"); ++ + data = sess.getData("/test_module:denyAllLeaf"); + // After turning on NACM, we can't access the leaf. + REQUIRE(!data); +@@ -408,6 +412,9 @@ TEST_CASE("session") + sysrepo::ErrorWithCode); + } + ++ REQUIRE(!!sess.getNacmUser()); ++ REQUIRE(sess.getNacmUser() == "nobody"); ++ + // duplicate NACM initialization should throw + auto nacm = sess.initNacm(); + REQUIRE_THROWS_WITH_AS(auto x = sess.initNacm(), +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0016-CI-renamed-project-upstream.patch b/package/sysrepo-cpp/0016-CI-renamed-project-upstream.patch new file mode 100644 index 00000000..d5ceb5f7 --- /dev/null +++ b/package/sysrepo-cpp/0016-CI-renamed-project-upstream.patch @@ -0,0 +1,69 @@ +From afc4b8e04b4bf61d6bf0e0a66c1115b0111eaebf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= +Date: Wed, 9 Apr 2025 15:36:30 +0200 +Subject: [PATCH 16/20] CI: renamed project upstream +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069 +Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8570 +Signed-off-by: Mattias Walström +--- + .zuul.yaml | 4 ++-- + README.md | 2 +- + ci/build.sh | 2 +- + 3 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/.zuul.yaml b/.zuul.yaml +index dc87c6f..7ada6f7 100644 +--- a/.zuul.yaml ++++ b/.zuul.yaml +@@ -7,7 +7,7 @@ + override-checkout: devel + - name: github/sysrepo/sysrepo + override-checkout: devel +- - name: github/onqtam/doctest ++ - name: github/doctest/doctest + override-checkout: v2.4.8 + - name: github/rollbear/trompeloeil + override-checkout: v44 +@@ -18,7 +18,7 @@ + override-checkout: devel + - name: github/sysrepo/sysrepo + override-checkout: devel +- - name: github/onqtam/doctest ++ - name: github/doctest/doctest + override-checkout: v2.4.11 + - name: github/rollbear/trompeloeil + override-checkout: v44 +diff --git a/README.md b/README.md +index 27dc748..dd9570c 100644 +--- a/README.md ++++ b/README.md +@@ -12,7 +12,7 @@ It uses RAII for automatic memory management. + - [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang* + - C++20 compiler (e.g., GCC 10.x+, clang 10+) + - CMake 3.19+ +-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework ++- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework + - optionally for built-in tests, [trompeloeil](https://github.com/rollbear/trompeloeil) for mock objects in C++ + - optionally for the docs, Doxygen + +diff --git a/ci/build.sh b/ci/build.sh +index d886b79..785a811 100755 +--- a/ci/build.sh ++++ b/ci/build.sh +@@ -78,7 +78,7 @@ build_n_test() { + + build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF + build_n_test github/sysrepo/sysrepo -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF -DREPO_PATH=${PREFIX}/etc-sysrepo +-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF ++build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF + # non-release builds download Catch2 + CMAKE_BUILD_TYPE=Release build_n_test github/rollbear/trompeloeil + build_n_test CzechLight/libyang-cpp -DBUILD_TESTING=ON +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0017-wrap-sr_nacm_check_operation.patch b/package/sysrepo-cpp/0017-wrap-sr_nacm_check_operation.patch new file mode 100644 index 00000000..d1ae2545 --- /dev/null +++ b/package/sysrepo-cpp/0017-wrap-sr_nacm_check_operation.patch @@ -0,0 +1,133 @@ +From ad070266f9347f159874e6b2fa57302385f354e3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Wed, 9 Apr 2025 14:51:31 +0200 +Subject: [PATCH 17/20] wrap sr_nacm_check_operation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Sysrepo provides API function for explicit checking if an operation can +be authorized on a node. This might come handy if one decides to do NACM +authorization himself, like we will do in our RESTCONF server. + +Change-Id: Ida41514a7f03ab120a331363b7f9ed8b69918d88 +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Session.hpp | 1 + + src/Session.cpp | 13 ++++++++++ + tests/session.cpp | 46 +++++++++++++++++++++++++++++++++ + tests/test_module.yang | 4 +++ + 4 files changed, 64 insertions(+) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 1c409b8..2112d21 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -101,6 +101,7 @@ public: + + void setNacmUser(const std::string& user); + std::optional getNacmUser() const; ++ bool checkNacmOperation(const libyang::DataNode& node) const; + [[nodiscard]] Subscription initNacm( + SubscribeOptions opts = SubscribeOptions::Default, + ExceptionHandler handler = nullptr, +diff --git a/src/Session.cpp b/src/Session.cpp +index 2273c79..ce0dec2 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -715,6 +715,19 @@ std::optional Session::getNacmUser() const + return username ? std::make_optional(username) : std::nullopt; + } + ++/** ++ * @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`. ++ * @return true if the current user is authorized to perform operation on given @p node. ++ * ++ * Details on unsuccessfull authorizations can be retrieved via Session::getErrors. ++ * Note that if the NACM user is not set, `sr_nacm_check_operation` and this function both return true. ++ */ ++bool Session::checkNacmOperation(const libyang::DataNode& node) const ++{ ++ auto res = sr_nacm_check_operation(m_sess.get(), libyang::getRawNode(node)); ++ return res == SR_ERR_OK; ++} ++ + /** + * @brief Initializes NACM callbacks. + * +diff --git a/tests/session.cpp b/tests/session.cpp +index 548b371..35dd545 100644 +--- a/tests/session.cpp ++++ b/tests/session.cpp +@@ -423,6 +423,52 @@ TEST_CASE("session") + sysrepo::ErrorWithCode); + } + ++ DOCTEST_SUBCASE("Session::checkNacmOperation") ++ { ++ auto nacmSub = sess.initNacm(); ++ ++ // check NACM access for RPCs ++ auto shutdownRPC = sess.getContext().newPath("/test_module:shutdown", std::nullopt); ++ auto denyAllRPC = sess.getContext().newPath("/test_module:deny-all-rpc", std::nullopt); ++ ++ // user not set, everything is permitted ++ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); ++ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true); ++ REQUIRE(sess.getErrors().size() == 0); ++ ++ sess.setNacmUser("root"); ++ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); ++ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true); ++ REQUIRE(sess.getErrors().size() == 0); ++ ++ sess.setNacmUser("nobody"); ++ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); ++ REQUIRE(sess.checkNacmOperation(denyAllRPC) == false); ++ REQUIRE(sess.getErrors().size() == 1); ++ REQUIRE(sess.getErrors().at(0) == sysrepo::ErrorInfo{ ++ .code = sysrepo::ErrorCode::Unauthorized, ++ .errorMessage = "Executing the operation is denied because \"nobody\" NACM authorization failed.", ++ }); ++ ++ sess.setNacmUser("root"); // 'nobody' is not authorized to write into this subtree ++ sess.switchDatastore(sysrepo::Datastore::Running); ++ sess.setItem("/ietf-netconf-acm:nacm/enable-external-groups", "false"); ++ sess.setItem("/ietf-netconf-acm:nacm/groups/group[name='grp']/user-name[.='nobody']", ""); ++ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/group[.='grp']", ""); ++ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/module-name", "test_module"); ++ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/access-operations", "*"); ++ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/action", "deny"); ++ sess.applyChanges(); ++ ++ sess.setNacmUser("root"); ++ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true); ++ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true); ++ ++ sess.setNacmUser("nobody"); ++ REQUIRE(sess.checkNacmOperation(denyAllRPC) == false); ++ REQUIRE(sess.checkNacmOperation(shutdownRPC) == false); ++ } ++ + DOCTEST_SUBCASE("Session::getPendingChanges") + { + REQUIRE(sess.getPendingChanges() == std::nullopt); +diff --git a/tests/test_module.yang b/tests/test_module.yang +index 02c467b..3d6c26d 100644 +--- a/tests/test_module.yang ++++ b/tests/test_module.yang +@@ -55,6 +55,10 @@ module test_module { + rpc noop { + } + ++ rpc deny-all-rpc { ++ nacm:default-deny-all; ++ } ++ + rpc shutdown { + output { + leaf success { +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0018-Fix-return-value-type-in-doxygen.patch b/package/sysrepo-cpp/0018-Fix-return-value-type-in-doxygen.patch new file mode 100644 index 00000000..b0910d4b --- /dev/null +++ b/package/sysrepo-cpp/0018-Fix-return-value-type-in-doxygen.patch @@ -0,0 +1,50 @@ +From cecf636eecea53a597bcbf75eb4dda0f916e9967 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Tue, 15 Apr 2025 10:54:11 +0200 +Subject: [PATCH 18/20] Fix return value type in doxygen +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Fixes: 415c401 ("wrap dynamic subscription functions") +Change-Id: I66912530d8aa16bb4dde3d35611be1e6e4247b36 +Signed-off-by: Mattias Walström +--- + src/Session.cpp | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/Session.cpp b/src/Session.cpp +index ce0dec2..8258295 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -564,7 +564,7 @@ Subscription Session::onNotification( + * @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period. + * @param stopTime Optional stop time ending the notification subscription. + * +- * @return A YangPushSubscription handle. ++ * @return A DynamicSubscription handle. + */ + DynamicSubscription Session::yangPushPeriodic( + const std::optional>& filter, +@@ -601,7 +601,7 @@ DynamicSubscription Session::yangPushPeriodic( + * @param syncOnStart Whether to start with a notification of the current state. + * @param stopTime Optional stop time ending the notification subscription. + * +- * @return A YangPushSubscription handle. ++ * @return A DynamicSubscription handle. + */ + DynamicSubscription Session::yangPushOnChange( + const std::optional>& filter, +@@ -650,7 +650,7 @@ DynamicSubscription Session::yangPushOnChange( + * @param stopTime Optional stop time ending the subscription. + * @param startTime Optional start time of the subscription, used for replaying stored notifications. + * +- * @return A YangPushSubscription handle. ++ * @return A DynamicSubscription handle. + */ + DynamicSubscription Session::subscribeNotifications( + const std::optional>& filter, +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0019-add-a-session-getter-to-dynamic-subscriptions.patch b/package/sysrepo-cpp/0019-add-a-session-getter-to-dynamic-subscriptions.patch new file mode 100644 index 00000000..201640fa --- /dev/null +++ b/package/sysrepo-cpp/0019-add-a-session-getter-to-dynamic-subscriptions.patch @@ -0,0 +1,137 @@ +From ee53d86929296c4824e9860f6425850f7d795302 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Tue, 15 Apr 2025 11:06:50 +0200 +Subject: [PATCH 19/20] add a session getter to dynamic subscriptions +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Sometimes it might be useful to get the session which created the +subscription (e.g. to do some work in processEvent callback). There was +no easy way to do it up until now because the session was not stored as +sysrepo::Session. +However, it seems quite easy to change that and add the getter. + +Change-Id: Ia943a47debba6f84c3faad5ea48a69da68dac874 +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Subscription.hpp | 3 ++- + src/Session.cpp | 6 +++--- + src/Subscription.cpp | 17 +++++++++++------ + 3 files changed, 16 insertions(+), 10 deletions(-) + +diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp +index 7fdc08b..4c58e78 100644 +--- a/include/sysrepo-cpp/Subscription.hpp ++++ b/include/sysrepo-cpp/Subscription.hpp +@@ -303,6 +303,7 @@ public: + DynamicSubscription& operator=(DynamicSubscription&&) noexcept; + ~DynamicSubscription(); + ++ sysrepo::Session getSession() const; + int fd() const; + uint64_t subscriptionId() const; + std::optional replayStartTime() const; +@@ -310,7 +311,7 @@ public: + void terminate(const std::optional& reason = std::nullopt); + + private: +- DynamicSubscription(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime = std::nullopt); ++ DynamicSubscription(sysrepo::Session sess, int fd, uint64_t subId, const std::optional& replayStartTime = std::nullopt); + + struct Data; + std::unique_ptr m_data; +diff --git a/src/Session.cpp b/src/Session.cpp +index 8258295..7d44662 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -588,7 +588,7 @@ DynamicSubscription Session::yangPushPeriodic( + &subId); + throwIfError(res, "Couldn't create yang-push periodic subscription", m_sess.get()); + +- return {m_sess, fd, subId}; ++ return {*this, fd, subId}; + } + + /** +@@ -637,7 +637,7 @@ DynamicSubscription Session::yangPushOnChange( + &subId); + throwIfError(res, "Couldn't create yang-push on-change subscription", m_sess.get()); + +- return {m_sess, fd, subId}; ++ return {*this, fd, subId}; + } + + /** +@@ -683,7 +683,7 @@ DynamicSubscription Session::subscribeNotifications( + replayStart = toTimePoint(replayStartSpec); + } + +- return {m_sess, fd, subId, replayStart}; ++ return {*this, fd, subId, replayStart}; + } + + /** +diff --git a/src/Subscription.cpp b/src/Subscription.cpp +index ab27b16..6dccaee 100644 +--- a/src/Subscription.cpp ++++ b/src/Subscription.cpp +@@ -414,18 +414,18 @@ bool ChangeIterator::operator==(const ChangeIterator& other) const + + + struct DynamicSubscription::Data { +- std::shared_ptr sess; ++ sysrepo::Session sess; + int fd; + uint64_t subId; + std::optional m_replayStartTime; + bool m_terminated; + +- Data(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime, bool terminated); ++ Data(sysrepo::Session sess, int fd, uint64_t subId, const std::optional& replayStartTime, bool terminated); + ~Data(); + void terminate(const std::optional& reason = std::nullopt); + }; + +-DynamicSubscription::DynamicSubscription(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime) ++DynamicSubscription::DynamicSubscription(sysrepo::Session sess, int fd, uint64_t subId, const std::optional& replayStartTime) + : m_data(std::make_unique(std::move(sess), fd, subId, replayStartTime, false)) + { + } +@@ -434,6 +434,12 @@ DynamicSubscription::DynamicSubscription(DynamicSubscription&&) noexcept = defau + DynamicSubscription& DynamicSubscription::operator=(DynamicSubscription&&) noexcept = default; + DynamicSubscription::~DynamicSubscription() = default; + ++/** @brief Returns sysrepo Session associated with this subscription */ ++sysrepo::Session DynamicSubscription::getSession() const ++{ ++ return m_data->sess; ++} ++ + /** @brief Returns the file descriptor associated with this subscription. */ + int DynamicSubscription::fd() const + { +@@ -472,9 +478,8 @@ void DynamicSubscription::processEvent(YangPushNotifCb cb) const + { + struct timespec timestamp; + struct lyd_node* tree; +- auto ctx = std::unique_ptr>(sr_session_acquire_context(m_data->sess.get()), [&](const ly_ctx*) { sr_session_release_context(m_data->sess.get()); }); + +- auto err = srsn_read_notif(fd(), ctx.get(), ×tamp, &tree); ++ auto err = srsn_read_notif(fd(), libyang::retrieveContext(m_data->sess.getContext()), ×tamp, &tree); + throwIfError(err, "Couldn't read yang-push notification"); + + const auto wrappedNotification = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt; +@@ -488,7 +493,7 @@ void DynamicSubscription::processEvent(YangPushNotifCb cb) const + cb(wrappedNotification, toTimePoint(timestamp)); + } + +-DynamicSubscription::Data::Data(std::shared_ptr sess, int fd, uint64_t subId, const std::optional& replayStartTime, bool terminated) ++DynamicSubscription::Data::Data(sysrepo::Session sess, int fd, uint64_t subId, const std::optional& replayStartTime, bool terminated) + : sess(std::move(sess)) + , fd(fd) + , subId(subId) +-- +2.43.0 + diff --git a/package/sysrepo-cpp/0020-wrap-sr_nacm_get_recovery_user.patch b/package/sysrepo-cpp/0020-wrap-sr_nacm_get_recovery_user.patch new file mode 100644 index 00000000..f6ec6edf --- /dev/null +++ b/package/sysrepo-cpp/0020-wrap-sr_nacm_get_recovery_user.patch @@ -0,0 +1,55 @@ +From 67eed65414cb9ccd031e16ec76149cc09c341186 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= +Date: Tue, 15 Apr 2025 15:26:30 +0200 +Subject: [PATCH 20/20] wrap sr_nacm_get_recovery_user +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +The function does not actually need a session, I am only adding it to +Session so it is in the same place as other NACM user methods. + +Change-Id: I3ee015b2bdc180ab1a9115ffc1a3a53f8e6f677c +Signed-off-by: Mattias Walström +--- + include/sysrepo-cpp/Session.hpp | 1 + + src/Session.cpp | 10 ++++++++++ + 2 files changed, 11 insertions(+) + +diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp +index 2112d21..2612e86 100644 +--- a/include/sysrepo-cpp/Session.hpp ++++ b/include/sysrepo-cpp/Session.hpp +@@ -101,6 +101,7 @@ public: + + void setNacmUser(const std::string& user); + std::optional getNacmUser() const; ++ static std::string getNacmRecoveryUser(); + bool checkNacmOperation(const libyang::DataNode& node) const; + [[nodiscard]] Subscription initNacm( + SubscribeOptions opts = SubscribeOptions::Default, +diff --git a/src/Session.cpp b/src/Session.cpp +index 7d44662..e96e6ec 100644 +--- a/src/Session.cpp ++++ b/src/Session.cpp +@@ -715,6 +715,16 @@ std::optional Session::getNacmUser() const + return username ? std::make_optional(username) : std::nullopt; + } + ++/** ++ * @brief Get the sysrepo NACM recovery user. ++ * ++ * wraps `sr_nacm_get_recovery_user`. ++ */ ++std::string Session::getNacmRecoveryUser() ++{ ++ return sr_nacm_get_recovery_user(); ++} ++ + /** + * @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`. + * @return true if the current user is authorized to perform operation on given @p node. +-- +2.43.0 + diff --git a/patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch b/patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch deleted file mode 100644 index eb04b2d3..00000000 --- a/patches/netopeer2/2.2.35/0002-Disable-local-users-backwards-compat-with-older-mode.patch +++ /dev/null @@ -1,50 +0,0 @@ -From d2e4e60838761e2bdd02d651b8e0bea47cc2dcb5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= -Date: Tue, 2 Jul 2024 14:56:15 +0200 -Subject: [PATCH 2/4] Disable local users (backwards compat with older model) -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Wires - -Drop local-users, because if enabled netopeer require all users that -should be allowed to use NETCONF to also be configured in the updated -ietf-netconf-server model. - -Signed-off-by: Mattias Walström -Signed-off-by: Joachim Wiberg ---- - scripts/common.sh | 2 +- - scripts/merge_config.sh | 3 --- - 2 files changed, 1 insertion(+), 4 deletions(-) - -diff --git a/scripts/common.sh b/scripts/common.sh -index 25e531b..13234e1 100644 ---- a/scripts/common.sh -+++ b/scripts/common.sh -@@ -29,7 +29,7 @@ LN2_MODULES=( - "ietf-tcp-server@2023-12-28.yang -e tcp-server-keepalives" - "ietf-tcp-client@2023-12-28.yang -e local-binding-supported -e tcp-client-keepalives" - "ietf-ssh-common@2023-12-28.yang -e transport-params" --"ietf-ssh-server@2023-12-28.yang -e local-users-supported -e local-user-auth-publickey -e local-user-auth-password -e local-user-auth-none" -+"ietf-ssh-server@2023-12-28.yang" - "ietf-tls-common@2023-12-28.yang -e tls10 -e tls11 -e tls12 -e tls13 -e hello-params" - "ietf-tls-server@2023-12-28.yang -e server-ident-x509-cert -e client-auth-supported -e client-auth-x509-cert" - "ietf-netconf-server@2023-12-28.yang -e ssh-listen -e tls-listen -e ssh-call-home -e tls-call-home -e central-netconf-server-supported" -diff --git a/scripts/merge_config.sh b/scripts/merge_config.sh -index eddada7..61af617 100755 ---- a/scripts/merge_config.sh -+++ b/scripts/merge_config.sh -@@ -89,9 +89,6 @@ CONFIG=" - - -- -- ${USERS_CONFIG} -- - - - --- -2.43.0 - diff --git a/patches/netopeer2/2.2.35/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch b/patches/netopeer2/2.4.1/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch similarity index 76% rename from patches/netopeer2/2.2.35/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch rename to patches/netopeer2/2.4.1/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch index 97c02332..6c204574 100644 --- a/patches/netopeer2/2.2.35/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch +++ b/patches/netopeer2/2.4.1/0001-Allow-factory-as-copy-from-only-in-rpc-copy-config.patch @@ -1,7 +1,7 @@ -From 2950046cd17bf9296a0b70b8f6b38114f6985864 Mon Sep 17 00:00:00 2001 +From c62a0c72ac2cc7c840f6004c274c37bf8ce6c854 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 22 Jun 2023 10:24:57 +0200 -Subject: [PATCH 1/4] Allow 'factory' as copy-from (only) in rpc copy-config +Subject: [PATCH 1/2] Allow 'factory' as copy-from (only) in rpc copy-config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -14,10 +14,10 @@ Signed-off-by: Mattias Walström 1 file changed, 2 insertions(+) diff --git a/src/netconf.c b/src/netconf.c -index f1cc6b3..fdd7297 100644 +index 1d9ba79..2a6f1a4 100644 --- a/src/netconf.c +++ b/src/netconf.c -@@ -369,6 +369,8 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con +@@ -334,6 +334,8 @@ np2srv_rpc_copyconfig_cb(const struct lyd_node *rpc, struct np_user_sess *user_s ds = SR_DS_STARTUP; } else if (!strcmp(nodeset->dnodes[0]->schema->name, "candidate")) { ds = SR_DS_CANDIDATE; diff --git a/patches/netopeer2/2.2.35/0003-Do-not-generate-data-in-sysrepo.patch b/patches/netopeer2/2.4.1/0002-Do-not-generate-data-in-sysrepo.patch similarity index 93% rename from patches/netopeer2/2.2.35/0003-Do-not-generate-data-in-sysrepo.patch rename to patches/netopeer2/2.4.1/0002-Do-not-generate-data-in-sysrepo.patch index 81381fa8..d44567a2 100644 --- a/patches/netopeer2/2.2.35/0003-Do-not-generate-data-in-sysrepo.patch +++ b/patches/netopeer2/2.4.1/0002-Do-not-generate-data-in-sysrepo.patch @@ -1,7 +1,7 @@ -From 2f09813c91def7672c036c7f713302cb3a12cf18 Mon Sep 17 00:00:00 2001 +From 15bff587a9096354f20803c78fbae729f50f8d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Tue, 4 Feb 2025 20:14:50 +0100 -Subject: [PATCH 4/4] Do not generate data in sysrepo +Subject: [PATCH 2/2] Do not generate data in sysrepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -16,10 +16,10 @@ Signed-off-by: Mattias Walström 1 file changed, 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt -index 4341be1..f1c4ae8 100644 +index 94dc315..66d69d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -383,47 +383,7 @@ if(SYSREPO_SETUP) +@@ -386,47 +386,7 @@ if(SYSREPO_SETUP) message(FATAL_ERROR \" OUTPUT:\\n \${CMD_OUT_F}\\n ERROR:\\n \${CMD_ERR_F}\") endif() ") diff --git a/patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch b/patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch deleted file mode 100644 index 8c9b19ca..00000000 --- a/patches/sysrepo/3.3.10/0001-modinfo-BUGFIX-add-NP-containers-to-push-oper-data.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 2137bc0d614ad0aeabc37fd0041d05bc341a9007 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Fri, 10 Jan 2025 08:55:38 +0100 -Subject: [PATCH 01/22] modinfo BUGFIX add NP containers to push oper data -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -... so that oper get subscriptions for the -containers are called. - -Signed-off-by: Mattias Walström ---- - src/modinfo.c | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/src/modinfo.c b/src/modinfo.c -index a58717f8..14d0b2ae 100644 ---- a/src/modinfo.c -+++ b/src/modinfo.c -@@ -1459,6 +1459,11 @@ sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, - if ((err_info = sr_module_oper_data_load(mod, conn, 0, NULL, data))) { - return err_info; - } -+ -+ /* add any missing NP containers in the data */ -+ if ((err_info = sr_lyd_new_implicit_module(data, mod->ly_mod, LYD_IMPLICIT_NO_DEFAULTS, NULL))) { -+ return err_info; -+ } - } - - if (get_oper_opts & SR_OPER_NO_SUBS) { --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch b/patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch deleted file mode 100644 index fe2dbe6e..00000000 --- a/patches/sysrepo/3.3.10/0002-sysrepo-BUGFIX-wrong-param.patch +++ /dev/null @@ -1,30 +0,0 @@ -From d4cded05509b63083283ba3a9104b27663c7eb39 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Fri, 10 Jan 2025 08:56:29 +0100 -Subject: [PATCH 02/22] sysrepo BUGFIX wrong param -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - src/sysrepo.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/sysrepo.c b/src/sysrepo.c -index 844e11a5..7c96243c 100644 ---- a/src/sysrepo.c -+++ b/src/sysrepo.c -@@ -3578,7 +3578,7 @@ sr_delete_item(sr_session_ctx_t *session, const char *path, const sr_edit_option - /* just delete the selected node */ - node = NULL; - if (session->dt[session->ds].edit->tree && -- (err_info = sr_lyd_find_path(session->dt[session->ds].edit->tree, path, 1, &node))) { -+ (err_info = sr_lyd_find_path(session->dt[session->ds].edit->tree, path, 0, &node))) { - goto cleanup; - } - if (node) { --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch b/patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch deleted file mode 100644 index 8b069626..00000000 --- a/patches/sysrepo/3.3.10/0003-SOVERSION-bump-to-version-7.29.11.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 526da167043615ab9a0a8d36d4dd4a92644a0f58 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Fri, 10 Jan 2025 08:56:54 +0100 -Subject: [PATCH 03/22] SOVERSION bump to version 7.29.11 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 84990130..4ecfe4d9 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_ - # with backward compatible change and micro version is connected with any internal change of the library. - set(SYSREPO_MAJOR_SOVERSION 7) - set(SYSREPO_MINOR_SOVERSION 29) --set(SYSREPO_MICRO_SOVERSION 10) -+set(SYSREPO_MICRO_SOVERSION 11) - set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION}) - set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION}) - --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch b/patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch deleted file mode 100644 index a6acde1e..00000000 --- a/patches/sysrepo/3.3.10/0004-VERSION-bump-to-version-3.3.11.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 880e15c69312a471c85a8f125e5c0ade3a93dcb1 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Fri, 10 Jan 2025 08:57:04 +0100 -Subject: [PATCH 04/22] VERSION bump to version 3.3.11 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 4ecfe4d9..9f3cfde0 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -54,7 +54,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - # micro version is changed with a set of small changes or bugfixes anywhere in the project. - set(SYSREPO_MAJOR_VERSION 3) - set(SYSREPO_MINOR_VERSION 3) --set(SYSREPO_MICRO_VERSION 10) -+set(SYSREPO_MICRO_VERSION 11) - set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION}) - - # Version of the library --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch b/patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch deleted file mode 100644 index c8caff85..00000000 --- a/patches/sysrepo/3.3.10/0005-sysrepo-UPDATE-generate-notifications-on-module-chan.patch +++ /dev/null @@ -1,963 +0,0 @@ -From 31af3459bd8ea24228e2b13193176af6c34c289e Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Mon, 13 Jan 2025 11:19:30 +0100 -Subject: [PATCH 05/22] sysrepo UPDATE generate notifications on module changes -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - modules/sysrepo-notifications@2025-01-10.yang | 97 ++++++ - modules/sysrepo_notifications_yang.h | 208 ++++++++++++ - src/common.c | 295 +++++++++++++++++- - src/common.h | 42 ++- - src/lyd_mods.c | 4 + - src/modinfo.c | 2 +- - src/replay.c | 42 +-- - src/replay.h | 10 +- - src/sysrepo.c | 20 +- - tests/test_oper_pull.c | 6 + - 10 files changed, 693 insertions(+), 33 deletions(-) - create mode 100644 modules/sysrepo-notifications@2025-01-10.yang - create mode 100644 modules/sysrepo_notifications_yang.h - -diff --git a/modules/sysrepo-notifications@2025-01-10.yang b/modules/sysrepo-notifications@2025-01-10.yang -new file mode 100644 -index 00000000..73df4353 ---- /dev/null -+++ b/modules/sysrepo-notifications@2025-01-10.yang -@@ -0,0 +1,97 @@ -+module sysrepo-notifications { -+ namespace "http://www.sysrepo.org/yang/sysrepo-notifications"; -+ prefix srn; -+ -+ yang-version 1.1; -+ -+ import ietf-yang-library { -+ prefix yanglib; -+ } -+ -+ organization -+ "CESNET"; -+ -+ contact -+ "Author: Michal Vasko -+ "; -+ -+ description -+ "Sysrepo YANG notifications for various events."; -+ -+ revision "2025-01-10" { -+ description -+ "Initial revision."; -+ } -+ -+ typedef module-revision { -+ type union { -+ type yanglib:revision-identifier; -+ type string { -+ length "0"; -+ } -+ } -+ description -+ "The YANG module revision date. -+ A zero-length string is used if no revision statement -+ is present in the YANG module."; -+ } -+ -+ notification module-change { -+ description -+ "YANG module change occurred."; -+ -+ leaf name { -+ type string; -+ mandatory true; -+ description -+ "Name of the changed module."; -+ } -+ -+ leaf revision { -+ type module-revision; -+ mandatory true; -+ description -+ "Revision of the module."; -+ } -+ -+ leaf change { -+ type enumeration { -+ enum installed { -+ description -+ "New module was installed."; -+ } -+ enum uninstalled { -+ description -+ "Module was uninstalled."; -+ } -+ enum updated { -+ description -+ "Module was updated to a newer revision."; -+ } -+ enum feature-enabled { -+ description -+ "Feature was enabled in a module."; -+ } -+ enum feature-disabled { -+ description -+ "Feature was disabled in a module."; -+ } -+ } -+ mandatory true; -+ description -+ "Type of the module change."; -+ } -+ -+ leaf old-revision { -+ when "../change = 'updated'"; -+ type module-revision; -+ mandatory true; -+ } -+ -+ leaf feature-name { -+ when "../change = ('feature-enabled' or 'feature-disabled')"; -+ type string; -+ mandatory true; -+ } -+ } -+} -diff --git a/modules/sysrepo_notifications_yang.h b/modules/sysrepo_notifications_yang.h -new file mode 100644 -index 00000000..8f99c518 ---- /dev/null -+++ b/modules/sysrepo_notifications_yang.h -@@ -0,0 +1,208 @@ -+char sysrepo_notifications_yang[] = { -+ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x73, 0x79, 0x73, 0x72, 0x65, -+ 0x70, 0x6f, 0x2d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, -+ 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e, -+ 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x22, 0x68, 0x74, -+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x73, -+ 0x72, 0x65, 0x70, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x79, 0x61, 0x6e, -+ 0x67, 0x2f, 0x73, 0x79, 0x73, 0x72, 0x65, 0x70, 0x6f, 0x2d, 0x6e, 0x6f, -+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, -+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, -+ 0x20, 0x73, 0x72, 0x6e, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x79, -+ 0x61, 0x6e, 0x67, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, -+ 0x31, 0x2e, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d, -+ 0x70, 0x6f, 0x72, 0x74, 0x20, 0x69, 0x65, 0x74, 0x66, 0x2d, 0x79, 0x61, -+ 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x7b, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, -+ 0x66, 0x69, 0x78, 0x20, 0x79, 0x61, 0x6e, 0x67, 0x6c, 0x69, 0x62, 0x3b, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x45, -+ 0x53, 0x4e, 0x45, 0x54, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x3a, -+ 0x20, 0x4d, 0x69, 0x63, 0x68, 0x61, 0x6c, 0x20, 0x56, 0x61, 0x73, 0x6b, -+ 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x6d, 0x76, 0x61, 0x73, -+ 0x6b, 0x6f, 0x40, 0x63, 0x65, 0x73, 0x6e, 0x65, 0x74, 0x2e, 0x63, 0x7a, -+ 0x3e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, -+ 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x79, 0x73, 0x72, 0x65, 0x70, -+ 0x6f, 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, -+ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, 0x6f, 0x72, -+ 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x65, 0x76, 0x65, -+ 0x6e, 0x74, 0x73, 0x2e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x22, 0x32, 0x30, -+ 0x32, 0x35, 0x2d, 0x30, 0x31, 0x2d, 0x31, 0x30, 0x22, 0x20, 0x7b, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, -+ 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x69, -+ 0x74, 0x69, 0x61, 0x6c, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, -+ 0x6e, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x64, 0x65, 0x66, 0x20, -+ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, 0x72, 0x65, 0x76, 0x69, 0x73, -+ 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e, -+ 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x79, 0x61, 0x6e, 0x67, -+ 0x6c, 0x69, 0x62, 0x3a, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, -+ 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x3b, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, -+ 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74, -+ 0x68, 0x20, 0x22, 0x30, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, -+ 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x59, 0x41, 0x4e, 0x47, -+ 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x76, 0x69, -+ 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x0a, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x41, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74, -+ 0x68, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, -+ 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x20, 0x72, -+ 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x74, 0x61, 0x74, -+ 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, -+ 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, -+ 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, -+ 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, -+ 0x20, 0x20, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, -+ 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, 0x63, -+ 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, -+ 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x22, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6d, 0x6f, -+ 0x64, 0x75, 0x6c, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, -+ 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, -+ 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, -+ 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, -+ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65, -+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, -+ 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4e, 0x61, 0x6d, 0x65, 0x20, -+ 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, -+ 0x65, 0x64, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, -+ 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x74, 0x79, 0x70, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, -+ 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, -+ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65, -+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, -+ 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x65, 0x76, 0x69, 0x73, -+ 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, -+ 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x63, 0x68, 0x61, -+ 0x6e, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, -+ 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x69, 0x6e, -+ 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, -+ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4e, 0x65, 0x77, 0x20, 0x6d, -+ 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x69, 0x6e, -+ 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, -+ 0x6d, 0x20, 0x75, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, -+ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, -+ 0x75, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e, -+ 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, -+ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, -+ 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, -+ 0x20, 0x6e, 0x65, 0x77, 0x65, 0x72, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, -+ 0x69, 0x6f, 0x6e, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x66, 0x65, -+ 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, -+ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x22, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, -+ 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, -+ 0x61, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, -+ 0x75, 0x6d, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, -+ 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, -+ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x46, 0x65, 0x61, 0x74, 0x75, -+ 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, -+ 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x6f, 0x64, -+ 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, -+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, -+ 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, -+ 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, -+ 0x54, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, -+ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, -+ 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x6c, 0x65, 0x61, 0x66, 0x20, 0x6f, 0x6c, 0x64, 0x2d, 0x72, 0x65, 0x76, -+ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x6e, -+ 0x20, 0x22, 0x2e, 0x2e, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, -+ 0x3d, 0x20, 0x27, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x27, 0x22, -+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, -+ 0x65, 0x2d, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x6d, 0x61, 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, -+ 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, -+ 0x65, 0x61, 0x66, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, -+ 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, -+ 0x22, 0x2e, 0x2e, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d, -+ 0x20, 0x28, 0x27, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x65, -+ 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x27, 0x20, 0x6f, 0x72, 0x20, 0x27, -+ 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x69, 0x73, 0x61, -+ 0x62, 0x6c, 0x65, 0x64, 0x27, 0x29, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, -+ 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, -+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, -+ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65, -+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, -+ 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x00 -+}; -diff --git a/src/common.c b/src/common.c -index 10a379c0..72f3fc93 100644 ---- a/src/common.c -+++ b/src/common.c -@@ -55,10 +55,12 @@ - #include "modinfo.h" - #include "plugins_datastore.h" - #include "plugins_notification.h" -+#include "replay.h" - #include "shm_ext.h" - #include "shm_main.h" - #include "shm_mod.h" - #include "shm_sub.h" -+#include "subscr.h" - #include "sysrepo.h" - - #define SR_IS_YANG_ID_CHAR(c) (isalpha(c) || isdigit(c) || ((c) == '_') || ((c) == '-') || ((c) == '.')) -@@ -965,9 +967,9 @@ sr_module_default_mode(const struct lys_module *ly_mod) - if (!strcmp(ly_mod->name, "sysrepo")) { - return SR_INTMOD_MAIN_FILE_PERM; - } else if (sr_is_module_internal(ly_mod)) { -- if (!strcmp(ly_mod->name, "sysrepo-plugind") || !strcmp(ly_mod->name, "ietf-yang-schema-mount") || -- !strcmp(ly_mod->name, "ietf-yang-library") || !strcmp(ly_mod->name, "ietf-netconf-notifications") || -- !strcmp(ly_mod->name, "ietf-netconf")) { -+ if (!strcmp(ly_mod->name, "sysrepo-plugind") || !strcmp(ly_mod->name, "sysrepo-notifications") || -+ !strcmp(ly_mod->name, "ietf-yang-schema-mount") || !strcmp(ly_mod->name, "ietf-yang-library") || -+ !strcmp(ly_mod->name, "ietf-netconf-notifications") || !strcmp(ly_mod->name, "ietf-netconf")) { - return SR_INTMOD_WITHDATA_FILE_PERM; - } else if (!strcmp(ly_mod->name, "ietf-netconf-acm") || !strcmp(ly_mod->name, "sysrepo-monitoring")) { - return SR_INTMOD_NACM_SRMON_FILE_PERM; -@@ -5551,3 +5553,290 @@ sr_is_prod_env(void) - } - return sr_prod_env; - } -+ -+/** -+ * @brief Check whether a 'sysrepo-notifications' notification even need to be generated. -+ * -+ * @param[in] conn Connection to use. -+ * @param[in] shm_mod SHM mod. -+ * @param[out] subs_or_replay Set if there are any subscribers or replay is enabled for the module. -+ * @return err_info, NULL on success. -+ */ -+static sr_error_info_t * -+sr_generate_notif_has_subs_or_replay(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, int *subs_or_replay) -+{ -+ sr_error_info_t *err_info = NULL; -+ sr_mod_notif_sub_t *notif_subs; -+ uint32_t notif_sub_count; -+ -+ /* EXT READ LOCK */ -+ if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 0, __func__))) { -+ goto cleanup; -+ } -+ -+ /* get subscriber count */ -+ err_info = sr_notif_find_subscriber(conn, "sysrepo-notifications", ¬if_subs, ¬if_sub_count, NULL); -+ -+ /* EXT READ UNLOCK */ -+ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 0, __func__); -+ -+ if (err_info) { -+ goto cleanup; -+ } -+ -+ /* check replay support and subscribers */ -+ if (!shm_mod->replay_supp && !notif_sub_count) { -+ /* no subscribers and replay off */ -+ *subs_or_replay = 0; -+ } else { -+ *subs_or_replay = 1; -+ } -+ -+cleanup: -+ return err_info; -+} -+ -+/** -+ * @brief Send a generated notification. -+ * -+ * @param[in] conn Connection to use. -+ * @param[in] shm_mod SHM mod. -+ * @param[in] notif Notification to send. -+ * @return err_info, NULL on success. -+ */ -+static sr_error_info_t * -+sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_node *notif) -+{ -+ sr_error_info_t *err_info = NULL; -+ struct timespec notif_ts_mono, notif_ts_real; -+ -+ /* NOTIF SUB READ LOCK */ -+ if ((err_info = sr_rwlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, -+ __func__, NULL, NULL))) { -+ goto cleanup; -+ } -+ -+ /* remember when the notification was generated */ -+ sr_timeouttime_get(¬if_ts_mono, 0); -+ sr_realtime_get(¬if_ts_real); -+ -+ /* send the notification (non-validated, must be valid) */ -+ err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, 0, 0); -+ -+ /* NOTIF SUB READ UNLOCK */ -+ sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, __func__); -+ -+ if (err_info) { -+ goto cleanup; -+ } -+ -+ /* store the notification for a replay */ -+ if ((err_info = sr_replay_store(conn, NULL, notif, notif_ts_real))) { -+ goto cleanup; -+ } -+ -+cleanup: -+ return err_info; -+} -+ -+void -+sr_generate_notif_module_change_installed(sr_conn_ctx_t *conn, sr_int_install_mod_t *new_mods, uint32_t new_mod_count) -+{ -+ sr_error_info_t *err_info = NULL; -+ struct lyd_node *notif = NULL; -+ sr_mod_t *shm_mod; -+ uint32_t i; -+ int subs_or_replay; -+ -+ /* get this module */ -+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); -+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); -+ -+ /* check whether to even generate any notifications */ -+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { -+ goto cleanup; -+ } -+ -+ for (i = 0; i < new_mod_count; ++i) { -+ /* generate the notifcation */ -+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, -+ ¬if))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", new_mods[i].ly_mod->name))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", new_mods[i].ly_mod->revision))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "installed"))) { -+ goto cleanup; -+ } -+ -+ /* send it */ -+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { -+ goto cleanup; -+ } -+ -+ lyd_free_siblings(notif); -+ notif = NULL; -+ } -+ -+cleanup: -+ lyd_free_siblings(notif); -+ sr_errinfo_free(&err_info); -+} -+ -+void -+sr_generate_notif_module_change_uninstalled(sr_conn_ctx_t *conn, struct ly_set *mod_set) -+{ -+ sr_error_info_t *err_info = NULL; -+ struct lyd_node *notif = NULL; -+ const struct lys_module *ly_mod; -+ sr_mod_t *shm_mod; -+ uint32_t i; -+ int subs_or_replay; -+ -+ /* get this module */ -+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); -+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); -+ -+ /* check whether to even generate any notifications */ -+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { -+ goto cleanup; -+ } -+ -+ for (i = 0; i < mod_set->count; ++i) { -+ ly_mod = mod_set->objs[i]; -+ -+ /* generate the notifcation */ -+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, -+ ¬if))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "uninstalled"))) { -+ goto cleanup; -+ } -+ -+ /* send it */ -+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { -+ goto cleanup; -+ } -+ -+ lyd_free_siblings(notif); -+ notif = NULL; -+ } -+ -+cleanup: -+ lyd_free_siblings(notif); -+ sr_errinfo_free(&err_info); -+} -+ -+void -+sr_generate_notif_module_change_updated(sr_conn_ctx_t *conn, struct ly_set *old_mod_set, struct ly_set *upd_mod_set) -+{ -+ sr_error_info_t *err_info = NULL; -+ struct lyd_node *notif = NULL; -+ const struct lys_module *ly_mod; -+ sr_mod_t *shm_mod; -+ uint32_t i; -+ int subs_or_replay; -+ -+ /* get this module */ -+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); -+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); -+ -+ /* check whether to even generate any notifications */ -+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { -+ goto cleanup; -+ } -+ -+ for (i = 0; i < upd_mod_set->count; ++i) { -+ ly_mod = upd_mod_set->objs[i]; -+ -+ /* generate the notifcation */ -+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, -+ ¬if))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "updated"))) { -+ goto cleanup; -+ } -+ -+ ly_mod = old_mod_set->objs[i]; -+ if ((err_info = sr_lyd_new_term(notif, NULL, "old-revision", ly_mod->revision))) { -+ goto cleanup; -+ } -+ -+ /* send it */ -+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { -+ goto cleanup; -+ } -+ -+ lyd_free_siblings(notif); -+ notif = NULL; -+ } -+ -+cleanup: -+ lyd_free_siblings(notif); -+ sr_errinfo_free(&err_info); -+} -+ -+void -+sr_generate_notif_module_change_feature(sr_conn_ctx_t *conn, const struct lys_module *ly_mod, const char *feature_name, -+ int enabled) -+{ -+ sr_error_info_t *err_info = NULL; -+ struct lyd_node *notif = NULL; -+ sr_mod_t *shm_mod; -+ int subs_or_replay; -+ -+ /* get this module */ -+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications"); -+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup); -+ -+ /* check whether to even generate any notifications */ -+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) { -+ goto cleanup; -+ } -+ -+ /* generate the notifcation */ -+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL, -+ ¬if))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) { -+ goto cleanup; -+ } -+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", enabled ? "feature-enabled" : "feature-disabled"))) { -+ goto cleanup; -+ } -+ -+ if ((err_info = sr_lyd_new_term(notif, NULL, "feature-name", feature_name))) { -+ goto cleanup; -+ } -+ -+ /* send it */ -+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) { -+ goto cleanup; -+ } -+ -+cleanup: -+ lyd_free_siblings(notif); -+ sr_errinfo_free(&err_info); -+} -diff --git a/src/common.h b/src/common.h -index 99304bd7..9fc675d4 100644 ---- a/src/common.h -+++ b/src/common.h -@@ -4,8 +4,8 @@ - * @brief common routines header - * - * @copyright -- * Copyright (c) 2018 - 2023 Deutsche Telekom AG. -- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. -+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. -+ * Copyright (c) 2018 - 2025 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. -@@ -1371,4 +1371,42 @@ sr_error_info_t *sr_conn_info(sr_cid_t **cids, pid_t **pids, uint32_t *count, sr - */ - int sr_is_prod_env(void); - -+/** -+ * @brief Generate the 'module-change' notification for newly installed modules. -+ * -+ * @param[in] conn Connection to use. -+ * @param[in] new_mods Array of new modules. -+ * @param[in] new_mod_count Count of @p new_mods. -+ */ -+void sr_generate_notif_module_change_installed(sr_conn_ctx_t *conn, sr_int_install_mod_t *new_mods, -+ uint32_t new_mod_count); -+ -+/** -+ * @brief Generate the 'module-change' notification for uninstalled modules. -+ * -+ * @param[in] conn Connection to use. -+ * @param[in] mod_set Set of uninstalled modules. -+ */ -+void sr_generate_notif_module_change_uninstalled(sr_conn_ctx_t *conn, struct ly_set *mod_set); -+ -+/** -+ * @brief Generate the 'module-change' notification for updated modules. -+ * -+ * @param[in] conn Connection to use. -+ * @param[in] old_mod_set Set of old modules. -+ * @param[in] upd_mod_set Set of updated modules. -+ */ -+void sr_generate_notif_module_change_updated(sr_conn_ctx_t *conn, struct ly_set *old_mod_set, struct ly_set *upd_mod_set); -+ -+/** -+ * @brief Generate the 'module-change' notification for a module feature change. -+ * -+ * @param[in] conn Connection to use. -+ * @param[in] ly_mod Changed module. -+ * @param[in] feature_name Changed feature name. -+ * @param[in] enabled Whether the feature was enabled or disabled. -+ */ -+void sr_generate_notif_module_change_feature(sr_conn_ctx_t *conn, const struct lys_module *ly_mod, -+ const char *feature_name, int enabled); -+ - #endif /* _COMMON_H */ -diff --git a/src/lyd_mods.c b/src/lyd_mods.c -index 256f292e..77f8cd7b 100644 ---- a/src/lyd_mods.c -+++ b/src/lyd_mods.c -@@ -65,6 +65,7 @@ - #include "../modules/ietf_netconf_yang.h" - #include "../modules/ietf_origin_yang.h" - #include "../modules/sysrepo_monitoring_yang.h" -+#include "../modules/sysrepo_notifications_yang.h" - #include "../modules/sysrepo_plugind_yang.h" - - /** -@@ -1000,6 +1001,9 @@ sr_lydmods_create(sr_conn_ctx_t *conn, struct ly_ctx *ly_ctx, struct lyd_node ** - /* install sysrepo-plugind */ - SR_INSTALL_INT_MOD(ly_ctx, sysrepo_plugind_yang, 0, new_mods, new_mod_count); - -+ /* install sysrepo-notifications */ -+ SR_INSTALL_INT_MOD(ly_ctx, sysrepo_notifications_yang, 0, new_mods, new_mod_count); -+ - /* install ietf-netconf (implemented dependency) and ietf-netconf-with-defaults */ - SR_INSTALL_INT_MOD(ly_ctx, ietf_netconf_yang, 1, new_mods, new_mod_count); - SR_INSTALL_INT_MOD(ly_ctx, ietf_netconf_with_defaults_yang, 0, new_mods, new_mod_count); -diff --git a/src/modinfo.c b/src/modinfo.c -index 14d0b2ae..7414121e 100644 ---- a/src/modinfo.c -+++ b/src/modinfo.c -@@ -3824,7 +3824,7 @@ sr_modinfo_generate_config_change_notif(struct sr_mod_info_s *mod_info, sr_sessi - } - - /* store the notification for a replay */ -- if ((err_info = sr_replay_store(session, notif, notif_ts_real))) { -+ if ((err_info = sr_replay_store(mod_info->conn, session, notif, notif_ts_real))) { - goto cleanup; - } - -diff --git a/src/replay.c b/src/replay.c -index c31043de..00e63eec 100644 ---- a/src/replay.c -+++ b/src/replay.c -@@ -127,7 +127,7 @@ cleanup: - } - - sr_error_info_t * --sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts) -+sr_replay_store(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts) - { - sr_error_info_t *err_info = NULL; - sr_mod_t *shm_mod; -@@ -146,7 +146,7 @@ sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct tim - SR_CHECK_INT_RET(notif_op->schema->nodetype != LYS_NOTIF, err_info); - - /* find SHM mod for replay lock and check if replay is even supported */ -- shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(sess->conn), ly_mod->name); -+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), ly_mod->name); - SR_CHECK_INT_RET(!shm_mod, err_info); - - if (!shm_mod->replay_supp) { -@@ -154,32 +154,34 @@ sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct tim - return NULL; - } - -- /* MUTEX LOCK */ -- sr_timeouttime_get(&timeout_ts, SR_NOTIF_BUF_LOCK_TIMEOUT); -- if ((r = pthread_mutex_clocklock(&sess->notif_buf.lock.mutex, COMPAT_CLOCK_ID, &timeout_ts))) { -- SR_ERRINFO_LOCK(&err_info, __func__, r); -- return err_info; -- } -+ if (sess) { -+ /* MUTEX LOCK */ -+ sr_timeouttime_get(&timeout_ts, SR_NOTIF_BUF_LOCK_TIMEOUT); -+ if ((r = pthread_mutex_clocklock(&sess->notif_buf.lock.mutex, COMPAT_CLOCK_ID, &timeout_ts))) { -+ SR_ERRINFO_LOCK(&err_info, __func__, r); -+ return err_info; -+ } - -- if (sess->notif_buf.thread_running) { -- /* store the notification in the buffer */ -- has_buf = 1; -- err_info = sr_notif_buf_store(sess, notif, notif_ts); -+ if (sess->notif_buf.thread_running) { -+ /* store the notification in the buffer */ -+ has_buf = 1; -+ err_info = sr_notif_buf_store(sess, notif, notif_ts); - -- /* broadcast condition */ -- sr_cond_broadcast(&sess->notif_buf.lock.cond); -- } -+ /* broadcast condition */ -+ sr_cond_broadcast(&sess->notif_buf.lock.cond); -+ } - -- /* MUTEX UNLOCK */ -- pthread_mutex_unlock(&sess->notif_buf.lock.mutex); -+ /* MUTEX UNLOCK */ -+ pthread_mutex_unlock(&sess->notif_buf.lock.mutex); - -- if (err_info) { -- return err_info; -+ if (err_info) { -+ return err_info; -+ } - } - - if (!has_buf) { - /* write the notification to a replay file */ -- if ((err_info = sr_notif_write(sess->conn, shm_mod, notif, notif_ts))) { -+ if ((err_info = sr_notif_write(conn, shm_mod, notif, notif_ts))) { - return err_info; - } - } -diff --git a/src/replay.h b/src/replay.h -index ecf0cf25..7f3a851a 100644 ---- a/src/replay.h -+++ b/src/replay.h -@@ -4,8 +4,8 @@ - * @brief header for notification replay routines - * - * @copyright -- * Copyright (c) 2018 - 2021 Deutsche Telekom AG. -- * Copyright (c) 2018 - 2021 CESNET, z.s.p.o. -+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. -+ * Copyright (c) 2018 - 2025 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. -@@ -26,12 +26,14 @@ - /** - * @brief Store a notification for replay. - * -- * @param[in] sess Session to use. -+ * @param[in] conn Connection to use. -+ * @param[in] sess Session to use, if available. - * @param[in] notif Notification to store. - * @param[in] notif_ts Notification timestamp to store. - * @return err_info, NULL on success. - */ --sr_error_info_t *sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts); -+sr_error_info_t *sr_replay_store(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, const struct lyd_node *notif, -+ struct timespec notif_ts); - - /** - * @brief Notification buffer thread. -diff --git a/src/sysrepo.c b/src/sysrepo.c -index 7c96243c..614b5454 100644 ---- a/src/sysrepo.c -+++ b/src/sysrepo.c -@@ -4,8 +4,8 @@ - * @brief sysrepo API routines - * - * @copyright -- * Copyright (c) 2018 - 2024 Deutsche Telekom AG. -- * Copyright (c) 2018 - 2024 CESNET, z.s.p.o. -+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. -+ * Copyright (c) 2018 - 2025 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. -@@ -1541,6 +1541,9 @@ _sr_install_modules(sr_conn_ctx_t *conn, const char *search_dirs, const char *da - SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); - sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); - -+ /* send the notification */ -+ sr_generate_notif_module_change_installed(conn, *new_mods, *new_mod_count); -+ - goto cleanup; - - error: -@@ -1859,6 +1862,9 @@ sr_remove_modules(sr_conn_ctx_t *conn, const char **module_names, int force) - SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); - sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); - -+ /* send the notification */ -+ sr_generate_notif_module_change_uninstalled(conn, &mod_set); -+ - cleanup: - sr_lycc_update_data_clear(&data_info); - lyd_free_siblings(sr_mods); -@@ -2104,6 +2110,9 @@ sr_update_modules(sr_conn_ctx_t *conn, const char **schema_paths, const char *se - SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); - sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); - -+ /* send the notification */ -+ sr_generate_notif_module_change_updated(conn, &old_mod_set, &upd_mod_set); -+ - cleanup: - sr_lycc_update_data_clear(&data_info); - lyd_free_siblings(sr_mods); -@@ -2606,6 +2615,9 @@ sr_change_module_feature(sr_conn_ctx_t *conn, const char *module_name, const cha - SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx); - sr_conn_ctx_switch(conn, &new_ctx, &old_ctx); - -+ /* send the notification */ -+ sr_generate_notif_module_change_feature(conn, ly_mod, feature_name, enable); -+ - cleanup: - sr_lycc_update_data_clear(&data_info); - lyd_free_siblings(sr_mods); -@@ -2735,6 +2747,8 @@ sr_is_module_internal(const struct lys_module *ly_mod) - return 1; - } else if (!strcmp(ly_mod->name, "sysrepo-plugind")) { - return 1; -+ } else if (!strcmp(ly_mod->name, "sysrepo-notifications")) { -+ return 1; - } else if (!strcmp(ly_mod->name, "ietf-netconf-acm")) { - return 1; - } -@@ -7407,7 +7421,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t - } - - /* store the notification for a replay */ -- if ((err_info = sr_replay_store(session, notif_top, notif_ts_real))) { -+ if ((err_info = sr_replay_store(session->conn, session, notif_top, notif_ts_real))) { - goto cleanup; - } - -diff --git a/tests/test_oper_pull.c b/tests/test_oper_pull.c -index c9654faa..91ba526e 100644 ---- a/tests/test_oper_pull.c -+++ b/tests/test_oper_pull.c -@@ -406,6 +406,9 @@ test_sr_mon(void **state) - " \n" - " \n" - " \n" -+ " sysrepo-notifications\n" -+ " \n" -+ " \n" - " ietf-netconf\n" - " \n" - " \n" -@@ -669,6 +672,9 @@ test_sr_mon(void **state) - " \n" - " \n" - " \n" -+ " sysrepo-notifications\n" -+ " \n" -+ " \n" - " ietf-netconf\n" - " \n" - " \n" --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch b/patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch deleted file mode 100644 index d77e621c..00000000 --- a/patches/sysrepo/3.3.10/0006-SOVERSION-bump-to-version-7.29.12.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 05ef34960277dca49e1dda26eb2f4ffd5f1ba354 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Mon, 13 Jan 2025 11:19:51 +0100 -Subject: [PATCH 06/22] SOVERSION bump to version 7.29.12 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 9f3cfde0..fcb49a24 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_ - # with backward compatible change and micro version is connected with any internal change of the library. - set(SYSREPO_MAJOR_SOVERSION 7) - set(SYSREPO_MINOR_SOVERSION 29) --set(SYSREPO_MICRO_SOVERSION 11) -+set(SYSREPO_MICRO_SOVERSION 12) - set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION}) - set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION}) - --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch b/patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch deleted file mode 100644 index 719a9aad..00000000 --- a/patches/sysrepo/3.3.10/0007-VERSION-bump-to-version-3.4.0.patch +++ /dev/null @@ -1,32 +0,0 @@ -From 74f5fa11043e579b6c007a2ff1f5f1d880637b92 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Mon, 13 Jan 2025 11:20:00 +0100 -Subject: [PATCH 07/22] VERSION bump to version 3.4.0 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - CMakeLists.txt | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index fcb49a24..7d49cedb 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -53,8 +53,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - # minor version changes with added functionality (new tool, functionality of the tool or library, ...) and - # micro version is changed with a set of small changes or bugfixes anywhere in the project. - set(SYSREPO_MAJOR_VERSION 3) --set(SYSREPO_MINOR_VERSION 3) --set(SYSREPO_MICRO_VERSION 11) -+set(SYSREPO_MINOR_VERSION 4) -+set(SYSREPO_MICRO_VERSION 0) - set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION}) - - # Version of the library --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch b/patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch deleted file mode 100644 index 50f3eb8b..00000000 --- a/patches/sysrepo/3.3.10/0008-shmext-REFACTOR-conn_remap_unlock-remove-last-hole.patch +++ /dev/null @@ -1,62 +0,0 @@ -From a0e02874aaf24a9e94c501ac95c5b9f55c316500 Mon Sep 17 00:00:00 2001 -From: IrfanMohammad -Date: Mon, 13 Jan 2025 08:19:04 +0000 -Subject: [PATCH 08/22] shmext REFACTOR conn_remap_unlock remove last hole -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -The logic in sr_shmext_conn_remap_unlock can be cleaner. - -A call to sr_file_get_size() can be avoided since we already know it -as `conn->ext_shm.size`. - -Also cleanup the somewhat complex cast + arithmetic in a if condition. - -Signed-off-by: Mattias Walström ---- - src/shm_ext.c | 16 +++++++--------- - 1 file changed, 7 insertions(+), 9 deletions(-) - -diff --git a/src/shm_ext.c b/src/shm_ext.c -index f4103837..78930469 100644 ---- a/src/shm_ext.c -+++ b/src/shm_ext.c -@@ -131,8 +131,8 @@ sr_shmext_conn_remap_unlock(sr_conn_ctx_t *conn, sr_lock_mode_t mode, int ext_lo - { - sr_error_info_t *err_info = NULL; - sr_ext_hole_t *iter, *last = NULL; -- uint32_t last_size; -- size_t shm_file_size = 0; -+ uint32_t new_size; -+ char *last_hole_end; - - /* make ext SHM smaller if there is a memory hole at its end */ - if (((mode == SR_LOCK_WRITE) || (mode == SR_LOCK_WRITE_URGE)) && ext_lock) { -@@ -140,17 +140,15 @@ sr_shmext_conn_remap_unlock(sr_conn_ctx_t *conn, sr_lock_mode_t mode, int ext_lo - last = iter; - } - -- if (last && ((uint32_t)((char *)last - conn->ext_shm.addr) + last->size == conn->ext_shm.size)) { -- if ((err_info = sr_file_get_size(conn->ext_shm.fd, &shm_file_size))) { -- goto cleanup_unlock; -- } -+ /* cast `last` as a char* for correct pointer arithmetic. */ -+ last_hole_end = last ? ((char *)last + last->size) : NULL; - -+ if (last_hole_end == conn->ext_shm.addr + conn->ext_shm.size) { - /* remove the hole */ -- last_size = last->size; -+ new_size = conn->ext_shm.size - last->size; - sr_ext_hole_del(SR_CONN_EXT_SHM(conn), last); -- - /* remap (and truncate) ext SHM */ -- if ((err_info = sr_shm_remap(&conn->ext_shm, shm_file_size - last_size))) { -+ if ((err_info = sr_shm_remap(&conn->ext_shm, new_size))) { - goto cleanup_unlock; - } - } --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch b/patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch deleted file mode 100644 index 843ba76c..00000000 --- a/patches/sysrepo/3.3.10/0009-modinfo-UPDATE-sess-push-oper-data-module-cache.patch +++ /dev/null @@ -1,250 +0,0 @@ -From e0f1e2480a3f69e291deed06a2626ed9b4df1662 Mon Sep 17 00:00:00 2001 -From: IrfanMohammad -Date: Mon, 13 Jan 2025 13:46:28 +0000 -Subject: [PATCH 09/22] modinfo UPDATE sess push oper data module cache -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Maintain a list of modules whose push oper data were modified -by the session. - -Acquiring the global ext_lock is significantly more expensive. - -Previously this was implemented for connections in -3b0165d656709b5f0cd3e8debc2e55c76c73663a - -But in sysrepo v3.x, oper push data is tied to the session and the logic -needs to be re-introduced. - -Signed-off-by: Mattias Walström ---- - src/common_types.h | 7 +++- - src/modinfo.c | 93 ++++++++++++++++++++++++++-------------------- - src/sysrepo.c | 14 ++++++- - 3 files changed, 71 insertions(+), 43 deletions(-) - -diff --git a/src/common_types.h b/src/common_types.h -index 915d9196..3ce27ad4 100644 ---- a/src/common_types.h -+++ b/src/common_types.h -@@ -180,7 +180,12 @@ struct sr_session_ctx_s { - char *user; /**< Session (system) user. */ - char *nacm_user; /**< Optional NACM user. If set, NACM is applied. */ - sr_error_info_t *err_info; /**< Session error information. */ -- int push_oper_data; /**< Whether the session has stored any push operational data. */ -+ -+ struct sr_oper_push_cache_s { -+ char *name; /**< Module name whose push oper data were ever modified by this session. */ -+ int has_data; /**< Flag if there are any actual data currently. */ -+ } *oper_push_mods; -+ uint32_t oper_push_mod_count; /**< Count of modules with modified push oper data by this session. */ - - char *orig_name; /**< Originator name used for all events sent on this session. */ - void *orig_data; /**< Originator data used for all events sent on this session. */ -diff --git a/src/modinfo.c b/src/modinfo.c -index 7414121e..d5c27672 100644 ---- a/src/modinfo.c -+++ b/src/modinfo.c -@@ -322,42 +322,26 @@ sr_error_info_t * - sr_modinfo_collect_oper_sess(sr_session_ctx_t *sess, const struct lys_module *ly_mod, struct sr_mod_info_s *mod_info) - { - sr_error_info_t *err_info = NULL; -- sr_mod_shm_t *mod_shm; -- sr_mod_t *shm_mod; -- sr_mod_oper_push_t *oper_push; - const struct lys_module *ly_mod2; -- uint32_t i, j; -- -- mod_shm = SR_CONN_MOD_SHM(sess->conn); -- -- /* EXT READ LOCK */ -- if ((err_info = sr_shmext_conn_remap_lock(sess->conn, SR_LOCK_READ, 1, __func__))) { -- return err_info; -- } -- -- /* go through all the SHM modules */ -- for (i = 0; i < mod_shm->mod_count; ++i) { -- shm_mod = SR_SHM_MOD_IDX(mod_shm, i); -+ uint32_t i; - -- if (ly_mod && strcmp(ly_mod->name, ((char *)mod_shm) + shm_mod->name)) { -+ /* add only the cached modules */ -+ for (i = 0; i < sess->oper_push_mod_count; ++i) { -+ if (ly_mod && strcmp(ly_mod->name, sess->oper_push_mods[i].name)) { - continue; - } - -- /* go through the sessions with oper data */ -- for (j = 0; j < shm_mod->oper_push_data_count; ++j) { -- oper_push = &((sr_mod_oper_push_t *)(sess->conn->ext_shm.addr + shm_mod->oper_push_data))[j]; -- if ((oper_push->sid == sess->sid) && oper_push->has_data) { -- break; -- } -+ if (!sess->oper_push_mods[i].has_data) { -+ continue; - } - -- if (j < shm_mod->oper_push_data_count) { -- /* remember the module */ -- ly_mod2 = ly_ctx_get_module_implemented(sess->conn->ly_ctx, ((char *)mod_shm) + shm_mod->name); -- SR_CHECK_INT_GOTO(!ly_mod2, err_info, cleanup_unlock); -- if ((err_info = sr_modinfo_add(ly_mod2, NULL, 0, 0, mod_info))) { -- goto cleanup_unlock; -- } -+ ly_mod2 = ly_ctx_get_module_implemented(sess->conn->ly_ctx, sess->oper_push_mods[i].name); -+ if (!ly_mod2) { -+ /* could have been removed */ -+ continue; -+ } -+ if ((err_info = sr_modinfo_add(ly_mod2, NULL, 0, 0, mod_info))) { -+ return err_info; - } - - if (ly_mod) { -@@ -365,11 +349,7 @@ sr_modinfo_collect_oper_sess(sr_session_ctx_t *sess, const struct lys_module *ly - } - } - --cleanup_unlock: -- /* EXT READ UNLOCK */ -- sr_shmext_conn_remap_unlock(sess->conn, SR_LOCK_READ, 1, __func__); -- -- return err_info; -+ return NULL; - } - - sr_error_info_t * -@@ -2664,7 +2644,7 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m - struct lyd_node *mod_data = NULL; - const char **xpaths; - uint32_t xpath_count; -- int modified, has_data; -+ int modified, has_data = 0; - char *orig_name = NULL; - void *orig_data = NULL; - -@@ -2674,8 +2654,8 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m - if ((mod_info->ds == SR_DS_OPERATIONAL) && (mod_info->ds2 == SR_DS_OPERATIONAL)) { - assert(sess); - -- /* check whether this session even has any push oper data for this module */ -- if ((err_info = sr_shmext_oper_push_get(conn, mod->shm_mod, mod->ly_mod->name, sess->sid, NULL, &has_data, -+ /* check whether this session even has any push oper data and next push data for this module */ -+ if (sess->oper_push_mod_count && (err_info = sr_shmext_oper_push_get(conn, mod->shm_mod, mod->ly_mod->name, sess->sid, NULL, &has_data, - SR_LOCK_READ))) { - return err_info; - } -@@ -2735,7 +2715,7 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m - /* no cached data or unusable */ - - if ((mod_info->ds == SR_DS_OPERATIONAL) && (mod_info->ds2 == SR_DS_RUNNING)) { -- /* we need the whole runnign DS to avoid not getting parents of oper pull subscriptions and so considering -+ /* we need the whole running DS to avoid not getting parents of oper pull subscriptions and so considering - * them incorrectly as non-existent */ - xpaths = NULL; - xpath_count = 0; -@@ -3839,6 +3819,35 @@ cleanup: - return err_info; - } - -+static -+sr_error_info_t * -+sr_modinfo_push_oper_mod_update_sess(sr_session_ctx_t *sess, const char *mod_name, int has_data) -+{ -+ sr_error_info_t *err_info = NULL; -+ uint32_t i; -+ void *mem; -+ -+ for (i = 0; i < sess->oper_push_mod_count; ++i) { -+ if (!strcmp(sess->oper_push_mods[i].name, mod_name)) { -+ /* already added, update has_data */ -+ sess->oper_push_mods[i].has_data = has_data; -+ return NULL; -+ } -+ } -+ -+ /* add new module */ -+ mem = realloc(sess->oper_push_mods, (i + 1) * sizeof *(sess->oper_push_mods)); -+ SR_CHECK_MEM_GOTO(!mem, err_info, cleanup); -+ sess->oper_push_mods = mem; -+ -+ sess->oper_push_mods[i].name = strdup(mod_name); -+ SR_CHECK_MEM_GOTO(!sess->oper_push_mods[i].name, err_info, cleanup); -+ sess->oper_push_mods[i].has_data = has_data; -+ ++sess->oper_push_mod_count; -+cleanup: -+ return err_info; -+} -+ - sr_error_info_t * - sr_modinfo_data_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del) - { -@@ -3911,12 +3920,14 @@ sr_modinfo_data_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, - } else { - /* stored oper data, update info in mod/ext SHM */ - if ((err_info = sr_shmext_oper_push_update(mod_info->conn, mod->shm_mod, mod->ly_mod->name, -- sid, 0, mod_data ? 1 : 0, SR_LOCK_WRITE))) { -+ sid, 0, !!mod_data, SR_LOCK_WRITE))) { - goto cleanup; - } - -- /* remember to discard the push oper data on session stop */ -- session->push_oper_data = 1; -+ /* cache the modified module in the session */ -+ if ((err_info = sr_modinfo_push_oper_mod_update_sess(session, mod->ly_mod->name, !!mod_data))) { -+ goto cleanup; -+ } - } - } - } -diff --git a/src/sysrepo.c b/src/sysrepo.c -index 614b5454..797de6aa 100644 ---- a/src/sysrepo.c -+++ b/src/sysrepo.c -@@ -713,6 +713,7 @@ _sr_session_stop(sr_session_ctx_t *session) - { - sr_error_info_t *err_info = NULL, *tmp_err; - sr_datastore_t ds; -+ uint32_t i; - - /* subscriptions need to be freed before, with a WRITE lock */ - assert(!session->subscription_count && !session->subscriptions); -@@ -721,7 +722,7 @@ _sr_session_stop(sr_session_ctx_t *session) - tmp_err = sr_session_notif_buf_stop(session); - sr_errinfo_merge(&err_info, tmp_err); - -- if (session->push_oper_data) { -+ if (session->oper_push_mod_count) { - /* free any stored operational data and the SHM ext push oper data entries */ - _sr_discard_oper_changes(session, NULL, 1, 0); - } -@@ -755,6 +756,13 @@ _sr_session_stop(sr_session_ctx_t *session) - sr_release_data(session->dt[ds].edit); - lyd_free_all(session->dt[ds].diff); - } -+ -+ /* free any push oper module names */ -+ for (i = 0; i < session->oper_push_mod_count; ++i) { -+ free(session->oper_push_mods[i].name); -+ } -+ free(session->oper_push_mods); -+ - sr_rwlock_destroy(&session->notif_buf.lock); - free(session); - -@@ -4708,6 +4716,10 @@ sr_get_oper_changes(sr_session_ctx_t *session, const char *module_name, sr_data_ - - *data = NULL; - -+ if (!session->oper_push_mod_count) { -+ return sr_api_ret(session, err_info); -+ } -+ - SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); - - /* CONTEXT LOCK */ --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch b/patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch deleted file mode 100644 index f60f5b3a..00000000 --- a/patches/sysrepo/3.3.10/0010-modinfo-REFACTOR-formatting-and-missing-doxygen.patch +++ /dev/null @@ -1,39 +0,0 @@ -From fef86228fcb8636d53faf43b36afd92b7161162b Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Tue, 14 Jan 2025 08:53:59 +0100 -Subject: [PATCH 10/22] modinfo REFACTOR formatting and missing doxygen -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - src/modinfo.c | 11 +++++++++-- - 1 file changed, 9 insertions(+), 2 deletions(-) - -diff --git a/src/modinfo.c b/src/modinfo.c -index d5c27672..ff6ffc40 100644 ---- a/src/modinfo.c -+++ b/src/modinfo.c -@@ -3819,8 +3819,15 @@ cleanup: - return err_info; - } - --static --sr_error_info_t * -+/** -+ * @brief Update push oper mod data in a session, add a new module if not yet present. -+ * -+ * @param[in] sess Session to update. -+ * @param[in] mod_name Module name. -+ * @param[in] has_data Flag to store. -+ * @return err_info, NULL on success. -+ */ -+static sr_error_info_t * - sr_modinfo_push_oper_mod_update_sess(sr_session_ctx_t *sess, const char *mod_name, int has_data) - { - sr_error_info_t *err_info = NULL; --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch b/patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch deleted file mode 100644 index 86e01c51..00000000 --- a/patches/sysrepo/3.3.10/0011-shm_ext-BUGFIX-remap-READ-lock-is-enough-3496.patch +++ /dev/null @@ -1,50 +0,0 @@ -From a593e1c993ae4dd7aa2942aa046633c8aaeb5ee4 Mon Sep 17 00:00:00 2001 -From: Irfan -Date: Tue, 14 Jan 2025 08:59:44 +0100 -Subject: [PATCH 11/22] shm_ext BUGFIX remap READ lock is enough (#3496) -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -`sr_shmext_rpc_sub_remove_dead()` calls `sr_shmext_rpc_sub_stop()` -which doesn't need a WRITE `ext_remap_lock`. - -sr_shmext_recover_sub_rpc() already only acquire a READ lock. - -Subscriptions can only be removed, which does not remap the SHM. - -Signed-off-by: Mattias Walström ---- - src/shm_ext.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/src/shm_ext.c b/src/shm_ext.c -index 78930469..917a049a 100644 ---- a/src/shm_ext.c -+++ b/src/shm_ext.c -@@ -1476,8 +1476,8 @@ sr_shmext_rpc_sub_remove_dead(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_co - sr_mod_rpc_sub_t *shm_sub; - char *path = NULL; - -- /* EXT WRITE LOCK */ -- if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_WRITE, 1, __func__))) { -+ /* EXT READ LOCK */ -+ if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 1, __func__))) { - sr_errinfo_free(&err_info); - return; - } -@@ -1502,8 +1502,8 @@ sr_shmext_rpc_sub_remove_dead(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_co - path = NULL; - } - -- /* EXT WRITE UNLOCK */ -- sr_shmext_conn_remap_unlock(conn, SR_LOCK_WRITE, 1, __func__); -+ /* EXT READ UNLOCK */ -+ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); - } - - sr_error_info_t * --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch b/patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch deleted file mode 100644 index c04f2132..00000000 --- a/patches/sysrepo/3.3.10/0012-modinfo-OPTIMIZE-avoid-long-ext-lock-holding.patch +++ /dev/null @@ -1,162 +0,0 @@ -From ce908d743bc9f49d5986c748dba71be194b7df89 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Tue, 14 Jan 2025 09:57:49 +0100 -Subject: [PATCH 12/22] modinfo OPTIMIZE avoid long ext lock holding -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Fixes #3497 - -Signed-off-by: Mattias Walström ---- - src/modinfo.c | 77 +++++++++++++++++++++++++++++++-------------------- - 1 file changed, 47 insertions(+), 30 deletions(-) - -diff --git a/src/modinfo.c b/src/modinfo.c -index ff6ffc40..f969f363 100644 ---- a/src/modinfo.c -+++ b/src/modinfo.c -@@ -4,8 +4,8 @@ - * @brief routines for working with modinfo structure - * - * @copyright -- * Copyright (c) 2018 - 2023 Deutsche Telekom AG. -- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. -+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. -+ * Copyright (c) 2018 - 2025 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. -@@ -1254,49 +1254,69 @@ sr_module_oper_data_load(struct sr_mod_info_mod_s *mod, sr_conn_ctx_t *conn, uin - struct lyd_node **mod_oper_data, struct lyd_node **data) - { - sr_error_info_t *err_info = NULL; -- sr_mod_oper_push_t *oper_push; -+ sr_mod_oper_push_t *oper_push_dup = NULL, *oper_push_ext; - struct lyd_node *mod_data = NULL, *next, *node; - const struct lys_module *ly_mod; - const char *xpath; - struct ly_set *set; -- uint32_t i, j, merge_opts; -- int last_sid = 0; -+ uint32_t i, j, merge_opts, oper_push_count = 0; -+ int last_sid = 0, dead_cid = 0; - - /* EXT READ LOCK */ - if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 1, __func__))) { -- return err_info; -+ goto cleanup; - } - -- oper_push = (sr_mod_oper_push_t *)(conn->ext_shm.addr + mod->shm_mod->oper_push_data); -- for (i = 0; i < mod->shm_mod->oper_push_data_count; ++i) { -- if (!sr_conn_is_alive(oper_push[i].cid)) { -- /* EXT READ UNLOCK */ -- sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); -- -- /* recover oper push data of all dead connections */ -- if ((err_info = sr_shmmod_del_module_oper_data(conn, mod->ly_mod, &mod->state, mod->shm_mod, 1))) { -- goto cleanup; -+ if (mod->shm_mod->oper_push_data_count) { -+ /* make a local copy of the array, with only alive connections */ -+ oper_push_dup = malloc(mod->shm_mod->oper_push_data_count * sizeof *oper_push_dup); -+ if (!oper_push_dup) { -+ SR_ERRINFO_MEM(&err_info); -+ } else { -+ oper_push_ext = (sr_mod_oper_push_t *)(conn->ext_shm.addr + mod->shm_mod->oper_push_data); -+ for (i = 0; i < mod->shm_mod->oper_push_data_count; ++i) { -+ if (!sr_conn_is_alive(oper_push_ext[i].cid)) { -+ /* remember to remove the dead connections */ -+ dead_cid = 1; -+ } else { -+ oper_push_dup[oper_push_count] = oper_push_ext[i]; -+ ++oper_push_count; -+ } - } -+ } -+ } -+ -+ /* EXT READ UNLOCK */ -+ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); -+ -+ if (err_info) { -+ goto cleanup; -+ } - -- /* call the function again */ -- err_info = sr_module_oper_data_load(mod, conn, sid, mod_oper_data, data); -+ if (dead_cid) { -+ /* recover oper push data of all dead connections */ -+ if ((err_info = sr_shmmod_del_module_oper_data(conn, mod->ly_mod, &mod->state, mod->shm_mod, 1))) { - goto cleanup; -- } else if (!oper_push[i].has_data) { -+ } -+ } -+ -+ for (i = 0; i < oper_push_count; ++i) { -+ if (!oper_push_dup[i].has_data) { - /* no push oper data */ - continue; - } - - /* push data entries are ordered */ -- assert(!i || (oper_push[i].order > oper_push[i - 1].order)); -+ assert(!i || (oper_push_dup[i].order > oper_push_dup[i - 1].order)); - -- if (oper_push[i].sid == sid) { -+ if (oper_push_dup[i].sid == sid) { - last_sid = 1; - } - if (!last_sid || !mod_oper_data) { - /* load push oper data for the session */ -- if ((err_info = sr_module_file_data_append(mod->ly_mod, mod->ds_handle, SR_DS_OPERATIONAL, oper_push[i].cid, -- oper_push[i].sid, NULL, 0, &mod_data))) { -- goto cleanup_unlock; -+ if ((err_info = sr_module_file_data_append(mod->ly_mod, mod->ds_handle, SR_DS_OPERATIONAL, -+ oper_push_dup[i].cid, oper_push_dup[i].sid, NULL, 0, &mod_data))) { -+ goto cleanup; - } - } else { - use_mod_oper_data: -@@ -1324,12 +1344,12 @@ use_mod_oper_data: - - /* select the nodes to remove */ - if ((err_info = sr_lyd_find_xpath(*data, xpath, &set))) { -- goto cleanup_unlock; -+ goto cleanup; - } - - /* get rid of all redundant results that are descendants of another result */ - if ((err_info = sr_xpath_set_filter_subtrees(set))) { -- goto cleanup_unlock; -+ goto cleanup; - } - - /* free all the selected subtrees */ -@@ -1346,7 +1366,7 @@ use_mod_oper_data: - merge_opts = LYD_MERGE_WITH_FLAGS; - } - if ((err_info = sr_lyd_merge_module(data, mod_data, mod->ly_mod, sr_oper_data_merge_cb, NULL, merge_opts))) { -- goto cleanup_unlock; -+ goto cleanup; - } - mod_data = NULL; - -@@ -1362,11 +1382,8 @@ use_mod_oper_data: - goto use_mod_oper_data; - } - --cleanup_unlock: -- /* EXT READ UNLOCK */ -- sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__); -- - cleanup: -+ free(oper_push_dup); - lyd_free_siblings(mod_data); - return err_info; - } --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch b/patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch deleted file mode 100644 index da68f425..00000000 --- a/patches/sysrepo/3.3.10/0013-shm-sub-UPDATE-use-operation-ID-instead-of-request-I.patch +++ /dev/null @@ -1,1501 +0,0 @@ -From c1e13d121ee8f865f36c22a7765aff56b1229c5a Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Tue, 14 Jan 2025 13:35:28 +0100 -Subject: [PATCH 13/22] shm sub UPDATE use operation ID instead of request ID -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -... which can be used to connect various callback -calls that are part of a single operation. - -Signed-off-by: Mattias Walström ---- - src/common.c | 8 +- - src/common.h | 7 +- - src/modinfo.c | 27 +++--- - src/modinfo.h | 1 + - src/shm_main.c | 1 + - src/shm_sub.c | 202 ++++++++++++++++++++++------------------- - src/shm_sub.h | 27 +++--- - src/shm_types.h | 4 +- - src/sysrepo.c | 72 +++++++-------- - src/sysrepo_types.h | 17 ++-- - tests/test_oper_pull.c | 3 +- - 11 files changed, 202 insertions(+), 167 deletions(-) - -diff --git a/src/common.c b/src/common.c -index 72f3fc93..af607557 100644 ---- a/src/common.c -+++ b/src/common.c -@@ -2890,7 +2890,7 @@ sr_conn_ext_data_update(sr_conn_ctx_t *conn) - struct lyd_node *yl_data = NULL, *new_ext_data = NULL; - - /* init mod info for cleanup */ -- SR_MODINFO_INIT(mi, conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); -+ SR_MODINFO_INIT(mi, conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); - - /* manually get ietf-yang-schema-mount operational data but avoid recursive call of this function */ - ly_mod = ly_ctx_get_module_implemented(conn->ly_ctx, "ietf-yang-schema-mount"); -@@ -5609,6 +5609,7 @@ sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_ - { - sr_error_info_t *err_info = NULL; - struct timespec notif_ts_mono, notif_ts_real; -+ uint32_t operation_id; - - /* NOTIF SUB READ LOCK */ - if ((err_info = sr_rwlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, -@@ -5620,8 +5621,11 @@ sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_ - sr_timeouttime_get(¬if_ts_mono, 0); - sr_realtime_get(¬if_ts_real); - -+ /* generate a new operation ID */ -+ operation_id = ATOMIC_INC_RELAXED(SR_CONN_MAIN_SHM(conn)->new_operation_id); -+ - /* send the notification (non-validated, must be valid) */ -- err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, 0, 0); -+ err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, operation_id, 0, 0); - - /* NOTIF SUB READ UNLOCK */ - sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, __func__); -diff --git a/src/common.h b/src/common.h -index 9fc675d4..f6bc9128 100644 ---- a/src/common.h -+++ b/src/common.h -@@ -189,7 +189,12 @@ extern const sr_module_ds_t sr_module_ds_disabled_run; - #define SR_SHM_INITIALIZER {.fd = -1, .size = 0, .addr = NULL} - - /** initializer of mod_info structure */ --#define SR_MODINFO_INIT(mi, c, d, d2) memset(&(mi), 0, sizeof (mi)); (mi).ds = (d); (mi).ds2 = (d2); (mi).conn = (c) -+#define SR_MODINFO_INIT(mi, c, d, d2, op_id) \ -+ memset(&(mi), 0, sizeof (mi)); \ -+ (mi).ds = (d); \ -+ (mi).ds2 = (d2); \ -+ (mi).conn = (c); \ -+ (mi).operation_id = (op_id) ? (op_id) : ATOMIC_INC_RELAXED(SR_CONN_MAIN_SHM(c)->new_operation_id) - - /** - * @brief Internal information about a module to be installed. -diff --git a/src/modinfo.c b/src/modinfo.c -index f969f363..d002bbdb 100644 ---- a/src/modinfo.c -+++ b/src/modinfo.c -@@ -1053,6 +1053,7 @@ cleanup: - * @param[in] req_xpath_count Count of @p request_xpaths. - * @param[in] orig_name Event originator name. - * @param[in] orig_data Event originator data. -+ * @param[in] operation_id Operation ID. - * @param[in] shm_subs Subscription array. - * @param[in] idx1 Index of the subscription array from where to read subscriptions with the same XPath. - * @param[in] parent Data parent required for the subscription, NULL if top-level. -@@ -1063,9 +1064,9 @@ cleanup: - */ - static sr_error_info_t * - sr_xpath_oper_data_get(struct sr_mod_info_mod_s *mod, const char *xpath, const char **request_xpaths, -- uint32_t req_xpath_count, const char *orig_name, const void *orig_data, sr_mod_oper_get_sub_t *shm_subs, -- uint32_t idx1, const struct lyd_node *parent, uint32_t timeout_ms, sr_conn_ctx_t *conn, -- struct lyd_node **oper_data) -+ uint32_t req_xpath_count, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ sr_mod_oper_get_sub_t *shm_subs, uint32_t idx1, const struct lyd_node *parent, uint32_t timeout_ms, -+ sr_conn_ctx_t *conn, struct lyd_node **oper_data) - { - sr_error_info_t *err_info = NULL, *cb_err_info = NULL; - struct lyd_node *parent_dup = NULL, *last_parent; -@@ -1108,8 +1109,8 @@ sr_xpath_oper_data_get(struct sr_mod_info_mod_s *mod, const char *xpath, const c - request_xpath = (req_xpath_count == 1) ? request_xpaths[0] : NULL; - - /* get data from client */ -- if ((err_info = sr_shmsub_oper_get_notify(mod, xpath, request_xpath, parent_dup, orig_name, orig_data, shm_subs, -- idx1, timeout_ms, conn, oper_data, &cb_err_info))) { -+ if ((err_info = sr_shmsub_oper_get_notify(mod, xpath, request_xpath, parent_dup, orig_name, orig_data, operation_id, -+ shm_subs, idx1, timeout_ms, conn, oper_data, &cb_err_info))) { - sr_errinfo_merge(&err_info, cb_err_info); - goto cleanup; - } -@@ -1431,6 +1432,7 @@ cleanup: - * @param[in] oper_mode Current lock mode of @p mod for ::SR_DS_OPERATIONAL. - * @param[in] orig_name Event originator name. - * @param[in] orig_data Event originator data. -+ * @param[in] operation_id Operation ID. - * @param[in] conn Connection to use. - * @param[in] timeout_ms Operational callback timeout in milliseconds. - * @param[in] get_oper_opts Get oper data options. -@@ -1438,8 +1440,9 @@ cleanup: - * @return err_info, NULL on success. - */ - static sr_error_info_t * --sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, const void *orig_data, sr_conn_ctx_t *conn, -- uint32_t timeout_ms, sr_get_oper_flag_t get_oper_opts, struct lyd_node **data) -+sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, const void *orig_data, -+ uint32_t operation_id, sr_conn_ctx_t *conn, uint32_t timeout_ms, sr_get_oper_flag_t get_oper_opts, -+ struct lyd_node **data) - { - sr_error_info_t *err_info = NULL; - sr_mod_oper_get_sub_t *shm_subs; -@@ -1554,7 +1557,7 @@ sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name, - for (j = 0; j < set->count; ++j) { - /* get oper data from the client */ - if ((err_info = sr_xpath_oper_data_get(mod, sub_xpath, request_xpaths, req_xpath_count, orig_name, -- orig_data, shm_subs, i, set->dnodes[j], timeout_ms, conn, &oper_data))) { -+ orig_data, operation_id, shm_subs, i, set->dnodes[j], timeout_ms, conn, &oper_data))) { - goto cleanup_opergetsub_ext_unlock; - } - -@@ -1574,7 +1577,7 @@ next_iter: - } else { - /* top-level data */ - if ((err_info = sr_xpath_oper_data_get(mod, sub_xpath, request_xpaths, req_xpath_count, orig_name, -- orig_data, shm_subs, i, NULL, timeout_ms, conn, &oper_data))) { -+ orig_data, operation_id, shm_subs, i, NULL, timeout_ms, conn, &oper_data))) { - goto cleanup_opergetsub_ext_unlock; - } - -@@ -2774,8 +2777,8 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m - } - - /* append any operational data provided by clients */ -- if ((err_info = sr_module_oper_data_update(mod, orig_name, orig_data, conn, timeout_ms, get_oper_opts, -- &mod_info->data))) { -+ if ((err_info = sr_module_oper_data_update(mod, orig_name, orig_data, mod_info->operation_id, conn, timeout_ms, -+ get_oper_opts, &mod_info->data))) { - return err_info; - } - } -@@ -3811,7 +3814,7 @@ sr_modinfo_generate_config_change_notif(struct sr_mod_info_s *mod_info, sr_sessi - - /* send the notification (non-validated, if everything works correctly it must be valid) */ - err_info = sr_shmsub_notif_notify(mod_info->conn, notif, notif_ts_mono, notif_ts_real, session->orig_name, -- session->orig_data, 0, 0); -+ session->orig_data, mod_info->operation_id, 0, 0); - - /* NOTIF SUB READ UNLOCK */ - sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, mod_info->conn->cid, __func__); -diff --git a/src/modinfo.h b/src/modinfo.h -index 1b82300e..b8108f8e 100644 ---- a/src/modinfo.h -+++ b/src/modinfo.h -@@ -50,6 +50,7 @@ struct sr_mod_info_s { - struct lyd_node *data; /**< Data tree. */ - int data_cached; /**< Whether the data are actually cached. */ - sr_conn_ctx_t *conn; /**< Associated connection. */ -+ uint32_t operation_id; /**< ID of the current operation for all the callbacks. */ - - struct sr_mod_info_mod_s { - sr_mod_t *shm_mod; /**< Module SHM structure. */ -diff --git a/src/shm_main.c b/src/shm_main.c -index 4b273b49..fdcb567b 100644 ---- a/src/shm_main.c -+++ b/src/shm_main.c -@@ -509,6 +509,7 @@ sr_shmmain_open(sr_shm_t *shm, int *created) - ATOMIC_STORE_RELAXED(main_shm->new_sr_sid, 1); - ATOMIC_STORE_RELAXED(main_shm->new_sub_id, 1); - ATOMIC_STORE_RELAXED(main_shm->new_evpipe_num, 1); -+ ATOMIC_STORE_RELAXED(main_shm->new_operation_id, 1); - strncpy(main_shm->repo_path, sr_get_repo_path(), sizeof main_shm->repo_path - 1); - - /* remove leftover event pipes */ -diff --git a/src/shm_sub.c b/src/shm_sub.c -index babeaa0c..fd6f105b 100644 ---- a/src/shm_sub.c -+++ b/src/shm_sub.c -@@ -4,8 +4,8 @@ - * @brief subscription SHM routines - * - * @copyright -- * Copyright (c) 2018 - 2023 Deutsche Telekom AG. -- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. -+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG. -+ * Copyright (c) 2018 - 2025 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. -@@ -451,6 +451,7 @@ event_handled: - * ::SR_SUB_EV_ERROR - an answer is expected and SHM will be further accessed so do not clear any events. - * @param[in] clear_ev_on_err Whether to clear the current event if error/timeout occurs or leave it be. - * @param[in] cid Connection ID. -+ * @param[in] operation_id Operation ID. - * @param[in] shm_data_sub Opened sub data SHM. - * @param[in] timeout_abs Absolute timeout for the event to be handled. - * @param[out] lock_lost Set if the WRITE lock was released, possible only if err_info is returned. -@@ -459,8 +460,8 @@ event_handled: - */ - static sr_error_info_t * - _sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t event, uint32_t request_id, sr_sub_event_t expected_ev, -- int clear_ev_on_err, sr_cid_t cid, sr_shm_t *shm_data_sub, struct timespec *timeout_abs, int *lock_lost, -- sr_error_info_t **cb_err_info) -+ int clear_ev_on_err, sr_cid_t cid, uint32_t operation_id, sr_shm_t *shm_data_sub, struct timespec *timeout_abs, -+ int *lock_lost, sr_error_info_t **cb_err_info) - { - sr_error_info_t *err_info = NULL; - sr_error_t err_code; -@@ -509,14 +510,14 @@ _sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t event, uint32_t - if (!(err_info = sr_sub_rwlock(&sub_shm->lock, &timeout_abs2, SR_LOCK_WRITE, cid, __func__, NULL, NULL, 1))) { - /* event timeout */ - sr_errinfo_new(cb_err_info, SR_ERR_TIME_OUT, "EV ORIGIN: SHM event \"%s\" ID %" PRIu32 " processing timed out.", -- sr_ev2str(event), request_id); -+ sr_ev2str(event), operation_id); - write_lock = 1; - } - } else { - /* other error - not ETIMEDOUT or shm event has changed incorrectly */ - if (event != last_event) { - SR_LOG_WRN("EV ORIGIN: SHM event \"%s\" ID %" PRIu32 " changed to \"%s\" unexpectedly", -- sr_ev2str(event), request_id, sr_ev2str(last_event)); -+ sr_ev2str(event), operation_id, sr_ev2str(last_event)); - - } - SR_ERRINFO_COND(&err_info, __func__, ret); -@@ -650,6 +651,7 @@ event_handled: - * ::SR_SUB_EV_ERROR - an answer is expected and SHM will be further accessed so do not clear any events. - * @param[in] clear_ev_on_err Whether to clear the current event if error/timeout occurs or leave it be. - * @param[in] cid Connection ID. -+ * @param[in] operation_id Operation ID. - * @param[in] shm_data_sub Opened sub data SHM. - * @param[in] timeout_ms Timeout in milliseconds. - * @param[out] lock_lost Set if the WRITE lock was released, possible only if err_info is returned. -@@ -658,7 +660,7 @@ event_handled: - */ - static sr_error_info_t * - sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t expected_ev, int clear_ev_on_err, sr_cid_t cid, -- sr_shm_t *shm_data_sub, uint32_t timeout_ms, int *lock_lost, sr_error_info_t **cb_err_info) -+ uint32_t operation_id, sr_shm_t *shm_data_sub, uint32_t timeout_ms, int *lock_lost, sr_error_info_t **cb_err_info) - { - sr_sub_event_t event; - uint32_t request_id; -@@ -671,8 +673,8 @@ sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t expected_ev, int - /* compute the timeout */ - sr_timeouttime_get(&timeout_abs, timeout_ms); - -- return _sr_shmsub_notify_wait_wr(sub_shm, event, request_id, expected_ev, clear_ev_on_err, cid, shm_data_sub, -- &timeout_abs, lock_lost, cb_err_info); -+ return _sr_shmsub_notify_wait_wr(sub_shm, event, request_id, expected_ev, clear_ev_on_err, cid, operation_id, -+ shm_data_sub, &timeout_abs, lock_lost, cb_err_info); - } - - /** -@@ -693,12 +695,13 @@ sr_shmsub_notify_wait_wr(sr_sub_shm_t *sub_shm, sr_sub_event_t expected_ev, int - * ::SR_SUB_EV_ERROR - an answer is expected and SHM will be further accessed so do not clear any events. - * @param[in] clear_ev_on_err Whether to clear the current event if error/timeout occurs or leave it be. - * @param[in] cid Connection ID. -+ * @param[in] operation_id Operation ID. - * @param[in] timeout_ms Timeout in milliseconds. - * @return err_info, NULL on success. - */ - static sr_error_info_t * - sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_t notify_size, uint32_t notify_count, -- sr_sub_event_t expected_ev, int clear_ev_on_err, sr_cid_t cid, uint32_t timeout_ms) -+ sr_sub_event_t expected_ev, int clear_ev_on_err, sr_cid_t cid, uint32_t operation_id, uint32_t timeout_ms) - { - sr_error_info_t *err_info = NULL, *tmp_err; - struct sr_shmsub_many_info_s *nsub; -@@ -756,7 +759,7 @@ sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_ - - /* wait for an event change */ - tmp_err = _sr_shmsub_notify_wait_wr(nsub->sub_shm, nsub->event, nsub->request_id, expected_ev, clear_ev_on_err, -- cid, &nsub->shm_data_sub, &timeout_abs, &lock_lost, &nsub->cb_err_info); -+ cid, operation_id, &nsub->shm_data_sub, &timeout_abs, &lock_lost, &nsub->cb_err_info); - if (tmp_err) { - if (lock_lost) { - /* WRITE lock lost */ -@@ -785,6 +788,7 @@ sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_ - * @param[in] orig_name Originator name. - * @param[in] orig_data Originator data. - * @param[in] subscriber_count Subscriber count. -+ * @param[in] operation_id Operation ID. - * @param[in] shm_data_sub Opened sub data SHM. - * @param[in] xpath Optional XPath written into sub data SHM. - * @param[in] data Optional data written into sub data SHM. -@@ -795,7 +799,8 @@ sr_shmsub_notify_many_wait_wr(struct sr_shmsub_many_info_s *notify_subs, uint32_ - static sr_error_info_t * - sr_shmsub_notify_write_event(sr_sub_shm_t *sub_shm, sr_cid_t orig_cid, uint32_t request_id, uint32_t priority, - sr_sub_event_t event, const char *orig_name, const void *orig_data, uint32_t subscriber_count, -- sr_shm_t *shm_data_sub, const char *xpath, const char *data, uint32_t data_len, const char *event_desc) -+ uint32_t operation_id, sr_shm_t *shm_data_sub, const char *xpath, const char *data, uint32_t data_len, -+ const char *event_desc) - { - sr_error_info_t *err_info = NULL; - char *shm_data_ptr = NULL; -@@ -817,6 +822,7 @@ sr_shmsub_notify_write_event(sr_sub_shm_t *sub_shm, sr_cid_t orig_cid, uint32_t - ATOMIC_STORE_RELAXED(sub_shm->event, event); - ATOMIC_STORE_RELAXED(sub_shm->priority, priority); - sub_shm->subscriber_count = subscriber_count; -+ sub_shm->operation_id = operation_id; - - /* remap if needed */ - if (xpath || data_len) { -@@ -848,7 +854,7 @@ sr_shmsub_notify_write_event(sr_sub_shm_t *sub_shm, sr_cid_t orig_cid, uint32_t - - if (event && event_desc) { - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " for %" PRIu32 " subscribers published.", -- event_desc, sr_ev2str(event), request_id, priority, subscriber_count); -+ event_desc, sr_ev2str(event), operation_id, priority, subscriber_count); - } - return NULL; - } -@@ -1354,8 +1360,8 @@ sr_shmsub_change_notify_update(struct sr_mod_info_s *mod_info, const char *orig_ - mod->request_id = ++sub_shm->request_id; - } - if ((err_info = sr_shmsub_notify_write_event(sub_shm, cid, mod->request_id, cur_priority, -- SR_SUB_EV_UPDATE, orig_name, orig_data, subscriber_count, &shm_data_sub, NULL, diff_lyb, -- diff_lyb_len, mod->ly_mod->name))) { -+ SR_SUB_EV_UPDATE, orig_name, orig_data, subscriber_count, mod_info->operation_id, &shm_data_sub, -+ NULL, diff_lyb, diff_lyb_len, mod->ly_mod->name))) { - goto cleanup_wrunlock; - } - -@@ -1370,8 +1376,8 @@ sr_shmsub_change_notify_update(struct sr_mod_info_s *mod_info, const char *orig_ - } - - /* wait until the event is processed */ -- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, cid, &shm_data_sub, timeout_ms, -- &lock_lost, cb_err_info))) { -+ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, cid, mod_info->operation_id, -+ &shm_data_sub, timeout_ms, &lock_lost, cb_err_info))) { - if (lock_lost) { - goto cleanup; - } else { -@@ -1382,11 +1388,12 @@ sr_shmsub_change_notify_update(struct sr_mod_info_s *mod_info, const char *orig_ - if (*cb_err_info) { - /* failed callback or timeout */ - SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " failed (%s).", mod->ly_mod->name, -- sr_ev2str(SR_SUB_EV_UPDATE), mod->request_id, cur_priority, sr_strerror((*cb_err_info)->err[0].err_code)); -+ sr_ev2str(SR_SUB_EV_UPDATE), mod_info->operation_id, cur_priority, -+ sr_strerror((*cb_err_info)->err[0].err_code)); - goto cleanup_wrunlock; - } else { - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", mod->ly_mod->name, -- sr_ev2str(SR_SUB_EV_UPDATE), mod->request_id, cur_priority); -+ sr_ev2str(SR_SUB_EV_UPDATE), mod_info->operation_id, cur_priority); - } - - assert(sub_shm->event == SR_SUB_EV_SUCCESS); -@@ -1482,7 +1489,7 @@ sr_shmsub_change_notify_clear(struct sr_mod_info_s *mod_info) - - /* clear it */ - if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, mod->request_id, sub_shm->priority, 0, NULL, NULL, -- 0, NULL, NULL, NULL, 0, NULL))) { -+ 0, mod_info->operation_id, NULL, NULL, NULL, 0, NULL))) { - goto cleanup_wrunlock; - } - -@@ -1659,8 +1666,8 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ - nsub->mod->request_id = ++nsub->sub_shm->request_id; - } - if ((err_info = sr_shmsub_notify_write_event((sr_sub_shm_t *)nsub->shm_sub.addr, cid, nsub->mod->request_id, -- nsub->cur_priority, SR_SUB_EV_CHANGE, orig_name, orig_data, subscriber_count, &nsub->shm_data_sub, -- NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { -+ nsub->cur_priority, SR_SUB_EV_CHANGE, orig_name, orig_data, subscriber_count, mod_info->operation_id, -+ &nsub->shm_data_sub, NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { - goto cleanup; - } - -@@ -1696,7 +1703,7 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ - - /* wait until the events are processed */ - if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, -- notify_count, SR_SUB_EV_SUCCESS, 0, cid, timeout_ms))) { -+ notify_count, SR_SUB_EV_SUCCESS, 0, cid, mod_info->operation_id, timeout_ms))) { - goto cleanup; - } - -@@ -1715,7 +1722,7 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ - if (nsub->cb_err_info) { - /* failed callback or timeout */ - SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " failed (%s).", -- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), nsub->mod->request_id, nsub->cur_priority, -+ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), mod_info->operation_id, nsub->cur_priority, - sr_strerror(nsub->cb_err_info->err[0].err_code)); - - /* merge the error */ -@@ -1723,7 +1730,7 @@ sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, const char *orig_ - nsub->cb_err_info = NULL; - } else { - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", -- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), nsub->mod->request_id, nsub->cur_priority); -+ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_CHANGE), mod_info->operation_id, nsub->cur_priority); - } - nsub->pending_event = 0; - } -@@ -1852,8 +1859,8 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * - nsub->mod->request_id = ++nsub->sub_shm->request_id; - } - if ((err_info = sr_shmsub_notify_write_event((sr_sub_shm_t *)nsub->shm_sub.addr, cid, nsub->mod->request_id, -- nsub->cur_priority, SR_SUB_EV_DONE, orig_name, orig_data, subscriber_count, &nsub->shm_data_sub, -- NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { -+ nsub->cur_priority, SR_SUB_EV_DONE, orig_name, orig_data, subscriber_count, mod_info->operation_id, -+ &nsub->shm_data_sub, NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { - goto cleanup; - } - -@@ -1888,7 +1895,7 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * - - /* wait until the events are processed */ - if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, -- notify_count, SR_SUB_EV_FINISHED, 1, cid, timeout_ms))) { -+ notify_count, SR_SUB_EV_FINISHED, 1, cid, mod_info->operation_id, timeout_ms))) { - goto cleanup; - } - -@@ -1908,7 +1915,7 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * - sr_errinfo_free(&nsub->cb_err_info); - - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", -- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_DONE), nsub->mod->request_id, nsub->cur_priority); -+ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_DONE), mod_info->operation_id, nsub->cur_priority); - - nsub->pending_event = 0; - } -@@ -2017,8 +2024,8 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char - - /* clear the error */ - assert(nsub->sub_shm->request_id == nsub->mod->request_id); -- if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, nsub->mod->request_id, -- nsub->cur_priority, 0, NULL, NULL, 0, &nsub->shm_data_sub, NULL, NULL, 0, NULL))) { -+ if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, nsub->mod->request_id, nsub->cur_priority, 0, NULL, -+ NULL, 0, mod_info->operation_id, &nsub->shm_data_sub, NULL, NULL, 0, NULL))) { - goto cleanup; - } - } -@@ -2094,8 +2101,8 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char - - /* write the event */ - if ((err_info = sr_shmsub_notify_write_event(sub_shm, cid, nsub->mod->request_id, nsub->cur_priority, -- SR_SUB_EV_ABORT, orig_name, orig_data, subscriber_count, &nsub->shm_data_sub, NULL, diff_lyb, -- diff_lyb_len, nsub->mod->ly_mod->name))) { -+ SR_SUB_EV_ABORT, orig_name, orig_data, subscriber_count, mod_info->operation_id, &nsub->shm_data_sub, -+ NULL, diff_lyb, diff_lyb_len, nsub->mod->ly_mod->name))) { - goto cleanup; - } - -@@ -2130,7 +2137,7 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char - - /* wait until the events are processed */ - if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, -- notify_count, SR_SUB_EV_FINISHED, 1, cid, timeout_ms))) { -+ notify_count, SR_SUB_EV_FINISHED, 1, cid, mod_info->operation_id, timeout_ms))) { - goto cleanup; - } - -@@ -2150,7 +2157,7 @@ sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_info, const char - sr_errinfo_free(&nsub->cb_err_info); - - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", -- nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_ABORT), nsub->mod->request_id, nsub->cur_priority); -+ nsub->mod->ly_mod->name, sr_ev2str(SR_SUB_EV_ABORT), mod_info->operation_id, nsub->cur_priority); - - nsub->pending_event = 0; - } -@@ -2183,8 +2190,9 @@ cleanup: - - sr_error_info_t * - sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, const char *request_xpath, -- const struct lyd_node *parent, const char *orig_name, const void *orig_data, sr_mod_oper_get_sub_t *oper_get_subs, -- uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, struct lyd_node **data, sr_error_info_t **cb_err_info) -+ const struct lyd_node *parent, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ sr_mod_oper_get_sub_t *oper_get_subs, uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, -+ struct lyd_node **data, sr_error_info_t **cb_err_info) - { - sr_error_info_t *err_info = NULL; - uint32_t i, notify_count = 0, parent_lyb_len, request_id; -@@ -2257,11 +2265,11 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons - /* write the request for state data */ - request_id = ATOMIC_LOAD_RELAXED(nsub->sub_shm->request_id) + 1; - if ((err_info = sr_shmsub_notify_write_event(nsub->sub_shm, cid, request_id, 0, SR_SUB_EV_OPER, orig_name, -- orig_data, 1, &nsub->shm_data_sub, request_xpath, parent_lyb, parent_lyb_len, NULL))) { -+ orig_data, 1, operation_id, &nsub->shm_data_sub, request_xpath, parent_lyb, parent_lyb_len, NULL))) { - goto cleanup; - } - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" index %" PRIu32 " ID %" PRIu32 " published.", xpath, -- sr_ev2str(SR_SUB_EV_OPER), i, request_id); -+ sr_ev2str(SR_SUB_EV_OPER), i, operation_id); - - /* notify using event pipe */ - if ((err_info = sr_shmsub_notify_evpipe(nsub->xpath_sub->evpipe_num))) { -@@ -2273,7 +2281,7 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons - - /* wait until the events are processed */ - if ((err_info = sr_shmsub_notify_many_wait_wr((struct sr_shmsub_many_info_s *)notify_subs, sizeof *notify_subs, -- notify_count, SR_SUB_EV_ERROR, 1, cid, timeout_ms))) { -+ notify_count, SR_SUB_EV_ERROR, 1, cid, operation_id, timeout_ms))) { - goto cleanup; - } - -@@ -2286,7 +2294,7 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons - if (nsub->cb_err_info) { - /* failed callback */ - SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" index %" PRIu32 " ID %" PRIu32 " failed (%s).", xpath, -- sr_ev2str(SR_SUB_EV_OPER), i, nsub->request_id, sr_strerror(nsub->cb_err_info->err[0].err_code)); -+ sr_ev2str(SR_SUB_EV_OPER), i, operation_id, sr_strerror(nsub->cb_err_info->err[0].err_code)); - - /* merge the error and continue */ - sr_errinfo_merge(cb_err_info, nsub->cb_err_info); -@@ -2295,7 +2303,7 @@ sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, cons - continue; - } else { - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" index %" PRIu32 " ID %" PRIu32 " succeeded.", xpath, -- sr_ev2str(SR_SUB_EV_OPER), i, nsub->request_id); -+ sr_ev2str(SR_SUB_EV_OPER), i, operation_id); - } - - assert(ATOMIC_LOAD_RELAXED(nsub->sub_shm->event) == SR_SUB_EV_SUCCESS); -@@ -2348,11 +2356,13 @@ cleanup: - * - * @param[in] conn Connection to use. - * @param[in] input Input tree pointing to the operation node. -+ * @param[in] operation_id Operation ID. - * @param[out] output Output tree pointing to the operation node. - * @return err_info, NULL on success. - */ - static sr_error_info_t * --sr_shmsub_rpc_internal_call_callback(sr_conn_ctx_t *conn, const struct lyd_node *input, struct lyd_node **output) -+sr_shmsub_rpc_internal_call_callback(sr_conn_ctx_t *conn, const struct lyd_node *input, uint32_t operation_id, -+ struct lyd_node **output) - { - sr_error_info_t *err_info = NULL, *cb_err_info = NULL; - struct sr_mod_info_s mod_info; -@@ -2365,7 +2375,7 @@ sr_shmsub_rpc_internal_call_callback(sr_conn_ctx_t *conn, const struct lyd_node - - assert(input->schema->nodetype & (LYS_RPC | LYS_ACTION)); - -- SR_MODINFO_INIT(mod_info, conn, SR_DS_FACTORY_DEFAULT, SR_DS_FACTORY_DEFAULT); -+ SR_MODINFO_INIT(mod_info, conn, SR_DS_FACTORY_DEFAULT, SR_DS_FACTORY_DEFAULT, operation_id); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(conn, SR_LOCK_READ, 0, __func__))) { -@@ -2689,8 +2699,8 @@ cleanup: - - sr_error_info_t * - sr_shmsub_rpc_notify(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, -- const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t timeout_ms, -- uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info) -+ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ uint32_t timeout_ms, uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info) - { - sr_error_info_t *err_info = NULL; - char *input_lyb = NULL; -@@ -2725,7 +2735,7 @@ first_sub: - assert(subscriber_count == 1); - - /* internal RPC subscription */ -- if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, output))) { -+ if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, operation_id, output))) { - goto cleanup; - } - free(evpipes); -@@ -2763,7 +2773,7 @@ first_sub: - assert(subscriber_count == 1); - - /* internal RPC subscription */ -- if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, output))) { -+ if ((err_info = sr_shmsub_rpc_internal_call_callback(conn, input, operation_id, output))) { - goto cleanup; - } - goto next_sub; -@@ -2774,7 +2784,7 @@ first_sub: - *request_id = ++sub_shm->request_id; - } - if ((err_info = sr_shmsub_notify_write_event(sub_shm, conn->cid, *request_id, cur_priority, SR_SUB_EV_RPC, -- orig_name, orig_data, subscriber_count, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { -+ orig_name, orig_data, subscriber_count, operation_id, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { - goto cleanup_wrunlock; - } - -@@ -2786,8 +2796,8 @@ first_sub: - } - - /* wait until the event is processed */ -- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, conn->cid, &shm_data_sub, timeout_ms, -- &lock_lost, cb_err_info))) { -+ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_ERROR, 0, conn->cid, operation_id, &shm_data_sub, -+ timeout_ms, &lock_lost, cb_err_info))) { - if (lock_lost) { - goto cleanup; - } else { -@@ -2798,11 +2808,11 @@ first_sub: - if (*cb_err_info) { - /* failed callback or timeout */ - SR_LOG_WRN("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " failed (%s).", path, -- sr_ev2str(SR_SUB_EV_RPC), *request_id, cur_priority, sr_strerror((*cb_err_info)->err[0].err_code)); -+ sr_ev2str(SR_SUB_EV_RPC), operation_id, cur_priority, sr_strerror((*cb_err_info)->err[0].err_code)); - goto cleanup_wrunlock; - } else { - SR_LOG_DBG("EV ORIGIN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " succeeded.", path, -- sr_ev2str(SR_SUB_EV_RPC), *request_id, cur_priority); -+ sr_ev2str(SR_SUB_EV_RPC), operation_id, cur_priority); - } - - assert(sub_shm->event == SR_SUB_EV_SUCCESS); -@@ -2850,7 +2860,7 @@ cleanup: - - sr_error_info_t * - sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, -- const struct lyd_node *input, const char *orig_name, const void *orig_data, -+ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, - uint32_t timeout_ms, uint32_t request_id) - { - sr_error_info_t *err_info = NULL, *cb_err_info = NULL; -@@ -2884,7 +2894,7 @@ clear_shm: - /* clear the SHM */ - assert(sub_shm->event == SR_SUB_EV_ERROR); - if ((err_info = sr_shmsub_notify_write_event(sub_shm, 0, request_id, cur_priority, 0, NULL, NULL, 0, -- &shm_data_sub, NULL, NULL, 0, NULL))) { -+ operation_id, &shm_data_sub, NULL, NULL, 0, NULL))) { - goto cleanup_wrunlock; - } - -@@ -2928,7 +2938,7 @@ clear_shm: - - /* write "abort" event with the same input */ - if ((err_info = sr_shmsub_notify_write_event(sub_shm, conn->cid, request_id, cur_priority, SR_SUB_EV_ABORT, -- orig_name, orig_data, subscriber_count, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { -+ orig_name, orig_data, operation_id, subscriber_count, &shm_data_sub, NULL, input_lyb, input_lyb_len, path))) { - goto cleanup_wrunlock; - } - -@@ -2940,8 +2950,8 @@ clear_shm: - } - - /* wait until the event is processed */ -- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_FINISHED, 1, conn->cid, &shm_data_sub, timeout_ms, -- &lock_lost, &cb_err_info))) { -+ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_FINISHED, 1, conn->cid, operation_id, &shm_data_sub, -+ timeout_ms, &lock_lost, &cb_err_info))) { - if (lock_lost) { - goto cleanup; - } else { -@@ -2980,7 +2990,8 @@ cleanup: - - sr_error_info_t * - sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct timespec notif_ts_mono, -- struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t timeout_ms, int wait) -+ struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ uint32_t timeout_ms, int wait) - { - sr_error_info_t *err_info = NULL, *cb_err_info = NULL; - const struct lys_module *ly_mod; -@@ -3062,7 +3073,7 @@ sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct - /* write the notification, use first subscriber CID if not waiting - depends on the subscriber, not originator */ - request_id = sub_shm->request_id + 1; - if ((err_info = sr_shmsub_notify_write_event(sub_shm, wait ? conn->cid : sub_cid, request_id, 0, SR_SUB_EV_NOTIF, -- orig_name, orig_data, notif_sub_count, &shm_data_sub, NULL, data, data_len, ly_mod->name))) { -+ orig_name, orig_data, notif_sub_count, operation_id, &shm_data_sub, NULL, data, data_len, ly_mod->name))) { - goto cleanup_ext_sub_unlock; - } - -@@ -3088,8 +3099,8 @@ sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct - - if (wait) { - /* wait until the event is processed */ -- if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_NONE, 1, conn->cid, &shm_data_sub, timeout_ms, -- &lock_lost, &cb_err_info))) { -+ if ((err_info = sr_shmsub_notify_wait_wr(sub_shm, SR_SUB_EV_NONE, 1, conn->cid, operation_id, &shm_data_sub, -+ timeout_ms, &lock_lost, &cb_err_info))) { - if (lock_lost) { - goto cleanup; - } else { -@@ -3236,8 +3247,8 @@ sr_shmsub_listen_write_event(sr_sub_shm_t *sub_shm, uint32_t valid_subscr_count, - } - - SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " %s (remaining %" PRIu32 " subscribers).", -- event_desc, sr_ev2str(event), (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->request_id), -- (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->priority), result_str, sub_shm->subscriber_count); -+ event_desc, sr_ev2str(event), sub_shm->operation_id, (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->priority), -+ result_str, sub_shm->subscriber_count); - return NULL; - } - -@@ -3337,6 +3348,7 @@ struct info_sub_s { - sr_sub_event_t event; - uint32_t request_id; - uint32_t priority; -+ uint32_t operation_id; - }; - - /** -@@ -3379,7 +3391,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc - sr_rwunlock(&sub_shm->lock, SR_SUBSHM_LOCK_TIMEOUT, mode, ev_sess->conn->cid, __func__); - - SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing %s (after timeout or earlier error).", -- module_name, sr_ev2str(sub_info->event), sub_info->request_id, sub_info->priority, err_code ? "fail" : "success"); -+ module_name, sr_ev2str(sub_info->event), sub_info->operation_id, sub_info->priority, err_code ? "fail" : "success"); - - /* self-generate abort event in case the change was applied successfully */ - if ((sub_info->event == SR_SUB_EV_CHANGE) && (err_code == SR_ERR_OK) && filter_valid && -@@ -3394,7 +3406,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc - ev_sess->dt[ev_sess->ds].diff = abort_diff; - - SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (self-generated).", -- module_name, sr_ev2str(SR_SUB_EV_ABORT), sub_info->request_id, sub_info->priority); -+ module_name, sr_ev2str(SR_SUB_EV_ABORT), sub_info->operation_id, sub_info->priority); - - /* call callback */ - sub->cb(ev_sess, sub->sub_id, module_name, sub->xpath, sr_ev2api(SR_SUB_EV_ABORT), sub_info->request_id, -@@ -3465,6 +3477,7 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub - sub_info.event = ATOMIC_LOAD_RELAXED(sub_shm->event); - sub_info.request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); - sub_info.priority = ATOMIC_LOAD_RELAXED(sub_shm->priority); -+ sub_info.operation_id = sub_shm->operation_id; - - /* parse originator name and data (while creating the event session) */ - if ((err_info = _sr_session_start(conn, change_subs->ds, sub_info.event, &shm_data_ptr, &ev_sess))) { -@@ -3483,7 +3496,7 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub - - /* process event */ - SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (remaining %" PRIu32 " subscribers).", -- change_subs->module_name, sr_ev2str(sub_info.event), sub_info.request_id, sub_info.priority, -+ change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, - sub_shm->subscriber_count); - - /* process individual subscriptions (starting at the last found subscription, it was valid) */ -@@ -3506,7 +3519,7 @@ process_event: - filter_valid = sr_shmsub_change_filter_is_valid(change_sub->xpath, diff); - if (filter_valid) { - ret = change_sub->cb(ev_sess, change_sub->sub_id, change_subs->module_name, change_sub->xpath, -- sr_ev2api(sub_info.event), sub_info.request_id, change_sub->private_data); -+ sr_ev2api(sub_info.event), sub_info.operation_id, change_sub->private_data); - } else if (!(change_sub->opts & SR_SUBSCR_FILTER_ORIG)) { - /* filtered out (not by originator) */ - ATOMIC_INC_RELAXED(change_sub->filtered_out); -@@ -3528,7 +3541,7 @@ process_event: - if (ret == SR_ERR_CALLBACK_SHELVE) { - /* this subscription did not process the event yet, skip it */ - SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing shelved.", -- change_subs->module_name, sr_ev2str(sub_info.event), sub_info.request_id, sub_info.priority); -+ change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority); - continue; - } else if (ret) { - /* whole event failed */ -@@ -3622,6 +3635,7 @@ cleanup: - * @param[in] mode SHM lock mode. - * @param[in] cid Connection ID. - * @param[in] exp_req_id Expected event request ID in the SHM. -+ * @param[in] operation_id Operation ID. - * @param[in] err_code Error code of the callback. - * @param[out] err_info Optional error info on error. - * @return 0 if SHM content is as expected. -@@ -3630,7 +3644,7 @@ cleanup: - */ - static int - sr_shmsub_oper_get_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, sr_cid_t cid, uint32_t exp_req_id, -- sr_error_t err_code, sr_error_info_t **err_info) -+ uint32_t operation_id, sr_error_t err_code, sr_error_info_t **err_info) - { - assert(!*err_info); - -@@ -3645,7 +3659,7 @@ sr_shmsub_oper_get_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, sr_ - sr_rwunlock(&sub_shm->lock, SR_SUBSHM_LOCK_TIMEOUT, mode, cid, __func__); - - SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " processing %s (after timeout).", sr_ev2str(SR_SUB_EV_OPER), -- exp_req_id, err_code ? "fail" : "success"); -+ operation_id, err_code ? "fail" : "success"); - - /* we have completely finished processing (with no error) */ - return 1; -@@ -3659,7 +3673,7 @@ sr_error_info_t * - sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_get_subs, sr_conn_ctx_t *conn) - { - sr_error_info_t *err_info = NULL; -- uint32_t i, data_len = 0, request_id; -+ uint32_t i, data_len = 0, request_id, operation_id; - char *data = NULL, *request_xpath = NULL, *shm_data_ptr; - const char *origin; - sr_error_t err_code = SR_ERR_OK; -@@ -3698,6 +3712,7 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge - continue; - } - request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); -+ operation_id = sub_shm->operation_id; - - /* open sub data SHM */ - if ((err_info = sr_shmsub_data_open_remap(oper_get_subs->module_name, "oper", sr_str_hash(oper_get_sub->path, -@@ -3732,12 +3747,12 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge - - /* process event */ - SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " processing.", oper_get_sub->path, sr_ev2str(SR_SUB_EV_OPER), -- request_id); -+ operation_id); - - /* call callback */ - orig_parent = parent; - err_code = oper_get_sub->cb(ev_sess, oper_get_sub->sub_id, oper_get_subs->module_name, oper_get_sub->path, -- request_xpath[0] ? request_xpath : NULL, request_id, &parent, oper_get_sub->private_data); -+ request_xpath[0] ? request_xpath : NULL, operation_id, &parent, oper_get_sub->private_data); - - /* go again to the top-level root for printing */ - if (parent) { -@@ -3758,7 +3773,7 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge - if (err_code == SR_ERR_CALLBACK_SHELVE) { - /* this subscription did not process the event yet, skip it */ - SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " processing shelved.", oper_get_sub->path, -- sr_ev2str(SR_SUB_EV_OPER), request_id); -+ sr_ev2str(SR_SUB_EV_OPER), operation_id); - goto next_iter; - } - -@@ -3779,7 +3794,8 @@ sr_shmsub_oper_get_listen_process_module_events(struct modsub_operget_s *oper_ge - } - - /* SUB WRITE LOCK */ -- if (sr_shmsub_oper_get_listen_relock(sub_shm, SR_LOCK_WRITE, conn->cid, request_id, err_code, &err_info)) { -+ if (sr_shmsub_oper_get_listen_relock(sub_shm, SR_LOCK_WRITE, conn->cid, request_id, operation_id, err_code, -+ &err_info)) { - /* not necessarily an error */ - goto error; - } -@@ -3806,7 +3822,6 @@ next_iter: - sr_shm_clear(&shm_data_sub); - } - -- /* success */ - return NULL; - - error_wrunlock: -@@ -3944,7 +3959,7 @@ sr_shmsub_oper_poll_listen_process_module_events(struct modsub_operpoll_s *oper_ - SR_CHECK_INT_GOTO(!ly_mod, err_info, cleanup); - - /* init mod info */ -- SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); -+ SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL, 0); - if ((err_info = sr_modinfo_add(ly_mod, NULL, 0, 0, &mod_info))) { - goto cleanup; - } -@@ -4174,14 +4189,14 @@ cleanup: - * @param[in] ev_sess Temporary event callback session. - * @param[in] input_op Input tree pointing to the operation node. - * @param[in] event Subscription event. -- * @param[in] request_id Request ID. -+ * @param[in] operation_id Operation ID. - * @param[out] output_op Output tree pointing to the operation node. - * @param[out] err_code Returned error code if the callback failed. - * @return err_info, NULL on success. - */ - static sr_error_info_t * - sr_shmsub_rpc_listen_call_callback(struct opsub_rpcsub_s *rpc_sub, sr_session_ctx_t *ev_sess, const struct lyd_node *input_op, -- sr_sub_event_t event, uint32_t request_id, struct lyd_node **output_op, sr_error_t *err_code) -+ sr_sub_event_t event, uint32_t operation_id, struct lyd_node **output_op, sr_error_t *err_code) - { - sr_error_info_t *err_info = NULL; - const struct lyd_node *elem; -@@ -4203,7 +4218,7 @@ sr_shmsub_rpc_listen_call_callback(struct opsub_rpcsub_s *rpc_sub, sr_session_ct - } - - /* callback */ -- *err_code = rpc_sub->tree_cb(ev_sess, rpc_sub->sub_id, rpc_sub->xpath, input_op, sr_ev2api(event), request_id, -+ *err_code = rpc_sub->tree_cb(ev_sess, rpc_sub->sub_id, rpc_sub->xpath, input_op, sr_ev2api(event), operation_id, - *output_op, rpc_sub->private_data); - if (*err_code) { - goto cleanup; -@@ -4240,7 +4255,7 @@ sr_shmsub_rpc_listen_call_callback(struct opsub_rpcsub_s *rpc_sub, sr_session_ct - output_vals = NULL; - output_val_count = 0; - *err_code = rpc_sub->cb(ev_sess, rpc_sub->sub_id, op_xpath, input_vals, input_val_count, sr_ev2api(event), -- request_id, &output_vals, &output_val_count, rpc_sub->private_data); -+ operation_id, &output_vals, &output_val_count, rpc_sub->private_data); - if (*err_code) { - goto cleanup; - } -@@ -4376,7 +4391,7 @@ sr_shmsub_rpc_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struct i - sr_rwunlock(&sub_shm->lock, SR_SUBSHM_LOCK_TIMEOUT, mode, ev_sess->conn->cid, __func__); - - SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing %s (after timeout or earlier error).", -- sr_ev2str(sub_info->event), sub_info->request_id, sub_info->priority, err_code ? "Failed" : "Successful"); -+ sr_ev2str(sub_info->event), sub_info->operation_id, sub_info->priority, err_code ? "Failed" : "Successful"); - - /* self-generate abort event in case the RPC was applied successfully */ - if (err_code == SR_ERR_OK) { -@@ -4384,11 +4399,11 @@ sr_shmsub_rpc_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struct i - ev_sess->ev = SR_SUB_EV_ABORT; - - SR_LOG_INF("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (self-generated).", -- path, sr_ev2str(SR_SUB_EV_ABORT), sub_info->request_id, sub_info->priority); -+ path, sr_ev2str(SR_SUB_EV_ABORT), sub_info->operation_id, sub_info->priority); - - /* call callback */ - *err_info = sr_shmsub_rpc_listen_call_callback(sub, ev_sess, input_op, SR_SUB_EV_ABORT, -- sub_info->request_id, &output, &err_code); -+ sub_info->operation_id, &output, &err_code); - - /* we do not care about output of error code */ - lyd_free_all(output); -@@ -4477,6 +4492,7 @@ sr_shmsub_rpc_listen_process_rpc_events(struct opsub_rpc_s *rpc_subs, sr_conn_ct - sub_info.event = ATOMIC_LOAD_RELAXED(sub_shm->event); - sub_info.request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); - sub_info.priority = ATOMIC_LOAD_RELAXED(sub_shm->priority); -+ sub_info.operation_id = sub_shm->operation_id; - - /* go to the operation, not the root */ - input_op = input; -@@ -4486,7 +4502,7 @@ sr_shmsub_rpc_listen_process_rpc_events(struct opsub_rpc_s *rpc_subs, sr_conn_ct - - /* process event */ - SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing (remaining %" PRIu32 " subscribers).", -- rpc_subs->path, sr_ev2str(sub_info.event), sub_info.request_id, sub_info.priority, -+ rpc_subs->path, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, - sub_shm->subscriber_count); - - /* process individual subscriptions (starting at the last found subscription, it was valid) */ -@@ -4510,7 +4526,7 @@ process_event: - - /* call callback */ - if ((err_info = sr_shmsub_rpc_listen_call_callback(rpc_sub, ev_sess, input_op, sub_info.event, -- sub_info.request_id, &output, &ret))) { -+ sub_info.operation_id, &output, &ret))) { - goto cleanup; - } - -@@ -4525,8 +4541,7 @@ process_event: - if (ret == SR_ERR_CALLBACK_SHELVE) { - /* processing was shelved, so interupt the whole RPC processing in order to get correct final output */ - SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " priority %" PRIu32 " processing shelved.", -- sr_ev2str(ATOMIC_LOAD_RELAXED(sub_shm->event)), -- (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->request_id), -+ sr_ev2str(ATOMIC_LOAD_RELAXED(sub_shm->event)), sub_shm->operation_id, - (uint32_t)ATOMIC_LOAD_RELAXED(sub_shm->priority)); - goto cleanup; - } else if (ret != SR_ERR_OK) { -@@ -4626,7 +4641,7 @@ sr_error_info_t * - sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, sr_conn_ctx_t *conn) - { - sr_error_info_t *err_info = NULL; -- uint32_t i, request_id, valid_subscr_count; -+ uint32_t i, request_id, operation_id, valid_subscr_count; - struct lyd_node *notif = NULL, *notif_op; - struct sr_denied denied = {0}; - struct timespec notif_ts_mono, notif_ts_real; -@@ -4656,6 +4671,7 @@ sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, - goto cleanup_rdunlock; - } - request_id = ATOMIC_LOAD_RELAXED(sub_shm->request_id); -+ operation_id = sub_shm->operation_id; - - /* open sub data SHM */ - if ((err_info = sr_shmsub_data_open_remap(notif_subs->module_name, "notif", -1, &shm_data_sub, 0))) { -@@ -4695,13 +4711,13 @@ sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, - - if (!valid_subscr_count) { - /* Print a message only the first time we get here */ -- SR_LOG_DBG("EV LISTEN: \"%s\" \"notif\" ID %" PRIu32 " processing.", notif_subs->module_name, request_id); -+ SR_LOG_DBG("EV LISTEN: \"%s\" \"notif\" ID %" PRIu32 " processing.", notif_subs->module_name, operation_id); - } - - if (sr_time_cmp(&sub->listen_since_mono, ¬if_ts_mono) > 0) { - /* generated before this subscription has been made */ - SR_LOG_DBG("EV LISTEN: \"%s\" \"notif\" ID %" PRIu32 " ignored, subscription created after the notification.", -- notif_subs->module_name, request_id); -+ notif_subs->module_name, operation_id); - continue; - } - -@@ -4747,7 +4763,7 @@ sr_shmsub_notif_listen_process_module_events(struct modsub_notif_s *notif_subs, - if ((ATOMIC_LOAD_RELAXED(sub_shm->event) != SR_SUB_EV_NOTIF) || - (ATOMIC_LOAD_RELAXED(sub_shm->request_id) != ATOMIC_LOAD_RELAXED(notif_subs->request_id))) { - SR_LOG_INF("EV LISTEN: \"%s\" ID %" PRIu32 " processing success (after timeout).", sr_ev2str(SR_SUB_EV_NOTIF), -- (uint32_t)ATOMIC_LOAD_RELAXED(notif_subs->request_id)); -+ operation_id); - goto cleanup_wrunlock; - } - -diff --git a/src/shm_sub.h b/src/shm_sub.h -index 62050e98..505dab39 100644 ---- a/src/shm_sub.h -+++ b/src/shm_sub.h -@@ -167,6 +167,7 @@ sr_error_info_t *sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_ - * @param[in] parent Existing parent to append the data to. - * @param[in] orig_name Event originator name. - * @param[in] orig_data Event originator data. -+ * @param[in] operation_id Operation ID. - * @param[in] oper_get_subs An array of operational get subscriptions. - * @param[in] idx1 Index of the array where operational subscriptions with the same XPath are. - * @param[in] timeout_ms Operational callback timeout in milliseconds. -@@ -176,8 +177,9 @@ sr_error_info_t *sr_shmsub_change_notify_change_abort(struct sr_mod_info_s *mod_ - * @return err_info, NULL on success. - */ - sr_error_info_t *sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const char *xpath, const char *request_xpath, -- const struct lyd_node *parent, const char *orig_name, const void *orig_data, sr_mod_oper_get_sub_t *oper_get_subs, -- uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, struct lyd_node **data, sr_error_info_t **cb_err_info); -+ const struct lyd_node *parent, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ sr_mod_oper_get_sub_t *oper_get_subs, uint32_t idx1, uint32_t timeout_ms, sr_conn_ctx_t *conn, -+ struct lyd_node **data, sr_error_info_t **cb_err_info); - - /** - * @brief Notify about (generate) an RPC/action event. -@@ -191,6 +193,7 @@ sr_error_info_t *sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const - * @param[in] input Operation input tree. - * @param[in] orig_name Event originator name. - * @param[in] orig_data Event originator data. -+ * @param[in] operation_id Operation ID. - * @param[in] timeout_ms RPC/action callback timeout in milliseconds. - * @param[in,out] request_id Generated request ID, set to 0 when passing. - * @param[out] output Operation output returned by the last subscriber on success. -@@ -198,8 +201,8 @@ sr_error_info_t *sr_shmsub_oper_get_notify(struct sr_mod_info_mod_s *mod, const - * @return err_info, NULL on success. - */ - sr_error_info_t *sr_shmsub_rpc_notify(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, -- const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t timeout_ms, -- uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info); -+ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ uint32_t timeout_ms, uint32_t *request_id, struct lyd_node **output, sr_error_info_t **cb_err_info); - - /** - * @brief Notify about (generate) an RPC/action abort event. -@@ -211,13 +214,14 @@ sr_error_info_t *sr_shmsub_rpc_notify(sr_conn_ctx_t *conn, off_t *subs, uint32_t - * @param[in] input Operation input tree. - * @param[in] orig_name Event originator name. - * @param[in] orig_data Event originator data. -+ * @param[in] operation_id Operation ID. - * @param[in] timeout_ms RPC/action callback timeout in milliseconds. - * @param[in] request_id Generated request ID from previous event. - * @return err_info, NULL on success. - */ --sr_error_info_t *sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, -- const char *path, const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t timeout_ms, -- uint32_t request_id); -+sr_error_info_t *sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_count, const char *path, -+ const struct lyd_node *input, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ uint32_t timeout_ms, uint32_t request_id); - - /** - * @brief Notify about (generate) a notification event. -@@ -228,12 +232,14 @@ sr_error_info_t *sr_shmsub_rpc_notify_abort(sr_conn_ctx_t *conn, off_t *subs, ui - * @param[in] notif_ts_real Notification realtime timestamp. - * @param[in] orig_name Event originator name. - * @param[in] orig_data Event originator data. -+ * @param[in] operation_id Operation ID. - * @param[in] timeout_ms Notification callback timeout in milliseconds. Used only if @p wait is set. - * @param[in] wait Whether to wait for the callbacks or not. - * @return err_info, NULL on success. - */ - sr_error_info_t *sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_node *notif, struct timespec notif_ts_mono, -- struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t timeout_ms, int wait); -+ struct timespec notif_ts_real, const char *orig_name, const void *orig_data, uint32_t operation_id, -+ uint32_t timeout_ms, int wait); - - /** - * @brief Write the result of having processed an event. -@@ -250,9 +256,8 @@ sr_error_info_t *sr_shmsub_notif_notify(sr_conn_ctx_t *conn, const struct lyd_no - * @param[in] result_str Result of processing the event in string. - * @return err_info, NULL on success. - */ --sr_error_info_t *sr_shmsub_listen_write_event(sr_sub_shm_t *sub_shm, uint32_t valid_subscr_count, -- sr_error_t err_code, sr_shm_t *shm_data_sub, const char *data, uint32_t data_len, const char *event_desc, -- const char *result_str); -+sr_error_info_t *sr_shmsub_listen_write_event(sr_sub_shm_t *sub_shm, uint32_t valid_subscr_count, sr_error_t err_code, -+ sr_shm_t *shm_data_sub, const char *data, uint32_t data_len, const char *event_desc, const char *result_str); - - /** - * @brief Process all module change events, if any. -diff --git a/src/shm_types.h b/src/shm_types.h -index 7b43ecef..5e5f4e77 100644 ---- a/src/shm_types.h -+++ b/src/shm_types.h -@@ -25,7 +25,7 @@ - #include "common_types.h" - #include "sysrepo_types.h" - --#define SR_SHM_VER 18 /**< Main, mod, and ext SHM version of their expected content structures. */ -+#define SR_SHM_VER 19 /**< Main, mod, and ext SHM version of their expected content structures. */ - #define SR_MAIN_SHM_LOCK "sr_main_lock" /**< Main SHM file lock name. */ - - /** -@@ -201,6 +201,7 @@ typedef struct { - ATOMIC_T new_sr_sid; /**< SID for a new session. */ - ATOMIC_T new_sub_id; /**< Subscription ID of a new subscription. */ - ATOMIC_T new_evpipe_num; /**< Event pipe number for a new subscription. */ -+ ATOMIC_T new_operation_id; /**< Operation ID to use for each callback of every operation. */ - - char repo_path[256]; /**< Repository path used when main SHM was created. */ - } sr_main_shm_t; -@@ -367,6 +368,7 @@ typedef struct { - - ATOMIC_T priority; /**< Priority of the subscriber. */ - uint32_t subscriber_count; /**< Number of subscribers to process this event. */ -+ uint32_t operation_id; /**< Operation ID for the callback. */ - } sr_sub_shm_t; - - #endif /* _SHM_TYPES_H */ -diff --git a/src/sysrepo.c b/src/sysrepo.c -index 797de6aa..97537207 100644 ---- a/src/sysrepo.c -+++ b/src/sysrepo.c -@@ -2778,7 +2778,7 @@ sr_get_item(sr_session_ctx_t *session, const char *path, uint32_t timeout_ms, sr - } - *value = NULL; - /* for operational, use operational and running datastore */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -2882,7 +2882,7 @@ sr_get_items(sr_session_ctx_t *session, const char *xpath, uint32_t timeout_ms, - *values = NULL; - *value_cnt = 0; - /* for operational, use operational and running datastore */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -3041,7 +3041,7 @@ sr_get_subtree(sr_session_ctx_t *session, const char *path, uint32_t timeout_ms, - timeout_ms = SR_OPER_CB_TIMEOUT; - } - /* for operational, use operational and running datastore */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -3156,7 +3156,7 @@ sr_get_data(sr_session_ctx_t *session, const char *xpath, uint32_t max_depth, ui - } - - /* for operational, use operational and running datastore */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -3293,7 +3293,7 @@ sr_get_node(sr_session_ctx_t *session, const char *path, uint32_t timeout_ms, sr - } - - /* for operational, use operational and running datastore */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -3866,7 +3866,7 @@ sr_validate(sr_session_ctx_t *session, const char *module_name, uint32_t timeout - timeout_ms = SR_OPER_CB_TIMEOUT; - } - /* for operational, use operational and running datastore */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -4281,7 +4281,7 @@ sr_apply_changes(sr_session_ctx_t *session, uint32_t timeout_ms) - } - - /* prepare mod_info */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds, 0); - - if (session->ds == SR_DS_OPERATIONAL) { - /* handle specially */ -@@ -4419,20 +4419,21 @@ cleanup: - * - * @param[in] session Session to use. - * @param[in] ly_mod Optional specific module. -+ * @param[in] operation_id Operation ID. - * @param[in,out] src_config Source data for the replace, they are spent. - * @param[in] timeout_ms Change callback timeout in milliseconds. - * @return err_info, NULL on success. - */ - static sr_error_info_t * --_sr_replace_config(sr_session_ctx_t *session, const struct lys_module *ly_mod, struct lyd_node **src_config, -- uint32_t timeout_ms) -+_sr_replace_config(sr_session_ctx_t *session, const struct lys_module *ly_mod, uint32_t operation_id, -+ struct lyd_node **src_config, uint32_t timeout_ms) - { - sr_error_info_t *err_info = NULL, *cb_err_info = NULL; - struct sr_mod_info_s mod_info; - - assert(!*src_config || !(*src_config)->prev->next); - assert(session->ds != SR_DS_OPERATIONAL); -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds, operation_id); - - /* single module/all modules */ - if (ly_mod) { -@@ -4514,7 +4515,7 @@ sr_replace_config(sr_session_ctx_t *session, const char *module_name, struct lyd - } - - /* replace the data */ -- if ((err_info = _sr_replace_config(session, ly_mod, &src_config, timeout_ms))) { -+ if ((err_info = _sr_replace_config(session, ly_mod, 0, &src_config, timeout_ms))) { - goto cleanup_unlock; - } - -@@ -4547,9 +4548,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ - } - if ((src_datastore == SR_DS_RUNNING) && (session->ds == SR_DS_CANDIDATE)) { - /* discard-changes, need no data, but lock running for READ and candidate for WRITE */ -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, src_datastore); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, src_datastore, 0); - } else { -- SR_MODINFO_INIT(mod_info, session->conn, src_datastore, src_datastore); -+ SR_MODINFO_INIT(mod_info, session->conn, src_datastore, src_datastore, 0); - } - - /* CONTEXT LOCK */ -@@ -4598,7 +4599,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ - } - - /* replace the data */ -- if ((err_info = _sr_replace_config(session, ly_mod, &mod_info.data, timeout_ms))) { -+ if ((err_info = _sr_replace_config(session, ly_mod, mod_info.operation_id, &mod_info.data, timeout_ms))) { - goto cleanup; - } - -@@ -4616,7 +4617,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ - sr_shmmod_modinfo_unlock(&mod_info); - - /* replace the data */ -- if ((err_info = _sr_replace_config(session, ly_mod, &mod_info.data, timeout_ms))) { -+ if ((err_info = _sr_replace_config(session, ly_mod, mod_info.operation_id, &mod_info.data, timeout_ms))) { - goto cleanup; - } - } -@@ -4655,7 +4656,7 @@ _sr_discard_oper_changes(sr_session_ctx_t *session, const char *module_name, int - if (!timeout_ms) { - timeout_ms = SR_CHANGE_CB_TIMEOUT; - } -- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); -+ SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -4720,7 +4721,7 @@ sr_get_oper_changes(sr_session_ctx_t *session, const char *module_name, sr_data_ - return sr_api_ret(session, err_info); - } - -- SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL); -+ SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL, 0); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(conn, SR_LOCK_READ, 0, __func__))) { -@@ -4937,7 +4938,7 @@ _sr_un_lock(sr_session_ctx_t *session, const char *module_name, int lock, uint32 - - SR_CHECK_ARG_APIRET(!session || !SR_IS_CONVENTIONAL_DS(session->ds), session, err_info); - -- SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds, 1); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -5023,7 +5024,7 @@ sr_get_lock(sr_conn_ctx_t *conn, sr_datastore_t datastore, const char *module_na - if (timestamp) { - memset(timestamp, 0, sizeof *timestamp); - } -- SR_MODINFO_INIT(mod_info, conn, datastore, datastore); -+ SR_MODINFO_INIT(mod_info, conn, datastore, datastore, 1); - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(conn, SR_LOCK_READ, 0, __func__))) { -@@ -5686,8 +5687,6 @@ sr_module_change_subscribe_enable(sr_session_ctx_t *session, struct sr_mod_info_ - sr_session_ctx_t *ev_sess = NULL; - sr_error_t err_code; - -- SR_MODINFO_INIT((*mod_info), session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds); -- - /* create mod_info structure with this module only, do not use cache to allow reading data in the callback - * (avoid dead-lock) */ - if ((err_info = sr_modinfo_add(ly_mod, NULL, 0, 0, mod_info))) { -@@ -5739,7 +5738,8 @@ sr_module_change_subscribe_enable(sr_session_ctx_t *session, struct sr_mod_info_ - SR_LOG_INF("Triggering \"%s\" \"%s\" event on enabled data.", ly_mod->name, sr_ev2str(ev_sess->ev)); - - /* present all changes in an "enabled" event */ -- err_code = callback(ev_sess, sub_id, ly_mod->name, xpath, sr_ev2api(ev_sess->ev), 0, private_data); -+ err_code = callback(ev_sess, sub_id, ly_mod->name, xpath, sr_ev2api(ev_sess->ev), mod_info->operation_id, -+ private_data); - if (err_code != SR_ERR_OK) { - /* callback failed but it is the only one so no "abort" event is necessary */ - if (ev_sess->ev_err_info) { -@@ -5856,7 +5856,7 @@ sr_module_change_subscribe(sr_session_ctx_t *session, const char *module_name, c - SR_CHECK_ARG_APIRET(!session || !SR_IS_STANDARD_DS(session->ds) || SR_IS_EVENT_SESS(session) || !module_name || - !callback || !subscription, session, err_info); - -- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_RUNNING, SR_DS_RUNNING); -+ SR_MODINFO_INIT(mod_info, session->conn, session->ds, session->ds == SR_DS_OPERATIONAL ? SR_DS_RUNNING : session->ds, 0); - - conn = session->conn; - /* only these options are relevant outside this function and will be stored */ -@@ -6761,7 +6761,7 @@ _sr_rpc_send_tree(sr_session_ctx_t *session, struct sr_mod_info_s *mod_info, con - sr_rpc_t *shm_rpc; - sr_dep_t *shm_deps; - uint16_t shm_dep_count; -- uint32_t event_id = 0; -+ uint32_t request_id = 0; - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -6794,7 +6794,7 @@ _sr_rpc_send_tree(sr_session_ctx_t *session, struct sr_mod_info_s *mod_info, con - sr_shmmod_modinfo_unlock(mod_info); - - sr_modinfo_erase(mod_info); -- SR_MODINFO_INIT(*mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); -+ SR_MODINFO_INIT(*mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); - - if (!strcmp(path, SR_RPC_FACTORY_RESET_PATH)) { - /* update the input as needed */ -@@ -6815,14 +6815,15 @@ _sr_rpc_send_tree(sr_session_ctx_t *session, struct sr_mod_info_s *mod_info, con - - /* publish RPC in an event and wait for a reply from the last subscriber */ - if ((err_info = sr_shmsub_rpc_notify(session->conn, &shm_rpc->subs, &shm_rpc->sub_count, path, input, -- session->orig_name, session->orig_data, timeout_ms, &event_id, &(*output)->tree, &cb_err_info))) { -+ session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, &request_id, &(*output)->tree, -+ &cb_err_info))) { - goto cleanup_rpcsub_unlock; - } - - if (cb_err_info) { - /* "rpc" event failed, publish "abort" event and finish */ - err_info = sr_shmsub_rpc_notify_abort(session->conn, &shm_rpc->subs, &shm_rpc->sub_count, path, -- input, session->orig_name, session->orig_data, timeout_ms, event_id); -+ input, session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, request_id); - goto cleanup_rpcsub_unlock; - } - -@@ -6890,7 +6891,7 @@ _sr_rpc_ext_send_tree(sr_session_ctx_t *session, const struct lyd_node *ext_pare - { - sr_error_info_t *err_info = NULL, *cb_err_info = NULL; - sr_mod_t *shm_mod; -- uint32_t event_id = 0; -+ uint32_t request_id = 0; - - /* CONTEXT LOCK */ - if ((err_info = sr_lycc_lock(session->conn, SR_LOCK_READ, 0, __func__))) { -@@ -6930,16 +6931,16 @@ _sr_rpc_ext_send_tree(sr_session_ctx_t *session, const struct lyd_node *ext_pare - } - - /* publish RPC in an event and wait for a reply from the last subscriber */ -- if ((err_info = sr_shmsub_rpc_notify(session->conn, &shm_mod->rpc_ext_subs, -- &shm_mod->rpc_ext_sub_count, path, input, session->orig_name, session->orig_data, timeout_ms, &event_id, -+ if ((err_info = sr_shmsub_rpc_notify(session->conn, &shm_mod->rpc_ext_subs, &shm_mod->rpc_ext_sub_count, path, -+ input, session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, &request_id, - &(*output)->tree, &cb_err_info))) { - goto cleanup_rpcsub_unlock; - } - - if (cb_err_info) { - /* "rpc" event failed, publish "abort" event and finish */ -- err_info = sr_shmsub_rpc_notify_abort(session->conn, &shm_mod->rpc_ext_subs, -- &shm_mod->rpc_ext_sub_count, path, input, session->orig_name, session->orig_data, timeout_ms, event_id); -+ err_info = sr_shmsub_rpc_notify_abort(session->conn, &shm_mod->rpc_ext_subs, &shm_mod->rpc_ext_sub_count, path, -+ input, session->orig_name, session->orig_data, mod_info->operation_id, timeout_ms, request_id); - goto cleanup_rpcsub_unlock; - } - -@@ -6963,7 +6964,6 @@ _sr_rpc_ext_send_tree(sr_session_ctx_t *session, const struct lyd_node *ext_pare - goto cleanup; - } - -- /* success */ - goto cleanup; - - cleanup_rpcsub_unlock: -@@ -7003,7 +7003,7 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim - if (!timeout_ms) { - timeout_ms = SR_RPC_CB_TIMEOUT; - } -- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); -+ SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); - - /* check input data tree */ - input_op = NULL; -@@ -7330,7 +7330,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t - if (!timeout_ms) { - timeout_ms = SR_NOTIF_CB_TIMEOUT; - } -- SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING); -+ SR_MODINFO_INIT(mod_info, session->conn, SR_DS_OPERATIONAL, SR_DS_RUNNING, 0); - - /* check notif data tree */ - notif_op = NULL; -@@ -7423,7 +7423,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t - - /* publish notif in an event */ - err_info = sr_shmsub_notif_notify(session->conn, notif_top, notif_ts_mono, notif_ts_real, session->orig_name, -- session->orig_data, timeout_ms, wait); -+ session->orig_data, mod_info.operation_id, timeout_ms, wait); - - /* NOTIF SUB READ UNLOCK */ - sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, session->conn->cid, __func__); -diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h -index dff6f5bb..337ee51a 100644 ---- a/src/sysrepo_types.h -+++ b/src/sysrepo_types.h -@@ -542,13 +542,12 @@ typedef struct sr_change_iter_s sr_change_iter_t; - * @param[in] module_name Name of the module where the change has occurred. - * @param[in] xpath [XPath](@ref paths) used when subscribing, NULL if the whole module was subscribed to. - * @param[in] event Type of the callback event that has occurred. -- * @param[in] request_id Request ID unique for the specific @p module_name. Connected events -- * for one request (::SR_EV_CHANGE and ::SR_EV_DONE, for example) have the same request ID. -+ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. - * @param[in] private_data Private context opaque to sysrepo, as passed to ::sr_module_change_subscribe call. - * @return User error code (::SR_ERR_OK on success). - */ - typedef int (*sr_module_change_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *module_name, const char *xpath, -- sr_event_t event, uint32_t request_id, void *private_data); -+ sr_event_t event, uint32_t operation_id, void *private_data); - - /** @} datasubs */ - -@@ -568,7 +567,7 @@ typedef int (*sr_module_change_cb)(sr_session_ctx_t *session, uint32_t sub_id, c - * @param[in] input Array of input parameters. - * @param[in] input_cnt Number of input parameters. - * @param[in] event Type of the callback event that has occurred. -- * @param[in] request_id Request ID unique for the specific @p op_path. -+ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. - * @param[out] output Array of output parameters. Should be allocated on heap, - * will be freed by sysrepo after sending of the RPC response. - * @param[out] output_cnt Number of output parameters. -@@ -576,7 +575,7 @@ typedef int (*sr_module_change_cb)(sr_session_ctx_t *session, uint32_t sub_id, c - * @return User error code (::SR_ERR_OK on success). - */ - typedef int (*sr_rpc_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *xpath, const sr_val_t *input, -- const size_t input_cnt, sr_event_t event, uint32_t request_id, sr_val_t **output, size_t *output_cnt, -+ const size_t input_cnt, sr_event_t event, uint32_t operation_id, sr_val_t **output, size_t *output_cnt, - void *private_data); - - /** -@@ -589,13 +588,13 @@ typedef int (*sr_rpc_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char - * @param[in] op_path Simple operation [path](@ref paths) identifying the RPC/action. - * @param[in] input Data tree of input parameters. Always points to the __RPC/action__ itself, even for nested operations. - * @param[in] event Type of the callback event that has occurred. -- * @param[in] request_id Request ID unique for the specific @p op_path. -+ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. - * @param[out] output Data tree for appending any output parameters, the operation root node is provided.. - * @param[in] private_data Private context opaque to sysrepo, as passed to ::sr_rpc_subscribe_tree call. - * @return User error code (::SR_ERR_OK on success). - */ - typedef int (*sr_rpc_tree_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *op_path, const struct lyd_node *input, -- sr_event_t event, uint32_t request_id, struct lyd_node *output, void *private_data); -+ sr_event_t event, uint32_t operation_id, struct lyd_node *output, void *private_data); - - /** @} rpcsubs */ - -@@ -680,7 +679,7 @@ typedef void (*sr_event_notif_tree_cb)(sr_session_ctx_t *session, uint32_t sub_i - * @param[in] path [Path](@ref paths) identifying the subtree that is supposed to be provided, same as the one used - * for the subscription. - * @param[in] request_xpath [XPath](@ref paths) as requested by a client. Can be NULL. -- * @param[in] request_id Request ID unique for the specific @p module_name. -+ * @param[in] operation_id Operation ID of the current operation used for all the callbacks and events. - * @param[in,out] parent Pointer to an existing parent of the requested nodes. Is NULL for top-level nodes. - * Caller is supposed to append the requested nodes to this data subtree and return either the original parent - * or a top-level node. -@@ -688,7 +687,7 @@ typedef void (*sr_event_notif_tree_cb)(sr_session_ctx_t *session, uint32_t sub_i - * @return User error code (::SR_ERR_OK on success). - */ - typedef int (*sr_oper_get_items_cb)(sr_session_ctx_t *session, uint32_t sub_id, const char *module_name, const char *path, -- const char *request_xpath, uint32_t request_id, struct lyd_node **parent, void *private_data); -+ const char *request_xpath, uint32_t operation_id, struct lyd_node **parent, void *private_data); - - /** @} oper_subs */ - -diff --git a/tests/test_oper_pull.c b/tests/test_oper_pull.c -index 91ba526e..793a6456 100644 ---- a/tests/test_oper_pull.c -+++ b/tests/test_oper_pull.c -@@ -974,8 +974,7 @@ enabled_change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *module - int ret, *called = (int *)private_data; - - (void)sub_id; -- -- assert_int_equal(request_id, 0); -+ (void)request_id; - - if (!strcmp(xpath, "/ietf-interfaces:interfaces/interface[name='eth128']")) { - assert_string_equal(module_name, "ietf-interfaces"); --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch b/patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch deleted file mode 100644 index 17910306..00000000 --- a/patches/sysrepo/3.3.10/0014-SOVERSION-bump-to-version-7.29.13.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 1977326b9a63bc9e1b7746dc0a5becb9b2aece26 Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Tue, 14 Jan 2025 13:36:59 +0100 -Subject: [PATCH 14/22] SOVERSION bump to version 7.29.13 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 7d49cedb..040d2242 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_ - # with backward compatible change and micro version is connected with any internal change of the library. - set(SYSREPO_MAJOR_SOVERSION 7) - set(SYSREPO_MINOR_SOVERSION 29) --set(SYSREPO_MICRO_SOVERSION 12) -+set(SYSREPO_MICRO_SOVERSION 13) - set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION}) - set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION}) - --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch b/patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch deleted file mode 100644 index 2ff17b68..00000000 --- a/patches/sysrepo/3.3.10/0015-VERSION-bump-to-version-3.4.1.patch +++ /dev/null @@ -1,30 +0,0 @@ -From df88c15e014d75dbd6e48d4ff1291fb6068f296d Mon Sep 17 00:00:00 2001 -From: Michal Vasko -Date: Tue, 14 Jan 2025 13:37:09 +0100 -Subject: [PATCH 15/22] VERSION bump to version 3.4.1 -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik - -Signed-off-by: Mattias Walström ---- - CMakeLists.txt | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 040d2242..7b86c0f3 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -54,7 +54,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - # micro version is changed with a set of small changes or bugfixes anywhere in the project. - set(SYSREPO_MAJOR_VERSION 3) - set(SYSREPO_MINOR_VERSION 4) --set(SYSREPO_MICRO_VERSION 0) -+set(SYSREPO_MICRO_VERSION 1) - set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION}) - - # Version of the library --- -2.43.0 - diff --git a/patches/sysrepo/3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch b/patches/sysrepo/3.6.11/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch similarity index 92% rename from patches/sysrepo/3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch rename to patches/sysrepo/3.6.11/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch index bcc85d5f..41c03133 100644 --- a/patches/sysrepo/3.3.10/0016-sysrepo-plugind-add-support-for-running-in-foregroun.patch +++ b/patches/sysrepo/3.6.11/0001-sysrepo-plugind-add-support-for-running-in-foregroun.patch @@ -1,12 +1,12 @@ -From 401c5b8ccce07b84e3b364283a8c789908f62f01 Mon Sep 17 00:00:00 2001 +From 0e40d3adbda4f9f215ed6bcb8de519dbfae237f0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Mar 2023 10:37:53 +0200 -Subject: [PATCH 16/22] sysrepo-plugind: add support for running in foreground +Subject: [PATCH 1/7] sysrepo-plugind: add support for running in foreground with syslog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Signed-off-by: Joachim Wiberg Signed-off-by: Mattias Walström @@ -15,7 +15,7 @@ Signed-off-by: Mattias Walström 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/executables/sysrepo-plugind.c b/src/executables/sysrepo-plugind.c -index 3b66cfa6..c531ca60 100644 +index be43478b..e183dc3c 100644 --- a/src/executables/sysrepo-plugind.c +++ b/src/executables/sysrepo-plugind.c @@ -100,6 +100,7 @@ help_print(void) diff --git a/patches/sysrepo/3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch b/patches/sysrepo/3.6.11/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch similarity index 87% rename from patches/sysrepo/3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch rename to patches/sysrepo/3.6.11/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch index 35f79f7d..8fd14dc0 100644 --- a/patches/sysrepo/3.3.10/0017-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch +++ b/patches/sysrepo/3.6.11/0002-Allow-SR_EV_DONE-to-return-any-error-to-sysrepocfg.patch @@ -1,11 +1,11 @@ -From 6cba2363b8d3a56755365f59af7830f5bd25824a Mon Sep 17 00:00:00 2001 +From 95e052137d1376df9c1a2a684cb642b40030f26e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 7 May 2024 15:41:53 +0200 -Subject: [PATCH 17/22] Allow SR_EV_DONE to return any error to sysrepocfg +Subject: [PATCH 2/7] Allow SR_EV_DONE to return any error to sysrepocfg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Importing a system configuration with sysrepocfg the model callbacks do their best to validate the configuration and prepare the transaction in @@ -28,10 +28,10 @@ Signed-off-by: Mattias Walström 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/shm_sub.c b/src/shm_sub.c -index fd6f105b..588cce09 100644 +index 17b21bd1..f0d9c788 100644 --- a/src/shm_sub.c +++ b/src/shm_sub.c -@@ -1766,7 +1766,7 @@ cleanup: +@@ -1831,7 +1831,7 @@ cleanup: sr_error_info_t * sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name, const void *orig_data, @@ -40,7 +40,7 @@ index fd6f105b..588cce09 100644 { sr_error_info_t *err_info = NULL; struct sr_mod_info_mod_s *mod = NULL; -@@ -1911,12 +1911,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * +@@ -1978,12 +1978,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char * sr_rwunlock(&nsub->sub_shm->lock, 0, SR_LOCK_WRITE, cid, __func__); nsub->lock = SR_LOCK_NONE; @@ -63,7 +63,7 @@ index fd6f105b..588cce09 100644 nsub->pending_event = 0; } } while (1); -@@ -3424,7 +3431,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc +@@ -3544,7 +3551,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc sr_error_info_t * sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_subs, sr_conn_ctx_t *conn) { @@ -72,7 +72,7 @@ index fd6f105b..588cce09 100644 uint32_t i, data_len = 0, valid_subscr_count; char *data = NULL, *shm_data_ptr; int ret = SR_ERR_OK, filter_valid; -@@ -3553,6 +3560,11 @@ process_event: +@@ -3673,6 +3680,11 @@ process_event: } break; } @@ -84,7 +84,7 @@ index fd6f105b..588cce09 100644 } /* subscription processed this event */ -@@ -3585,6 +3597,11 @@ process_event: +@@ -3705,6 +3717,11 @@ process_event: } break; case SR_SUB_EV_DONE: @@ -96,7 +96,7 @@ index fd6f105b..588cce09 100644 case SR_SUB_EV_ABORT: /* nothing to do */ break; -@@ -3604,14 +3621,17 @@ process_event: +@@ -3724,14 +3741,17 @@ process_event: /* SUB WRITE URGE LOCK */ if (sr_shmsub_change_listen_relock(sub_shm, SR_LOCK_WRITE_URGE, &sub_info, change_sub, change_subs->module_name, @@ -116,7 +116,7 @@ index fd6f105b..588cce09 100644 goto cleanup; } -@@ -4101,7 +4121,7 @@ finish_iter: +@@ -4221,7 +4241,7 @@ finish_iter: sr_errinfo_free(&cb_err_info); /* publish "done" event */ @@ -126,10 +126,10 @@ index fd6f105b..588cce09 100644 } diff --git a/src/shm_sub.h b/src/shm_sub.h -index 505dab39..617a88b7 100644 +index 0cd22884..bd697ccc 100644 --- a/src/shm_sub.h +++ b/src/shm_sub.h -@@ -144,7 +144,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, +@@ -159,7 +159,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info, * @return err_info, NULL on success. */ sr_error_info_t *sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name, @@ -139,19 +139,19 @@ index 505dab39..617a88b7 100644 /** * @brief Notify about (generate) a change "abort" event. diff --git a/src/sysrepo.c b/src/sysrepo.c -index 97537207..a1bbc61f 100644 +index f9ef46b3..93f73298 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -3994,7 +3994,7 @@ sr_error_info_t * +@@ -4256,7 +4256,7 @@ sr_error_info_t * sr_changes_notify_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del, - uint32_t timeout_ms, sr_error_info_t **err_info2) + uint32_t timeout_ms, sr_lock_mode_t has_change_sub_lock, sr_error_info_t **err_info2) { - sr_error_info_t *err_info = NULL; + sr_error_info_t *err_info = NULL, *cb_err_info = NULL; struct sr_denied denied = {0}; - sr_lock_mode_t change_sub_lock = SR_LOCK_NONE; + sr_lock_mode_t change_sub_lock = has_change_sub_lock; uint32_t sid = 0, err_count; -@@ -4141,7 +4141,7 @@ store: +@@ -4413,7 +4413,7 @@ store: } /* publish "done" event, all changes were applied */ diff --git a/patches/sysrepo/3.3.10/0018-Allow-to-copy-from-factory-default.patch b/patches/sysrepo/3.6.11/0003-Allow-to-copy-from-factory-default.patch similarity index 78% rename from patches/sysrepo/3.3.10/0018-Allow-to-copy-from-factory-default.patch rename to patches/sysrepo/3.6.11/0003-Allow-to-copy-from-factory-default.patch index e5f93298..7424e5ef 100644 --- a/patches/sysrepo/3.3.10/0018-Allow-to-copy-from-factory-default.patch +++ b/patches/sysrepo/3.6.11/0003-Allow-to-copy-from-factory-default.patch @@ -1,11 +1,11 @@ -From e328151d027e408befb8b941418f84e2909f92e6 Mon Sep 17 00:00:00 2001 +From 5d487799853f1550b1cb0cd071fedb75ac0166e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 8 May 2024 17:00:50 +0200 -Subject: [PATCH 18/22] Allow to copy from factory default +Subject: [PATCH 3/7] Allow to copy from factory default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Signed-off-by: Mattias Walström --- @@ -13,10 +13,10 @@ Signed-off-by: Mattias Walström 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sysrepo.c b/src/sysrepo.c -index a1bbc61f..22ee9fe4 100644 +index 93f73298..6c7ba43e 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -4535,7 +4535,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ +@@ -4838,7 +4838,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ struct sr_mod_info_s mod_info; const struct lys_module *ly_mod = NULL; diff --git a/patches/sysrepo/3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch b/patches/sysrepo/3.6.11/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch similarity index 93% rename from patches/sysrepo/3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch rename to patches/sysrepo/3.6.11/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch index a58f8730..364da784 100644 --- a/patches/sysrepo/3.3.10/0019-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch +++ b/patches/sysrepo/3.6.11/0004-Add-z-switch-to-sysrepoctl-to-install-factory-config.patch @@ -1,12 +1,12 @@ -From f833cf10647b0c76386fe0850011204a321bb0d9 Mon Sep 17 00:00:00 2001 +From 1c8c3b6fb780d281811a0ea25bd96c555d274a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Mon, 6 May 2024 14:49:32 +0200 -Subject: [PATCH 19/22] Add -z switch to sysrepoctl to install factory config +Subject: [PATCH 4/7] Add -z switch to sysrepoctl to install factory config from a json file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires This to be able to load the yang modules during build time instead on boot. @@ -73,10 +73,10 @@ index b9680551..4cdba6a0 100644 /* list */ if ((r = srctl_list(conn))) { diff --git a/src/lyd_mods.h b/src/lyd_mods.h -index 1960bc06..a8ca5add 100644 +index f3a9b215..b86f24b4 100644 --- a/src/lyd_mods.h +++ b/src/lyd_mods.h -@@ -102,4 +102,11 @@ sr_error_info_t *sr_lydmods_change_chng_feature(const struct ly_ctx *ly_ctx, con +@@ -103,4 +103,11 @@ sr_error_info_t *sr_lydmods_change_chng_feature(const struct ly_ctx *ly_ctx, con sr_error_info_t *sr_lydmods_change_chng_replay_support(const struct lys_module *ly_mod, int enable, struct ly_set *mod_set, sr_conn_ctx_t *conn, struct lyd_node **sr_mods); @@ -89,10 +89,10 @@ index 1960bc06..a8ca5add 100644 + #endif diff --git a/src/sysrepo.c b/src/sysrepo.c -index 22ee9fe4..518f34e6 100644 +index 6c7ba43e..4877080d 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -1621,6 +1621,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count) +@@ -1613,6 +1613,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count) free(new_mods); } @@ -149,7 +149,7 @@ index 22ee9fe4..518f34e6 100644 sr_install_module(sr_conn_ctx_t *conn, const char *schema_path, const char *search_dirs, const char **features) { diff --git a/src/sysrepo.h b/src/sysrepo.h -index 6881830d..0280244a 100644 +index e13d3776..4a830e26 100644 --- a/src/sysrepo.h +++ b/src/sysrepo.h @@ -34,7 +34,6 @@ extern "C" { diff --git a/patches/sysrepo/3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch b/patches/sysrepo/3.6.11/0005-Introduce-new-log-level-SEC-for-audit-trails.patch similarity index 95% rename from patches/sysrepo/3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch rename to patches/sysrepo/3.6.11/0005-Introduce-new-log-level-SEC-for-audit-trails.patch index cebf6a21..6d3ceae8 100644 --- a/patches/sysrepo/3.3.10/0020-Introduce-new-log-level-SEC-for-audit-trails.patch +++ b/patches/sysrepo/3.6.11/0005-Introduce-new-log-level-SEC-for-audit-trails.patch @@ -1,11 +1,11 @@ -From fa14b6da3db9cf08d10108ce95fdfaecf7f76bf7 Mon Sep 17 00:00:00 2001 +From 04966d045da6ae10a4fafccde2740f8c2aa03c29 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 21 Aug 2024 16:00:35 +0200 -Subject: [PATCH 20/22] Introduce new log level [SEC] for audit trails +Subject: [PATCH 5/7] Introduce new log level [SEC] for audit trails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires This adds a new log level for security and audit trail related log messages, see LOG_AUDIT defined in RFC5424. E.g., nacm user applied @@ -86,7 +86,7 @@ index 9a60f1f6..acd7eead 100644 #define SR_CHECK_MEM_GOTO(cond, err_info, go) if (cond) { SR_ERRINFO_MEM(&(err_info)); goto go; } diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h -index 337ee51a..bec7317d 100644 +index 1fc7ed0b..b6540f77 100644 --- a/src/sysrepo_types.h +++ b/src/sysrepo_types.h @@ -65,7 +65,8 @@ typedef enum { diff --git a/patches/sysrepo/3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch b/patches/sysrepo/3.6.11/0006-Add-audit-trail-for-high-priority-system-changes.patch similarity index 84% rename from patches/sysrepo/3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch rename to patches/sysrepo/3.6.11/0006-Add-audit-trail-for-high-priority-system-changes.patch index 3d1889f8..d83ea10d 100644 --- a/patches/sysrepo/3.3.10/0021-Add-audit-trail-for-high-priority-system-changes.patch +++ b/patches/sysrepo/3.6.11/0006-Add-audit-trail-for-high-priority-system-changes.patch @@ -1,11 +1,11 @@ -From 0c28a5f1c99d8fe200f23647cf06ba640e767166 Mon Sep 17 00:00:00 2001 +From 385382722f13fc3dff079b338201be1f2d7a81f0 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 21 Aug 2024 16:04:43 +0200 -Subject: [PATCH 21/22] Add audit trail for high priority system changes +Subject: [PATCH 6/7] Add audit trail for high priority system changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires Committing a change to running, copying to a datastore, or calling an RPC may be restricted by NACM. In these cases a system administrator @@ -22,10 +22,10 @@ Signed-off-by: Mattias Walström 1 file changed, 12 insertions(+) diff --git a/src/sysrepo.c b/src/sysrepo.c -index 518f34e6..a7e6563a 100644 +index 4877080d..b3cacca7 100644 --- a/src/sysrepo.c +++ b/src/sysrepo.c -@@ -4189,6 +4189,9 @@ store: +@@ -4461,6 +4461,9 @@ store: goto cleanup; } @@ -35,17 +35,17 @@ index 518f34e6..a7e6563a 100644 /* publish "done" event, all changes were applied */ if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, &cb_err_info))) { goto cleanup; -@@ -4200,6 +4203,9 @@ store: +@@ -4472,6 +4475,9 @@ store: } cleanup: + if (session && session->nacm_user && mod_info->ds == SR_DS_RUNNING) + SR_LOG_SEC("user \"%s\" committed changes to %s.", session->nacm_user, sr_ds2str(mod_info->ds)); + - if (change_sub_lock) { + if (change_sub_lock && !has_change_sub_lock) { assert(change_sub_lock == SR_LOCK_READ); -@@ -4671,6 +4677,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ +@@ -4974,6 +4980,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_ } } @@ -55,7 +55,7 @@ index 518f34e6..a7e6563a 100644 cleanup: /* MODULES UNLOCK */ sr_shmmod_modinfo_unlock(&mod_info); -@@ -7132,6 +7141,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim +@@ -7498,6 +7507,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim } } diff --git a/patches/sysrepo/3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch b/patches/sysrepo/3.6.11/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch similarity index 77% rename from patches/sysrepo/3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch rename to patches/sysrepo/3.6.11/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch index d4c1c7d1..271c9931 100644 --- a/patches/sysrepo/3.3.10/0022-On-error-in-sr_shmsub_listen_thread-exit-process.patch +++ b/patches/sysrepo/3.6.11/0007-On-error-in-sr_shmsub_listen_thread-exit-process.patch @@ -1,11 +1,11 @@ -From 72be8d7e245fe8cd30bd1e2b5d308aacbf0f7b07 Mon Sep 17 00:00:00 2001 +From 7a63e5b1ca6604a5cc156c61e080be3eaecafa60 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 23 Aug 2024 12:22:06 +0200 -Subject: [PATCH 22/22] On error in sr_shmsub_listen_thread(), exit process +Subject: [PATCH 7/7] On error in sr_shmsub_listen_thread(), exit process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -Organization: Addiva Elektronik +Organization: Wires If processing callback events in, e.g., sysrepo-plugind, make sure to log the error and exit(1) the entire process so the system can decide @@ -18,10 +18,10 @@ Signed-off-by: Mattias Walström 1 file changed, 2 insertions(+) diff --git a/src/shm_sub.c b/src/shm_sub.c -index 588cce09..07d8960f 100644 +index f0d9c788..eed80d92 100644 --- a/src/shm_sub.c +++ b/src/shm_sub.c -@@ -4987,5 +4987,7 @@ error: +@@ -5104,5 +5104,7 @@ error: /* free our own resources */ ATOMIC_STORE_RELAXED(subscr->thread_running, 0); pthread_detach(pthread_self()); diff --git a/src/confd/yang/libnetconf2.inc b/src/confd/yang/libnetconf2.inc index 4c176688..92c490d7 100644 --- a/src/confd/yang/libnetconf2.inc +++ b/src/confd/yang/libnetconf2.inc @@ -21,5 +21,5 @@ MODULES=( "ietf-tls-common@2023-12-28.yang -e tls10 -e tls11 -e tls12 -e tls13 -e hello-params" "ietf-tls-server@2023-12-28.yang -e server-ident-x509-cert -e client-auth-supported -e client-auth-x509-cert" "ietf-netconf-server@2023-12-28.yang -e ssh-listen -e tls-listen -e ssh-call-home -e tls-call-home -e central-netconf-server-supported" - "libnetconf2-netconf-server@2024-07-09.yang" + "libnetconf2-netconf-server@2025-01-23.yang" ) diff --git a/src/confd/yang/netopeer2.inc b/src/confd/yang/netopeer2.inc index 3e615126..15b45927 100644 --- a/src/confd/yang/netopeer2.inc +++ b/src/confd/yang/netopeer2.inc @@ -12,4 +12,6 @@ MODULES=( "ietf-network-instance@2019-01-21.yang" "ietf-subscribed-notifications@2019-09-09.yang -e encode-xml -e replay -e subtree -e xpath" "ietf-yang-push@2019-09-09.yang -e on-change" + "ietf-system-capabilities@2022-02-17.yang" + "ietf-notification-capabilities@2022-02-17.yang" ) diff --git a/src/confd/yang/rousette.inc b/src/confd/yang/rousette.inc index ce51a754..1c1de132 100644 --- a/src/confd/yang/rousette.inc +++ b/src/confd/yang/rousette.inc @@ -2,4 +2,6 @@ MODULES=( "ietf-restconf@2017-01-26.yang" "ietf-yang-patch@2017-02-22.yang" "ietf-restconf-monitoring@2017-01-26.yang" -) \ No newline at end of file + "ietf-subscribed-notifications@2019-09-09.yang" + "ietf-restconf-subscribed-notifications@2019-11-17.yang" +) diff --git a/src/confd/yang/rousette/ietf-restconf-subscribed-notifications@2019-11-17.yang b/src/confd/yang/rousette/ietf-restconf-subscribed-notifications@2019-11-17.yang new file mode 100644 index 00000000..98a79908 --- /dev/null +++ b/src/confd/yang/rousette/ietf-restconf-subscribed-notifications@2019-11-17.yang @@ -0,0 +1,85 @@ +module ietf-restconf-subscribed-notifications { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:" + + "ietf-restconf-subscribed-notifications"; + prefix rsn; + + import ietf-subscribed-notifications { + prefix sn; + } + import ietf-inet-types { + prefix inet; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + contact + "WG Web: + WG List: + + Editor: Eric Voit + + + Editor: Alexander Clemm + + + Editor: Reshad Rahman + "; + description + "Defines RESTCONF as a supported transport for subscribed + event notifications. + + Copyright (c) 2019 IETF Trust and the persons identified + as authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Simplified BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8650; see the + RFC itself for full legal notices."; + + revision 2019-11-17 { + description + "Initial version"; + reference + "RFC 8650: Dynamic Subscription to YANG Events and Datastores + over RESTCONF"; + } + + grouping uri { + description + "Provides a reusable description of a URI."; + leaf uri { + type inet:uri; + config false; + description + "Location of a subscription-specific URI on the publisher."; + } + } + + augment "/sn:establish-subscription/sn:output" { + description + "This augmentation allows RESTCONF-specific parameters for a + response to a publisher's subscription request."; + uses uri; + } + + augment "/sn:subscriptions/sn:subscription" { + description + "This augmentation allows RESTCONF-specific parameters to be + exposed for a subscription."; + uses uri; + } + + augment "/sn:subscription-modified" { + description + "This augmentation allows RESTCONF-specific parameters to be + included as part of the notification that a subscription has + been modified."; + uses uri; + } +} diff --git a/src/confd/yang/rousette/ietf-subscribed-notifications@2019-09-09.yang b/src/confd/yang/rousette/ietf-subscribed-notifications@2019-09-09.yang new file mode 100644 index 00000000..e04593c3 --- /dev/null +++ b/src/confd/yang/rousette/ietf-subscribed-notifications@2019-09-09.yang @@ -0,0 +1,1350 @@ +module ietf-subscribed-notifications { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications"; + prefix sn; + + import ietf-inet-types { + prefix inet; + reference + "RFC 6991: Common YANG Data Types"; + } + import ietf-interfaces { + prefix if; + reference + "RFC 8343: A YANG Data Model for Interface Management"; + } + import ietf-netconf-acm { + prefix nacm; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + import ietf-network-instance { + prefix ni; + reference + "RFC 8529: YANG Data Model for Network Instances"; + } + import ietf-restconf { + prefix rc; + reference + "RFC 8040: RESTCONF Protocol"; + } + import ietf-yang-types { + prefix yang; + reference + "RFC 6991: Common YANG Data Types"; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + contact + "WG Web: + WG List: + + Author: Alexander Clemm + + + Author: Eric Voit + + + Author: Alberto Gonzalez Prieto + + + Author: Einar Nilsen-Nygaard + + + Author: Ambika Prasad Tripathy + "; + description + "This module defines a YANG data model for subscribing to event + records and receiving matching content in notification messages. + + The key words 'MUST', 'MUST NOT', 'REQUIRED', 'SHALL', 'SHALL + NOT', 'SHOULD', 'SHOULD NOT', 'RECOMMENDED', 'NOT RECOMMENDED', + 'MAY', and 'OPTIONAL' in this document are to be interpreted as + described in BCP 14 (RFC 2119) (RFC 8174) when, and only when, + they appear in all capitals, as shown here. + + Copyright (c) 2019 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject to + the license terms contained in, the Simplified BSD License set + forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8639; see the + RFC itself for full legal notices."; + + revision 2019-09-09 { + description + "Initial version."; + reference + "RFC 8639: A YANG Data Model for Subscriptions to + Event Notifications"; + } + + /* + * FEATURES + */ + + feature configured { + description + "This feature indicates that configuration of subscriptions is + supported."; + } + + feature dscp { + description + "This feature indicates that a publisher supports the ability + to set the Differentiated Services Code Point (DSCP) value in + outgoing packets."; + } + + feature encode-json { + description + "This feature indicates that JSON encoding of notification + messages is supported."; + } + + feature encode-xml { + description + "This feature indicates that XML encoding of notification + messages is supported."; + } + + feature interface-designation { + description + "This feature indicates that a publisher supports sourcing all + receiver interactions for a configured subscription from a + single designated egress interface."; + } + + feature qos { + description + "This feature indicates that a publisher supports absolute + dependencies of one subscription's traffic over another + as well as weighted bandwidth sharing between subscriptions. + Both of these are Quality of Service (QoS) features that allow + differentiated treatment of notification messages between a + publisher and a specific receiver."; + } + + feature replay { + description + "This feature indicates that historical event record replay is + supported. With replay, it is possible for past event records + to be streamed in chronological order."; + } + + feature subtree { + description + "This feature indicates support for YANG subtree filtering."; + reference + "RFC 6241: Network Configuration Protocol (NETCONF), + Section 6"; + } + + feature supports-vrf { + description + "This feature indicates that a publisher supports VRF + configuration for configured subscriptions. VRF support for + dynamic subscriptions does not require this feature."; + reference + "RFC 8529: YANG Data Model for Network Instances, + Section 6"; + } + + feature xpath { + description + "This feature indicates support for XPath filtering."; + reference + "XML Path Language (XPath) Version 1.0 + (https://www.w3.org/TR/1999/REC-xpath-19991116)"; + } + + /* + * EXTENSIONS + */ + + extension subscription-state-notification { + description + "This statement applies only to notifications. It indicates + that the notification is a subscription state change + notification. Therefore, it does not participate in a regular + event stream and does not need to be specifically subscribed + to in order to be received. This statement can only occur as + a substatement of the YANG 'notification' statement. This + statement is not for use outside of this YANG module."; + } + + /* + * IDENTITIES + */ + /* Identities for RPC and notification errors */ + + identity delete-subscription-error { + description + "Base identity for the problem found while attempting to + fulfill either a 'delete-subscription' RPC request or a + 'kill-subscription' RPC request."; + } + + identity establish-subscription-error { + description + "Base identity for the problem found while attempting to + fulfill an 'establish-subscription' RPC request."; + } + + identity modify-subscription-error { + description + "Base identity for the problem found while attempting to + fulfill a 'modify-subscription' RPC request."; + } + + identity subscription-suspended-reason { + description + "Base identity for the problem condition communicated to a + receiver as part of a 'subscription-suspended' + notification."; + } + + identity subscription-terminated-reason { + description + "Base identity for the problem condition communicated to a + receiver as part of a 'subscription-terminated' + notification."; + } + + identity dscp-unavailable { + base establish-subscription-error; + if-feature "dscp"; + description + "The publisher is unable to mark notification messages with + prioritization information in a way that will be respected + during network transit."; + } + + identity encoding-unsupported { + base establish-subscription-error; + description + "Unable to encode notification messages in the desired + format."; + } + + identity filter-unavailable { + base subscription-terminated-reason; + description + "Referenced filter does not exist. This means a receiver is + referencing a filter that doesn't exist or to which it + does not have access permissions."; + } + + identity filter-unsupported { + base establish-subscription-error; + base modify-subscription-error; + description + "Cannot parse syntax in the filter. This failure can be from + a syntax error or a syntax too complex to be processed by the + publisher."; + } + + identity insufficient-resources { + base establish-subscription-error; + base modify-subscription-error; + base subscription-suspended-reason; + description + "The publisher does not have sufficient resources to support + the requested subscription. An example might be that + allocated CPU is too limited to generate the desired set of + notification messages."; + } + + identity no-such-subscription { + base modify-subscription-error; + base delete-subscription-error; + base subscription-terminated-reason; + description + "Referenced subscription doesn't exist. This may be as a + result of a nonexistent subscription ID, an ID that belongs to + another subscriber, or an ID for a configured subscription."; + } + + identity replay-unsupported { + base establish-subscription-error; + if-feature "replay"; + description + "Replay cannot be performed for this subscription. This means + the publisher will not provide the requested historic + information from the event stream via replay to this + receiver."; + } + + identity stream-unavailable { + base subscription-terminated-reason; + description + "Not a subscribable event stream. This means the referenced + event stream is not available for subscription by the + receiver."; + } + + identity suspension-timeout { + base subscription-terminated-reason; + description + "Termination of a previously suspended subscription. The + publisher has eliminated the subscription, as it exceeded a + time limit for suspension."; + } + + identity unsupportable-volume { + base subscription-suspended-reason; + description + "The publisher does not have the network bandwidth needed to + get the volume of generated information intended for a + receiver."; + } + + /* Identities for encodings */ + + identity configurable-encoding { + description + "If a transport identity derives from this identity, it means + that it supports configurable encodings. An example of a + configurable encoding might be a new identity such as + 'encode-cbor'. Such an identity could use + 'configurable-encoding' as its base. This would allow a + dynamic subscription encoded in JSON (RFC 8259) to request + that notification messages be encoded via the Concise Binary + Object Representation (CBOR) (RFC 7049). Further details for + any specific configurable encoding would be explored in a + transport document based on this specification."; + reference + "RFC 8259: The JavaScript Object Notation (JSON) Data + Interchange Format + RFC 7049: Concise Binary Object Representation (CBOR)"; + } + + identity encoding { + description + "Base identity to represent data encodings."; + } + + identity encode-xml { + base encoding; + if-feature "encode-xml"; + description + "Encode data using XML as described in RFC 7950."; + reference + "RFC 7950: The YANG 1.1 Data Modeling Language"; + } + + identity encode-json { + base encoding; + if-feature "encode-json"; + description + "Encode data using JSON as described in RFC 7951."; + reference + "RFC 7951: JSON Encoding of Data Modeled with YANG"; + } + + /* Identities for transports */ + + identity transport { + description + "An identity that represents the underlying mechanism for + passing notification messages."; + } + + /* + * TYPEDEFs + */ + + typedef encoding { + type identityref { + base encoding; + } + description + "Specifies a data encoding, e.g., for a data subscription."; + } + + typedef stream-filter-ref { + type leafref { + path "/sn:filters/sn:stream-filter/sn:name"; + } + description + "This type is used to reference an event stream filter."; + } + + typedef stream-ref { + type leafref { + path "/sn:streams/sn:stream/sn:name"; + } + description + "This type is used to reference a system-provided + event stream."; + } + + typedef subscription-id { + type uint32; + description + "A type for subscription identifiers."; + } + + typedef transport { + type identityref { + base transport; + } + description + "Specifies the transport used to send notification messages + to a receiver."; + } + + /* + * GROUPINGS + */ + + grouping stream-filter-elements { + description + "This grouping defines the base for filters applied to event + streams."; + choice filter-spec { + description + "The content filter specification for this request."; + anydata stream-subtree-filter { + if-feature "subtree"; + description + "Event stream evaluation criteria encoded in the syntax of + a subtree filter as defined in RFC 6241, Section 6. + + The subtree filter is applied to the representation of + individual, delineated event records as contained in the + event stream. + + If the subtree filter returns a non-empty node set, the + filter matches the event record, and the event record is + included in the notification message sent to the + receivers."; + reference + "RFC 6241: Network Configuration Protocol (NETCONF), + Section 6"; + } + leaf stream-xpath-filter { + if-feature "xpath"; + type yang:xpath1.0; + description + "Event stream evaluation criteria encoded in the syntax of + an XPath 1.0 expression. + + The XPath expression is evaluated on the representation of + individual, delineated event records as contained in + the event stream. + + The result of the XPath expression is converted to a + boolean value using the standard XPath 1.0 rules. If the + boolean value is 'true', the filter matches the event + record, and the event record is included in the + notification message sent to the receivers. + + The expression is evaluated in the following XPath + context: + + o The set of namespace declarations is the set of + prefix and namespace pairs for all YANG modules + implemented by the server, where the prefix is the + YANG module name and the namespace is as defined by + the 'namespace' statement in the YANG module. + + If the leaf is encoded in XML, all namespace + declarations in scope on the 'stream-xpath-filter' + leaf element are added to the set of namespace + declarations. If a prefix found in the XML is + already present in the set of namespace + declarations, the namespace in the XML is used. + + o The set of variable bindings is empty. + + o The function library is comprised of the core + function library and the XPath functions defined in + Section 10 in RFC 7950. + + o The context node is the root node."; + reference + "XML Path Language (XPath) Version 1.0 + (https://www.w3.org/TR/1999/REC-xpath-19991116) + RFC 7950: The YANG 1.1 Data Modeling Language, + Section 10"; + } + } + } + + grouping update-qos { + description + "This grouping describes QoS information concerning a + subscription. This information is passed to lower layers + for transport prioritization and treatment."; + leaf dscp { + if-feature "dscp"; + type inet:dscp; + default "0"; + description + "The desired network transport priority level. This is the + priority set on notification messages encapsulating the + results of the subscription. This transport priority is + shared for all receivers of a given subscription."; + } + leaf weighting { + if-feature "qos"; + type uint8 { + range "0 .. 255"; + } + description + "Relative weighting for a subscription. Larger weights get + more resources. Allows an underlying transport layer to + perform informed load-balance allocations between various + subscriptions."; + reference + "RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2), + Section 5.3.2"; + } + leaf dependency { + if-feature "qos"; + type subscription-id; + description + "Provides the 'subscription-id' of a parent subscription. + The parent subscription has absolute precedence should + that parent have push updates ready to egress the publisher. + In other words, there should be no streaming of objects from + the current subscription if the parent has something ready + to push. + + If a dependency is asserted via configuration or via an RPC + but the referenced 'subscription-id' does not exist, the + dependency is silently discarded. If a referenced + subscription is deleted, this dependency is removed."; + reference + "RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2), + Section 5.3.1"; + } + } + + grouping subscription-policy-modifiable { + description + "This grouping describes all objects that may be changed + in a subscription."; + choice target { + mandatory true; + description + "Identifies the source of information against which a + subscription is being applied as well as specifics on the + subset of information desired from that source."; + case stream { + choice stream-filter { + description + "An event stream filter can be applied to a subscription. + That filter will either come referenced from a global + list or be provided in the subscription itself."; + case by-reference { + description + "Apply a filter that has been configured separately."; + leaf stream-filter-name { + type stream-filter-ref; + mandatory true; + description + "References an existing event stream filter that is + to be applied to an event stream for the + subscription."; + } + } + case within-subscription { + description + "A local definition allows a filter to have the same + lifecycle as the subscription."; + uses stream-filter-elements; + } + } + } + } + leaf stop-time { + type yang:date-and-time; + description + "Identifies a time after which notification messages for a + subscription should not be sent. If 'stop-time' is not + present, the notification messages will continue until the + subscription is terminated. If 'replay-start-time' exists, + 'stop-time' must be for a subsequent time. If + 'replay-start-time' doesn't exist, 'stop-time', when + established, must be for a future time."; + } + } + + grouping subscription-policy-dynamic { + description + "This grouping describes the only information concerning a + subscription that can be passed over the RPCs defined in this + data model."; + uses subscription-policy-modifiable { + augment "target/stream" { + description + "Adds additional objects that can be modified by an RPC."; + leaf stream { + type stream-ref { + require-instance false; + } + mandatory true; + description + "Indicates the event stream to be considered for + this subscription."; + } + leaf replay-start-time { + if-feature "replay"; + type yang:date-and-time; + config false; + description + "Used to trigger the 'replay' feature for a dynamic + subscription, where event records that are selected + need to be at or after the specified starting time. If + 'replay-start-time' is not present, this is not a replay + subscription and event record push should start + immediately. It is never valid to specify start times + that are later than or equal to the current time."; + } + } + } + uses update-qos; + } + + grouping subscription-policy { + description + "This grouping describes the full set of policy information + concerning both dynamic and configured subscriptions, with the + exclusion of both receivers and networking information + specific to the publisher, such as what interface should be + used to transmit notification messages."; + uses subscription-policy-dynamic; + leaf transport { + if-feature "configured"; + type transport; + description + "For a configured subscription, this leaf specifies the + transport used to deliver messages destined for all + receivers of that subscription."; + } + leaf encoding { + when 'not(../transport) or derived-from(../transport, + "sn:configurable-encoding")'; + type encoding; + description + "The type of encoding for notification messages. For a + dynamic subscription, if not included as part of an + 'establish-subscription' RPC, the encoding will be populated + with the encoding used by that RPC. For a configured + subscription, if not explicitly configured, the encoding + will be the default encoding for an underlying transport."; + } + leaf purpose { + if-feature "configured"; + type string; + description + "Open text allowing a configuring entity to embed the + originator or other specifics of this subscription."; + } + } + + /* + * RPCs + */ + + rpc establish-subscription { + description + "This RPC allows a subscriber to create (and possibly + negotiate) a subscription on its own behalf. If successful, + the subscription remains in effect for the duration of the + subscriber's association with the publisher or until the + subscription is terminated. If an error occurs or the + publisher cannot meet the terms of a subscription, an RPC + error is returned, and the subscription is not created. + In that case, the RPC reply's 'error-info' MAY include + suggested parameter settings that would have a higher + likelihood of succeeding in a subsequent + 'establish-subscription' request."; + input { + uses subscription-policy-dynamic; + leaf encoding { + type encoding; + description + "The type of encoding for the subscribed data. If not + included as part of the RPC, the encoding MUST be set by + the publisher to be the encoding used by this RPC."; + } + } + output { + leaf id { + type subscription-id; + mandatory true; + description + "Identifier used for this subscription."; + } + leaf replay-start-time-revision { + if-feature "replay"; + type yang:date-and-time; + description + "If a replay has been requested, this object represents + the earliest time covered by the event buffer for the + requested event stream. The value of this object is the + 'replay-log-aged-time' if it exists. Otherwise, it is + the 'replay-log-creation-time'. All buffered event + records after this time will be replayed to a receiver. + This object will only be sent if the starting time has + been revised to be later than the time requested by the + subscriber."; + } + } + } + + rc:yang-data establish-subscription-stream-error-info { + container establish-subscription-stream-error-info { + description + "If any 'establish-subscription' RPC parameters are + unsupportable against the event stream, a subscription + is not created and the RPC error response MUST indicate the + reason why the subscription failed to be created. This + yang-data MAY be inserted as structured data in a + subscription's RPC error response to indicate the reason for + the failure. This yang-data MUST be inserted if hints are + to be provided back to the subscriber."; + leaf reason { + type identityref { + base establish-subscription-error; + } + description + "Indicates the reason why the subscription has failed to + be created to a targeted event stream."; + } + leaf filter-failure-hint { + type string; + description + "Information describing where and/or why a provided + filter was unsupportable for a subscription. The + syntax and semantics of this hint are + implementation specific."; + } + } + } + + rpc modify-subscription { + description + "This RPC allows a subscriber to modify a dynamic + subscription's parameters. If successful, the changed + subscription parameters remain in effect for the duration of + the subscription, until the subscription is again modified, or + until the subscription is terminated. In the case of an error + or an inability to meet the modified parameters, the + subscription is not modified and the original subscription + parameters remain in effect. In that case, the RPC error MAY + include 'error-info' suggested parameter hints that would have + a high likelihood of succeeding in a subsequent + 'modify-subscription' request. A successful + 'modify-subscription' will return a suspended subscription to + the 'active' state."; + input { + leaf id { + type subscription-id; + mandatory true; + description + "Identifier to use for this subscription."; + } + uses subscription-policy-modifiable; + } + } + + rc:yang-data modify-subscription-stream-error-info { + container modify-subscription-stream-error-info { + description + "This yang-data MAY be provided as part of a subscription's + RPC error response when there is a failure of a + 'modify-subscription' RPC that has been made against an + event stream. This yang-data MUST be used if hints are to + be provided back to the subscriber."; + leaf reason { + type identityref { + base modify-subscription-error; + } + description + "Information in a 'modify-subscription' RPC error response + that indicates the reason why the subscription to an event + stream has failed to be modified."; + } + leaf filter-failure-hint { + type string; + description + "Information describing where and/or why a provided + filter was unsupportable for a subscription. The syntax + and semantics of this hint are + implementation specific."; + } + } + } + + rpc delete-subscription { + description + "This RPC allows a subscriber to delete a subscription that + was previously created by that same subscriber using the + 'establish-subscription' RPC. + + If an error occurs, the server replies with an 'rpc-error' + where the 'error-info' field MAY contain a + 'delete-subscription-error-info' structure."; + input { + leaf id { + type subscription-id; + mandatory true; + description + "Identifier of the subscription that is to be deleted. + Only subscriptions that were created using + 'establish-subscription' from the same origin as this RPC + can be deleted via this RPC."; + } + } + } + + rpc kill-subscription { + nacm:default-deny-all; + description + "This RPC allows an operator to delete a dynamic subscription + without restrictions on the originating subscriber or + underlying transport session. + + If an error occurs, the server replies with an 'rpc-error' + where the 'error-info' field MAY contain a + 'delete-subscription-error-info' structure."; + input { + leaf id { + type subscription-id; + mandatory true; + description + "Identifier of the subscription that is to be deleted. + Only subscriptions that were created using + 'establish-subscription' can be deleted via this RPC."; + } + } + } + + rc:yang-data delete-subscription-error-info { + container delete-subscription-error-info { + description + "If a 'delete-subscription' RPC or a 'kill-subscription' RPC + fails, the subscription is not deleted and the RPC error + response MUST indicate the reason for this failure. This + yang-data MAY be inserted as structured data in a + subscription's RPC error response to indicate the reason + for the failure."; + leaf reason { + type identityref { + base delete-subscription-error; + } + mandatory true; + description + "Indicates the reason why the subscription has failed to be + deleted."; + } + } + } + + /* + * NOTIFICATIONS + */ + + notification replay-completed { + sn:subscription-state-notification; + if-feature "replay"; + description + "This notification is sent to indicate that all of the replay + notifications have been sent."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the affected subscription."; + } + } + + notification subscription-completed { + sn:subscription-state-notification; + if-feature "configured"; + description + "This notification is sent to indicate that a subscription has + finished passing event records, as the 'stop-time' has been + reached."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the gracefully completed subscription."; + } + } + + notification subscription-modified { + sn:subscription-state-notification; + description + "This notification indicates that a subscription has been + modified. Notification messages sent from this point on will + conform to the modified terms of the subscription. For + completeness, this subscription state change notification + includes both modified and unmodified aspects of a + subscription."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the affected subscription."; + } + uses subscription-policy { + refine "target/stream/stream-filter/within-subscription" { + description + "Filter applied to the subscription. If the + 'stream-filter-name' is populated, the filter in the + subscription came from the 'filters' container. + Otherwise, it is populated in-line as part of the + subscription."; + } + } + } + + notification subscription-resumed { + sn:subscription-state-notification; + description + "This notification indicates that a subscription that had + previously been suspended has resumed. Notifications will + once again be sent. In addition, a 'subscription-resumed' + indicates that no modification of parameters has occurred + since the last time event records have been sent."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the affected subscription."; + } + } + + notification subscription-started { + sn:subscription-state-notification; + if-feature "configured"; + description + "This notification indicates that a subscription has started + and notifications will now be sent."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the affected subscription."; + } + uses subscription-policy { + refine "target/stream/replay-start-time" { + description + "Indicates the time that a replay is using for the + streaming of buffered event records. This will be + populated with the most recent of the following: + the event time of the previous event record sent to a + receiver, the 'replay-log-creation-time', the + 'replay-log-aged-time', or the most recent publisher + boot time."; + } + refine "target/stream/stream-filter/within-subscription" { + description + "Filter applied to the subscription. If the + 'stream-filter-name' is populated, the filter in the + subscription came from the 'filters' container. + Otherwise, it is populated in-line as part of the + subscription."; + } + augment "target/stream" { + description + "This augmentation adds additional parameters specific to a + 'subscription-started' notification."; + leaf replay-previous-event-time { + when '../replay-start-time'; + if-feature "replay"; + type yang:date-and-time; + description + "If there is at least one event in the replay buffer + prior to 'replay-start-time', this gives the time of + the event generated immediately prior to the + 'replay-start-time'. + + If a receiver previously received event records for + this configured subscription, it can compare this time + to the last event record previously received. If the + two are not the same (perhaps due to a reboot), then a + dynamic replay can be initiated to acquire any missing + event records."; + } + } + } + } + + notification subscription-suspended { + sn:subscription-state-notification; + description + "This notification indicates that a suspension of the + subscription by the publisher has occurred. No further + notifications will be sent until the subscription resumes. + This notification shall only be sent to receivers of a + subscription; it does not constitute a general-purpose + notification."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the affected subscription."; + } + leaf reason { + type identityref { + base subscription-suspended-reason; + } + mandatory true; + description + "Identifies the condition that resulted in the suspension."; + } + } + + notification subscription-terminated { + sn:subscription-state-notification; + description + "This notification indicates that a subscription has been + terminated."; + leaf id { + type subscription-id; + mandatory true; + description + "This references the affected subscription."; + } + leaf reason { + type identityref { + base subscription-terminated-reason; + } + mandatory true; + description + "Identifies the condition that resulted in the termination."; + } + } + + /* + * DATA NODES + */ + + container streams { + config false; + description + "Contains information on the built-in event streams provided by + the publisher."; + list stream { + key "name"; + description + "Identifies the built-in event streams that are supported by + the publisher."; + leaf name { + type string; + description + "A handle for a system-provided event stream made up of a + sequential set of event records, each of which is + characterized by its own domain and semantics."; + } + leaf description { + type string; + description + "A description of the event stream, including such + information as the type of event records that are + available in this event stream."; + } + leaf replay-support { + if-feature "replay"; + type empty; + description + "Indicates that event record replay is available on this + event stream."; + } + leaf replay-log-creation-time { + when '../replay-support'; + if-feature "replay"; + type yang:date-and-time; + mandatory true; + description + "The timestamp of the creation of the log used to support + the replay function on this event stream. This time + might be earlier than the earliest available information + contained in the log. This object is updated if the log + resets for some reason."; + } + leaf replay-log-aged-time { + when '../replay-support'; + if-feature "replay"; + type yang:date-and-time; + description + "The timestamp associated with the last event record that + has been aged out of the log. This timestamp identifies + how far back in history this replay log extends, if it + doesn't extend back to the 'replay-log-creation-time'. + This object MUST be present if replay is supported and any + event records have been aged out of the log."; + } + } + } + container filters { + description + "Contains a list of configurable filters that can be applied to + subscriptions. This facilitates the reuse of complex filters + once defined."; + list stream-filter { + key "name"; + description + "A list of preconfigured filters that can be applied to + subscriptions."; + leaf name { + type string; + description + "A name to differentiate between filters."; + } + uses stream-filter-elements; + } + } + container subscriptions { + description + "Contains the list of currently active subscriptions, i.e., + subscriptions that are currently in effect, used for + subscription management and monitoring purposes. This + includes subscriptions that have been set up via + RPC primitives as well as subscriptions that have been + established via configuration."; + list subscription { + key "id"; + description + "The identity and specific parameters of a subscription. + Subscriptions in this list can be created using a control + channel or RPC or can be established through configuration. + + If the 'kill-subscription' RPC or configuration operations + are used to delete a subscription, a + 'subscription-terminated' message is sent to any active or + suspended receivers."; + leaf id { + type subscription-id; + description + "Identifier of a subscription; unique in a given + publisher."; + } + uses subscription-policy { + refine "target/stream/stream" { + description + "Indicates the event stream to be considered for this + subscription. If an event stream has been removed + and can no longer be referenced by an active + subscription, send a 'subscription-terminated' + notification with 'stream-unavailable' as the reason. + If a configured subscription refers to a nonexistent + event stream, move that subscription to the + 'invalid' state."; + } + refine "transport" { + description + "For a configured subscription, this leaf specifies the + transport used to deliver messages destined for all + receivers of that subscription. This object is + mandatory for subscriptions in the configuration + datastore. This object (1) is not mandatory for dynamic + subscriptions in the operational state datastore and + (2) should not be present for other types of dynamic + subscriptions."; + } + augment "target/stream" { + description + "Enables objects to be added to a configured stream + subscription."; + leaf configured-replay { + if-feature "configured"; + if-feature "replay"; + type empty; + description + "The presence of this leaf indicates that replay for + the configured subscription should start at the + earliest time in the event log or at the publisher + boot time, whichever is later."; + } + } + } + choice notification-message-origin { + if-feature "configured"; + description + "Identifies the egress interface on the publisher + from which notification messages are to be sent."; + case interface-originated { + description + "When notification messages are to egress a specific, + designated interface on the publisher."; + leaf source-interface { + if-feature "interface-designation"; + type if:interface-ref; + description + "References the interface for notification messages."; + } + } + case address-originated { + description + "When notification messages are to depart from a + publisher using a specific originating address and/or + routing context information."; + leaf source-vrf { + if-feature "supports-vrf"; + type leafref { + path "/ni:network-instances/ni:network-instance/ni:name"; + } + description + "VRF from which notification messages should egress a + publisher."; + } + leaf source-address { + type inet:ip-address-no-zone; + description + "The source address for the notification messages. + If a source VRF exists but this object doesn't, a + publisher's default address for that VRF must + be used."; + } + } + } + leaf configured-subscription-state { + if-feature "configured"; + type enumeration { + enum valid { + value 1; + description + "The subscription is supportable with its current + parameters."; + } + enum invalid { + value 2; + description + "The subscription as a whole is unsupportable with its + current parameters."; + } + enum concluded { + value 3; + description + "A subscription is inactive, as it has hit a + stop time. It no longer has receivers in the + 'active' or 'suspended' state, but the subscription + has not yet been removed from configuration."; + } + } + config false; + description + "The presence of this leaf indicates that the subscription + originated from configuration, not through a control + channel or RPC. The value indicates the state of the + subscription as established by the publisher."; + } + container receivers { + description + "Set of receivers in a subscription."; + list receiver { + key "name"; + min-elements 1; + description + "A host intended as a recipient for the notification + messages of a subscription. For configured + subscriptions, transport-specific network parameters + (or a leafref to those parameters) may be augmented to a + specific receiver in this list."; + leaf name { + type string; + description + "Identifies a unique receiver for a subscription."; + } + leaf sent-event-records { + type yang:zero-based-counter64; + config false; + description + "The number of event records sent to the receiver. The + count is initialized when a dynamic subscription is + established or when a configured receiver + transitions to the 'valid' state."; + } + leaf excluded-event-records { + type yang:zero-based-counter64; + config false; + description + "The number of event records explicitly removed via + either an event stream filter or an access control + filter so that they are not passed to a receiver. + This count is set to zero each time + 'sent-event-records' is initialized."; + } + leaf state { + type enumeration { + enum active { + value 1; + description + "The receiver is currently being sent any + applicable notification messages for the + subscription."; + } + enum suspended { + value 2; + description + "The receiver state is 'suspended', so the + publisher is currently unable to provide + notification messages for the subscription."; + } + enum connecting { + value 3; + if-feature "configured"; + description + "A subscription has been configured, but a + 'subscription-started' subscription state change + notification needs to be successfully received + before notification messages are sent. + + If the 'reset' action is invoked for a receiver of + an active configured subscription, the state + must be moved to 'connecting'."; + } + enum disconnected { + value 4; + if-feature "configured"; + description + "A subscription has failed to send a + 'subscription-started' state change to the + receiver. Additional connection attempts are not + currently being made."; + } + } + config false; + mandatory true; + description + "Specifies the state of a subscription from the + perspective of a particular receiver. With this + information, it is possible to determine whether a + publisher is currently generating notification + messages intended for that receiver."; + } + action reset { + if-feature "configured"; + description + "Allows the reset of this configured subscription's + receiver to the 'connecting' state. This enables the + connection process to be reinitiated."; + output { + leaf time { + type yang:date-and-time; + mandatory true; + description + "Time at which a publisher returned the receiver to + the 'connecting' state."; + } + } + } + } + } + } + } +}