diff --git a/modules/ietf-notification-capabilities@2022-02-17.yang b/modules/ietf-notification-capabilities@2022-02-17.yang
new file mode 100644
index 00000000..c9385b25
--- /dev/null
+++ b/modules/ietf-notification-capabilities@2022-02-17.yang
@@ -0,0 +1,262 @@
+module ietf-notification-capabilities {
+ yang-version 1.1;
+ namespace
+ "urn:ietf:params:xml:ns:yang:ietf-notification-capabilities";
+ prefix notc;
+
+ import ietf-yang-push {
+ prefix yp;
+ description
+ "This module requires ietf-yang-push to be implemented.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for
+ Datastore Updates";
+ }
+ import ietf-system-capabilities {
+ prefix sysc;
+ description
+ "This module requires ietf-system-capabilities to be
+ implemented.";
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for Systems
+ and Datastore Update Notifications";
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web:
+ WG List:
+
+ Editor: Balazs Lengyel
+ ";
+ description
+ "This module specifies publisher capabilities related to
+ YANG-Push (RFC 8641).
+
+ The module contains:
+
+ - a specification of the data nodes that support 'on-change' or
+ 'periodic' notifications.
+
+ - capabilities related to the throughput of notification data
+ that the publisher can support. (Note that for a specific
+ subscription, the publisher MAY allow only longer periods
+ or smaller updates depending on, e.g., actual load conditions.)
+
+ Capability values can be specified at the system/publisher
+ level, at the datastore level, or for specific data nodes of
+ a specific datastore (and their contained subtrees), as defined
+ in the ietf-system-capabilities module.
+
+ If different data nodes covered by a single subscription
+ have different values for a specific capability, then using
+ values that are only acceptable for some of these data nodes,
+ but not for others, may result in the rejection of the
+ subscription.
+
+ 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) 2022 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 Revised 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 9196
+ (https://www.rfc-editor.org/info/rfc9196); see the RFC itself
+ for full legal notices.";
+
+ revision 2022-02-17 {
+ description
+ "Initial version";
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for Systems
+ and Datastore Update Notifications";
+ }
+
+ grouping subscription-capabilities {
+ description
+ "Capabilities related to YANG-Push subscriptions
+ and notifications";
+ container subscription-capabilities {
+ description
+ "Capabilities related to YANG-Push subscriptions
+ and notifications";
+ typedef notification-support {
+ type bits {
+ bit config-changes {
+ description
+ "The publisher is capable of sending
+ notifications for 'config true' nodes for the
+ relevant scope and subscription type.";
+ }
+ bit state-changes {
+ description
+ "The publisher is capable of sending
+ notifications for 'config false' nodes for the
+ relevant scope and subscription type.";
+ }
+ }
+ description
+ "Type for defining whether 'on-change' or
+ 'periodic' notifications are supported for all data nodes,
+ 'config false' data nodes, 'config true' data nodes, or
+ no data nodes.
+
+ The bits config-changes or state-changes have no effect
+ when they are set for a datastore or for a set of nodes
+ that does not contain nodes with the indicated config
+ value. In those cases, the effect is the same as if no
+ support was declared. One example of this is indicating
+ support for state-changes for a candidate datastore that
+ has no effect.";
+ }
+
+ leaf max-nodes-per-update {
+ type uint32 {
+ range "1..max";
+ }
+ description
+ "Maximum number of data nodes that can be sent
+ in an update. The publisher MAY support more data nodes
+ but SHOULD support at least this number.
+
+ May be used to avoid the 'update-too-big' error
+ during subscription.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for
+ Datastore Updates, the 'update-too-big' error/identity";
+ }
+ leaf periodic-notifications-supported {
+ type notification-support;
+ description
+ "Specifies whether the publisher is capable of
+ sending 'periodic' notifications for the selected
+ data nodes, including any subtrees that may exist
+ below them.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for
+ Datastore Updates, 'periodic' subscription concept";
+ }
+ choice update-period {
+ description
+ "Supported update period value or values for
+ 'periodic' subscriptions.";
+ leaf minimum-update-period {
+ type uint32;
+ units "centiseconds";
+ description
+ "Indicates the minimal update period that is
+ supported for a 'periodic' subscription.
+
+ A subscription request to the selected data nodes with
+ a smaller period than what this leaf specifies is
+ likely to result in a 'period-unsupported' error.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for
+ Datastore Updates, the period leaf in the ietf-yang-push
+ YANG module";
+ }
+ leaf-list supported-update-period {
+ type uint32;
+ units "centiseconds";
+ description
+ "Supported update period values for a 'periodic'
+ subscription.
+
+ A subscription request to the selected data nodes with a
+ period not included in the leaf-list will result in a
+ 'period-unsupported' error.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for
+ Datastore Updates, the period leaf in the ietf-yang-push
+ YANG module";
+ }
+ }
+ leaf on-change-supported {
+ if-feature "yp:on-change";
+ type notification-support;
+ description
+ "Specifies whether the publisher is capable of
+ sending 'on-change' notifications for the selected
+ data nodes and the subtree below them.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for Datastore
+ Updates, on-change concept";
+ }
+ leaf minimum-dampening-period {
+ if-feature "yp:on-change";
+ type uint32;
+ units "centiseconds";
+ description
+ "The minimum dampening period supported for 'on-change'
+ subscriptions for the selected data nodes.
+
+ If this value is present and greater than zero,
+ that implies dampening is mandatory.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for
+ Datastore Updates, the dampening-period leaf in the
+ ietf-yang-push YANG module";
+ }
+ leaf-list supported-excluded-change-type {
+ if-feature "yp:on-change";
+ type union {
+ type enumeration {
+ enum none {
+ value -2;
+ description
+ "None of the change types can be excluded.";
+ }
+ enum all {
+ value -1;
+ description
+ "Any combination of change types can be excluded.";
+ }
+ }
+ type yp:change-type;
+ }
+ description
+ "The change types that can be excluded in
+ YANG-Push subscriptions for the selected data nodes.";
+ reference
+ "RFC 8641: Subscription to YANG Notifications for Datastore
+ Updates, the change-type typedef in the ietf-yang-push
+ YANG module";
+ }
+ }
+ }
+
+ augment "/sysc:system-capabilities" {
+ description
+ "Add system level capabilities";
+ uses subscription-capabilities {
+ refine
+ "subscription-capabilities/supported-excluded-change-type" {
+ default "none";
+ }
+ }
+ }
+
+ augment "/sysc:system-capabilities/sysc:datastore-capabilities"
+ + "/sysc:per-node-capabilities" {
+ description
+ "Add datastore and node-level capabilities";
+ uses subscription-capabilities {
+ refine
+ "subscription-capabilities/supported-excluded-change-type" {
+ default "none";
+ }
+ }
+ }
+}
diff --git a/modules/ietf-system-capabilities@2022-02-17.yang b/modules/ietf-system-capabilities@2022-02-17.yang
new file mode 100644
index 00000000..55f959d8
--- /dev/null
+++ b/modules/ietf-system-capabilities@2022-02-17.yang
@@ -0,0 +1,170 @@
+module ietf-system-capabilities {
+ yang-version 1.1;
+ namespace "urn:ietf:params:xml:ns:yang:ietf-system-capabilities";
+ prefix sysc;
+
+ import ietf-netconf-acm {
+ prefix nacm;
+ reference
+ "RFC 8341: Network Configuration Access Control Model";
+ }
+ import ietf-yang-library {
+ prefix yanglib;
+ description
+ "This module requires ietf-yang-library to be implemented.
+ Revision 2019-01-04 or a revision derived from it
+ is REQUIRED.";
+ reference
+ "RFC8525: YANG Library";
+ }
+
+ organization
+ "IETF NETCONF (Network Configuration) Working Group";
+ contact
+ "WG Web:
+ WG List:
+
+ Editor: Balazs Lengyel
+ ";
+ description
+ "This module specifies a structure to specify system
+ capabilities for a server or a publisher. System capabilities
+ may include capabilities of a NETCONF or RESTCONF server or a
+ notification publisher.
+
+ This module does not contain any specific capabilities; it only
+ provides a structure where containers containing the actual
+ capabilities are augmented in.
+
+ Capability values can be specified at the system level, at the
+ datastore level (by selecting all nodes in the datastore), or
+ for specific data nodes of a specific datastore (and their
+ contained subtrees).
+ Capability values specified for a specific datastore or
+ node-set override values specified on the system/publisher
+ level.
+
+ The same grouping MUST be used to define hierarchical
+ capabilities supported both at the system level and at the
+ datastore/data-node level.
+
+ To find a capability value for a specific data node in a
+ specific datastore, the user SHALL:
+
+ 1) search for a datastore-capabilities list entry for
+ the specific datastore. When stating a specific capability, the
+ relative path for any specific capability must be the same
+ under the system-capabilities container and under the
+ per-node-capabilities list.
+
+ 2) If the datastore entry is found within that entry, process
+ all per-node-capabilities entries in the order they appear in
+ the list. The first entry that specifies the specific
+ capability and has a node-selector selecting the specific data
+ node defines the capability value.
+
+ 3) If the capability value is not found above and the specific
+ capability is specified under the system-capabilities container
+ (outside the datastore-capabilities list), this value shall be
+ used.
+
+ 4) If no values are found in the previous steps, the
+ system/publisher is not capable of providing a value. Possible
+ reasons are that it is unknown, the capability is changing for
+ some reason, there is no specified limit, etc. In this case,
+ the system's behavior is unspecified.
+
+ 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) 2022 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 Revised 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 9196
+ (https://www.rfc-editor.org/info/rfc9196); see the RFC itself
+ for full legal notices.";
+
+ revision 2022-02-17 {
+ description
+ "Initial version";
+ reference
+ "RFC 9196: YANG Modules Describing Capabilities for Systems
+ and Datastore Update Notifications";
+ }
+
+ container system-capabilities {
+ config false;
+ description
+ "System capabilities.
+ Capability values specified here at the system level
+ are valid for all datastores and are used when the
+ capability is not specified at the datastore level
+ or for specific data nodes.";
+ /*
+ * "Augmentation point for system-level capabilities."
+ */
+ list datastore-capabilities {
+ key "datastore";
+ description
+ "Capabilities values per datastore.
+
+ For non-NMDA servers/publishers, 'config false' data is
+ considered as if it were part of the running datastore.";
+ leaf datastore {
+ type leafref {
+ path
+ "/yanglib:yang-library/yanglib:datastore/yanglib:name";
+ }
+ description
+ "The datastore for which capabilities are defined.
+ Only one specific datastore can be specified,
+ e.g., ds:conventional must not be used, as it
+ represents a set of configuration datastores.";
+ }
+ list per-node-capabilities {
+ description
+ "Each list entry specifies capabilities for the selected
+ data nodes. The same capabilities apply to the data nodes
+ in the subtree below the selected nodes.
+
+ The system SHALL order the entries according to their
+ precedence. The order of the entries MUST NOT change
+ unless the underlying capabilities also change.
+
+ Note that the longest patch matching can be achieved
+ by ordering more specific matches before less
+ specific ones.";
+ choice node-selection {
+ description
+ "A method to select some or all nodes within a
+ datastore.";
+ leaf node-selector {
+ type nacm:node-instance-identifier;
+ description
+ "Selects the data nodes for which capabilities are
+ specified. The special value '/' denotes all data
+ nodes in the datastore, consistent with the path
+ leaf node on page 41 of [RFC8341].";
+ reference
+ "RFC 8341: Network Configuration Access Control Model";
+ }
+ }
+ /*
+ * "Augmentation point for datastore- or data-node-level
+ * capabilities."
+ */
+ }
+ }
+ }
+}
diff --git a/scripts/common.sh b/scripts/common.sh
index 7a5678e8..e56a15d3 100644
--- a/scripts/common.sh
+++ b/scripts/common.sh
@@ -13,6 +13,8 @@ NP2_MODULES=(
"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"
"netopeer-notifications@2025-01-15.yang"
+"ietf-system-capabilities@2022-02-17.yang"
+"ietf-notification-capabilities@2022-02-17.yang"
)
LN2_MODULES=(
diff --git a/src/common.c b/src/common.c
index 08938588..86e33ce7 100644
--- a/src/common.c
+++ b/src/common.c
@@ -1824,3 +1824,22 @@ np_reply_err_in_use(const struct ly_ctx *ly_ctx, const char *msg, uint32_t sr_id
}
return nc_server_reply_err(e);
}
+
+const char *
+sub_ntf_ds2ident(sr_datastore_t ds)
+{
+ switch (ds) {
+ case SR_DS_STARTUP:
+ return "ietf-datastores:startup";
+ case SR_DS_RUNNING:
+ return "ietf-datastores:running";
+ case SR_DS_CANDIDATE:
+ return "ietf-datastores:candidate";
+ case SR_DS_OPERATIONAL:
+ return "ietf-datastores:operational";
+ case SR_DS_FACTORY_DEFAULT:
+ return "ietf-factory-default:factory-default";
+ }
+
+ return NULL;
+}
diff --git a/src/common.h b/src/common.h
index 7b70c40d..b730845b 100644
--- a/src/common.h
+++ b/src/common.h
@@ -440,4 +440,13 @@ struct nc_server_reply *np_reply_err_bad_elem(const struct ly_ctx *ly_ctx, const
*/
struct nc_server_reply *np_reply_err_in_use(const struct ly_ctx *ly_ctx, const char *msg, uint32_t sr_id);
+/**
+ * @brief Transform a datastore into a string identity.
+ *
+ * @param[in] str Identity.
+ * @param[out] ds Datastore.
+ * @return Sysrepo error value.
+ */
+const char *sub_ntf_ds2ident(sr_datastore_t ds);
+
#endif /* NP2SRV_COMMON_H_ */
diff --git a/src/main.c b/src/main.c
index 952b460b..fe03c2b0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -57,6 +57,21 @@
/** @brief flag for main loop */
ATOMIC_T loop_continue;
+struct notification_capabilities {
+ const char *name;
+ const char *value;
+};
+
+/* Defines https://datatracker.ietf.org/doc/html/rfc9196#section-5 */
+static const struct notification_capabilities notif_capas[] = {
+ {"max-nodes-per-update", "4294967295"},
+ {"periodic-notifications-supported", "config-changes state-changes"},
+ {"minimum-update-period", "0"},
+ {"on-change-supported", "config-changes state-changes"},
+ {"minimum-dampening-period", "0"},
+ {"supported-excluded-change-type", "all"}
+};
+
static void *worker_thread(void *arg);
/**
@@ -452,6 +467,124 @@ np2srv_sm_oper_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char
return rc;
}
+/**
+ * @brief Add subscription capabilities to a node
+ * @param[in,out] node is a part of ietf-system-capabilities.
+ * @param[in] ly_ctx a libyang context.
+ */
+static int
+np2srv_add_subscription_capabilities(struct lyd_node *node, const struct ly_ctx *ly_ctx)
+{
+ const struct lys_module *notc_mod;
+ struct lyd_node *subs_capas;
+ int rc = SR_ERR_OK;
+ uint32_t c;
+
+ notc_mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-notification-capabilities");
+ if (!notc_mod) {
+ ERR("Module \"ietf-notification-capabilities\" not implemented in sysrepo.");
+ rc = -1;
+ goto cleanup;
+ }
+
+ /* subscription-capabilities */
+ if (lyd_new_path(node, ly_ctx, "ietf-notification-capabilities:subscription-capabilities",
+ NULL, 0, &subs_capas)) {
+ ERR("Failed to create subscription-capabilities.");
+ rc = -1;
+ goto cleanup;
+ }
+
+ /* notification capabilities */
+ for (c = 0; c < sizeof(notif_capas) / sizeof(*notif_capas); c++) {
+ if (lyd_new_term(subs_capas, notc_mod, notif_capas[c].name, notif_capas[c].value, 0,
+ NULL)) {
+ ERR("Failed to create %s.", notif_capas[c].name);
+ rc = -1;
+ goto cleanup;
+ }
+ }
+cleanup:
+ return rc;
+}
+
+/**
+ * @brief SR operational get callback for system-capabilities data.
+ */
+static int
+np2srv_capabilities_oper_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)
+{
+ struct lyd_node *sys_capas, *datastore_capas, *per_node_capas;
+ const struct ly_ctx *ly_ctx;
+ int rc = SR_ERR_OK;
+ uint32_t ds;
+
+ (void)sub_id;
+ (void)module_name;
+ (void)path;
+ (void)request_xpath;
+ (void)request_id;
+ (void)private_data;
+
+ /* context is locked while the callback is executing */
+ ly_ctx = sr_session_acquire_context(session);
+ if (!ly_ctx) {
+ ERR("Failed to acquire sysrepo context.");
+ rc = -1;
+ goto cleanup;
+ }
+
+ if (lyd_new_path(NULL, ly_ctx, "/ietf-system-capabilities:system-capabilities",
+ NULL, 0, &sys_capas)) {
+ ERR("Failed to create system-capabilities.");
+ rc = -1;
+ goto cleanup;
+ }
+
+ /* datastore-capabilities */
+ for (ds = 0; ds < SR_DS_COUNT; ds++) {
+ if (lyd_new_list(sys_capas, NULL, "datastore-capabilities",
+ 0, &datastore_capas, sub_ntf_ds2ident(ds))) {
+ ERR("Failed to create datastore-capabilities.");
+ rc = -1;
+ goto cleanup;
+ }
+
+ if (lyd_new_list(datastore_capas, NULL, "per-node-capabilities",
+ 0, &per_node_capas, '/')) {
+ ERR("Failed to create per-node-capabilities.");
+ rc = -1;
+ goto cleanup;
+ }
+
+ /* per datastore capabilities */
+ if (np2srv_add_subscription_capabilities(per_node_capas, ly_ctx)) {
+ ERR("Failed to add per node subscription-capabilities.");
+ rc = -1;
+ goto cleanup;
+ }
+ }
+
+ /* global capabilities */
+ if (np2srv_add_subscription_capabilities(sys_capas, ly_ctx)) {
+ ERR("Failed to add global subscription-capabilities.");
+ rc = -1;
+ goto cleanup;
+ }
+
+cleanup:
+ if (rc) {
+ lyd_free_tree(sys_capas);
+ } else {
+ *parent = sys_capas;
+ }
+
+ sr_session_release_context(session);
+ return rc;
+}
+
#ifdef NC_ENABLED_SSH_TLS
/**
@@ -871,6 +1004,15 @@ server_data_subscribe(void)
mod_name = "nc-notifications";
SR_OPER_SUBSCR(mod_name, "/nc-notifications:netconf", np2srv_nc_ntf_oper_cb);
+ mod_name = "ietf-system-capabilities";
+ rc = sr_oper_get_subscribe(np2srv.sr_sess, mod_name,
+ "/ietf-system-capabilities:system-capabilities", np2srv_capabilities_oper_cb, NULL,
+ SR_SUBSCR_OPER_MERGE, &np2srv.sr_data_sub);
+ if (rc != SR_ERR_OK) {
+ ERR("Subscribing for providing \"%s\" state data failed (%s).", mod_name, sr_strerror(rc));
+ goto error;
+ }
+
#ifdef NC_ENABLED_SSH_TLS
/* set callbacks for supported algorithms oper data */
mod_name = "iana-ssh-public-key-algs";
diff --git a/src/netconf_subscribed_notifications.c b/src/netconf_subscribed_notifications.c
index 195f69aa..3b0a8814 100644
--- a/src/netconf_subscribed_notifications.c
+++ b/src/netconf_subscribed_notifications.c
@@ -811,32 +811,6 @@ sub_ntf_append_params_filter(struct lyd_node *parent, const struct np2srv_sub_nt
return rc;
}
-/**
- * @brief Transform a datastore into a string identity.
- *
- * @param[in] str Identity.
- * @param[out] ds Datastore.
- * @return Sysrepo error value.
- */
-static const char *
-sub_ntf_ds2ident(sr_datastore_t ds)
-{
- switch (ds) {
- case SR_DS_STARTUP:
- return "ietf-datastores:startup";
- case SR_DS_RUNNING:
- return "ietf-datastores:running";
- case SR_DS_CANDIDATE:
- return "ietf-datastores:candidate";
- case SR_DS_OPERATIONAL:
- return "ietf-datastores:operational";
- case SR_DS_FACTORY_DEFAULT:
- return "ietf-factory-default:factory-default";
- }
-
- return NULL;
-}
-
/**
* @brief Transform yang-push operation into string.
*