diff --git a/CMakeLists.txt b/CMakeLists.txt index 096a67de7..59a0e972f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,13 +61,13 @@ set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # set version of the project set(LIBYANG_MAJOR_VERSION 6) set(LIBYANG_MINOR_VERSION 4) -set(LIBYANG_MICRO_VERSION 20) +set(LIBYANG_MICRO_VERSION 21) set(LIBYANG_VERSION ${LIBYANG_MAJOR_VERSION}.${LIBYANG_MINOR_VERSION}.${LIBYANG_MICRO_VERSION}) # set version of the library set(LIBYANG_MAJOR_SOVERSION 5) set(LIBYANG_MINOR_SOVERSION 11) -set(LIBYANG_MICRO_SOVERSION 17) +set(LIBYANG_MICRO_SOVERSION 18) set(LIBYANG_SOVERSION_FULL ${LIBYANG_MAJOR_SOVERSION}.${LIBYANG_MINOR_SOVERSION}.${LIBYANG_MICRO_SOVERSION}) set(LIBYANG_SOVERSION ${LIBYANG_MAJOR_SOVERSION}) diff --git a/src/parser_cbor.c b/src/parser_cbor.c index 1d3e1dd7d..ba810dcd5 100644 --- a/src/parser_cbor.c +++ b/src/parser_cbor.c @@ -1210,8 +1210,17 @@ lydcbor_parse_any(struct lyd_cbor_ctx *lydctx, const struct lysc_node *snode, st LY_CHECK_ERR_GOTO(r, rc = r, cleanup); /* parse data tree with correct options */ - lydctx->parse_opts &= ~LYD_PARSE_STRICT; - lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); + if (lydctx->parse_opts & LYD_PARSE_ANYDATA_STRICT) { + /* explicit strict data parsing */ + lydctx->parse_opts |= LYD_PARSE_STRICT; + } else if ((lydctx->int_opts & LYD_INTOPT_ENVELOPE) && !(lydctx->int_opts & LYD_INTOPT_ANY)) { + /* the notification in the envelope contents, parsed as data like any other notification */ + } else { + /* make the parser process data as opaq nodes */ + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ; + } + lydctx->parse_opts |= (ext ? LYD_PARSE_ONLY : 0); lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; lydctx->any_schema = snode; @@ -1382,6 +1391,11 @@ lydcbor_parse_instance(struct lyd_cbor_ctx *lydctx, struct lyd_node *parent, str } LY_CHECK_GOTO(!*node, cleanup); + /* extension instance data */ + if (ext) { + (*node)->flags |= LYD_EXT; + } + /* add/correct flags */ r = lyd_parser_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext); LY_CHECK_ERR_GOTO(r, rc = r, cleanup); diff --git a/src/parser_common.c b/src/parser_common.c index 5d4961b96..10cc64e00 100644 --- a/src/parser_common.c +++ b/src/parser_common.c @@ -140,10 +140,26 @@ lyd_parser_check_schema(struct lyd_ctx *lydctx, const struct lysc_node *snode) LY_ERR rc = LY_SUCCESS; if (lydctx->int_opts & LYD_INTOPT_ANY) { - /* nothing to check, everything is allowed */ + if ((lydctx->int_opts & LYD_INTOPT_ENVELOPE) && (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF))) { + /* only one notification and no RPC/action in the envelope */ + if (snode->nodetype != LYS_NOTIF) { + goto error_node_inval; + } + if (lydctx->op_node) { + goto error_node_dup; + } + } + + /* nothing else to check, everything is allowed */ goto cleanup; } + if ((lydctx->int_opts & LYD_INTOPT_ENVELOPE) && !snode->parent && (strcmp(snode->name, "envelope") || + strcmp(snode->module->name, "ietf-yp-notification"))) { + /* the root must be the envelope */ + goto error_node_inval; + } + if ((lydctx->parse_opts & LYD_PARSE_NO_STATE) && (snode->flags & LYS_CONFIG_R)) { LOG_LOCSET(snode); LOGVAL(lydctx->data_ctx->ctx, NULL, LY_VCODE_UNEXPNODE, "state", snode->name); diff --git a/src/parser_data.h b/src/parser_data.h index 714a1d089..ae0e8e87c 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -370,8 +370,12 @@ enum lyd_type { LYD_TYPE_NOTIF_RESTCONF, /**< RESTCONF JSON or CBOR notification data ([ref](https://www.rfc-editor.org/rfc/rfc8040.html#section-6.4)), to parse a notification in XML, use ::LYD_TYPE_NOTIF_NETCONF */ - LYD_TYPE_REPLY_RESTCONF /**< message-body of a RESTCONF operation output parameters + LYD_TYPE_REPLY_RESTCONF, /**< message-body of a RESTCONF operation output parameters ([ref](https://www.rfc-editor.org/rfc/rfc8040.html#section-3.6.2)) */ + + LYD_TYPE_NOTIF_ENVELOPE /**< YANG-Push notification envelope, the 'envelope' structure of the + 'ietf-yp-notification' module + ([ref](https://datatracker.ietf.org/doc/draft-ietf-netconf-notif-envelope/)) */ }; /** @} datatype */ @@ -427,6 +431,15 @@ enum lyd_type { * - @p op - must be NULL, @p parent points to the operation; * Note that error reply should be parsed as 'yang-data' extension data. * + * - ::LYD_TYPE_NOTIF_ENVELOPE: + * - @p ctx - must have the 'ietf-yp-notification' module implemented; + * - @p parent - must be NULL, the whole envelope is expected; + * - @p format - can be ::LYD_XML, ::LYD_JSON, or ::LYD_CBOR; + * - @p tree - must be provided, the envelope structure data tree with an empty 'contents' will be returned here, + * validate it using ::lyd_validate_all() with ::LYD_VALIDATE_PRESENT; + * - @p op - must be provided, the notification data tree separated from the envelope 'contents' will be returned + * here, pointing to the operation; + * * - ::LYD_TYPE_RPC_YANG: * - ::LYD_TYPE_NOTIF_YANG: * - ::LYD_TYPE_REPLY_YANG: diff --git a/src/parser_internal.h b/src/parser_internal.h index 269875ac4..afa62980a 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -66,6 +66,7 @@ typedef void (*lyd_ctx_free_clb)(struct lyd_ctx *ctx); #define LYD_INTOPT_SKIP_SIBLINGS 0x40 /**< Perform the validation task only for the specfic subtree, skip other siblings. */ #define LYD_INTOPT_NO_SIBLINGS 0x80 /**< If there are any siblings, return an error. */ #define LYD_INTOPT_EVENTTIME 0x0100 /**< Parse notification eventTime node. */ +#define LYD_INTOPT_ENVELOPE 0x0200 /**< YANG-Push notification envelope is being parsed. */ /** * @brief Internal (common) context for YANG data parsers. diff --git a/src/parser_json.c b/src/parser_json.c index 8975aa69a..ca23f6167 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -1382,6 +1382,8 @@ lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, co if (lydctx->parse_opts & LYD_PARSE_ANYDATA_STRICT) { /* explicit strict data parsing */ lydctx->parse_opts |= LYD_PARSE_STRICT; + } else if ((lydctx->int_opts & LYD_INTOPT_ENVELOPE) && !(lydctx->int_opts & LYD_INTOPT_ANY)) { + /* the notification in the envelope contents, parsed as data like any other notification */ } else { /* make the parser process data as opaq nodes */ lydctx->parse_opts &= ~LYD_PARSE_STRICT; diff --git a/src/parser_xml.c b/src/parser_xml.c index 59b015dd3..f27d4fdcf 100644 --- a/src/parser_xml.c +++ b/src/parser_xml.c @@ -970,6 +970,8 @@ lydxml_subtree_any(struct lyd_xml_ctx *lydctx, const struct lysc_node *snode, co if (lydctx->parse_opts & LYD_PARSE_ANYDATA_STRICT) { /* explicit strict data parsing */ lydctx->parse_opts |= LYD_PARSE_STRICT; + } else if ((lydctx->int_opts & LYD_INTOPT_ENVELOPE) && !(lydctx->int_opts & LYD_INTOPT_ANY)) { + /* the notification in the envelope contents, parsed as data like any other notification */ } else { /* make the parser process data as opaq nodes */ lydctx->parse_opts &= ~LYD_PARSE_STRICT; diff --git a/src/tree_data.c b/src/tree_data.c index 56eb01d4d..5f75f795d 100644 --- a/src/tree_data.c +++ b/src/tree_data.c @@ -382,7 +382,7 @@ lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in LY_ERR rc = LY_SUCCESS; struct lyd_ctx *lydctx = NULL; struct ly_set parsed = {0}; - struct lyd_node *first = NULL, *envp = NULL; + struct lyd_node *first = NULL, *envp = NULL, *top; uint32_t i, val_opts, int_opts = 0; ly_bool proto_msg = 0; @@ -441,6 +441,13 @@ lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in case LYD_TYPE_REPLY_YANG: int_opts = LYD_INTOPT_REPLY | (parent ? LYD_INTOPT_WITH_SIBLINGS : LYD_INTOPT_NO_SIBLINGS); break; + case LYD_TYPE_NOTIF_ENVELOPE: + LY_CHECK_ARG_RET(ctx, (format == LYD_XML) || (format == LYD_JSON) || (format == LYD_CBOR), !parent, tree, op, + LY_EINVAL); + LY_CHECK_ERR_RET(!ly_ctx_get_module_implemented(ctx, "ietf-yp-notification"), + LOGERR(ctx, LY_EINVAL, "Module \"ietf-yp-notification\" is not implemented."), LY_EINVAL); + int_opts = LYD_INTOPT_NOTIF | LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_ENVELOPE; + break; default: LOGINT(ctx); rc = LY_EINT; @@ -513,6 +520,12 @@ lyd_parse_op(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in } LY_CHECK_GOTO(rc, cleanup); + if (data_type == LYD_TYPE_NOTIF_ENVELOPE) { + /* unlink the notification from the envelope contents */ + for (top = lydctx->op_node; top->parent->parent != first; top = lyd_parent(top)) {} + lyd_unlink(top); + } + /* set out params correctly */ if (tree) { *tree = parent ? NULL : first; diff --git a/tests/modules/yang/ietf-network-instance@2019-01-21.yang b/tests/modules/yang/ietf-network-instance@2019-01-21.yang new file mode 100644 index 000000000..dfde7fbe8 --- /dev/null +++ b/tests/modules/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/modules/yang/ietf-notification-capabilities@2022-02-17.yang b/tests/modules/yang/ietf-notification-capabilities@2022-02-17.yang new file mode 100644 index 000000000..4583b3648 --- /dev/null +++ b/tests/modules/yang/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"; + } + } + } +} \ No newline at end of file diff --git a/tests/modules/yang/ietf-subscribed-notifications@2019-09-09.yang b/tests/modules/yang/ietf-subscribed-notifications@2019-09-09.yang new file mode 100644 index 000000000..14df3490d --- /dev/null +++ b/tests/modules/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."; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/modules/yang/ietf-system-capabilities@2022-02-17.yang b/tests/modules/yang/ietf-system-capabilities@2022-02-17.yang new file mode 100644 index 000000000..7fa73b208 --- /dev/null +++ b/tests/modules/yang/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." + */ + } + } + } +} \ No newline at end of file diff --git a/tests/modules/yang/ietf-yang-push@2019-09-09.yang b/tests/modules/yang/ietf-yang-push@2019-09-09.yang new file mode 100644 index 000000000..c020f4896 --- /dev/null +++ b/tests/modules/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; + } + } +} \ No newline at end of file diff --git a/tests/modules/yang/ietf-yp-notification@2026-09-25.yang b/tests/modules/yang/ietf-yp-notification@2026-09-25.yang new file mode 100644 index 000000000..f133921b6 --- /dev/null +++ b/tests/modules/yang/ietf-yp-notification@2026-09-25.yang @@ -0,0 +1,233 @@ +module ietf-yp-notification { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-yp-notification"; + prefix iypn; + + import ietf-yang-types { + prefix yang; + reference + "RFC 9911: Common YANG Data Types"; + } + import ietf-inet-types { + prefix inet; + reference + "RFC 9911: Common YANG Data Types"; + } + import ietf-subscribed-notifications { + prefix sn; + reference + "RFC 8639: Subscription to YANG Notifications"; + } + import ietf-system-capabilities { + prefix sysc; + reference + "RFC 9196: YANG Modules Describing Capabilities for + Systems and Datastore Update Notifications"; + } + import ietf-notification-capabilities { + prefix notc; + reference + "RFC 9196: YANG Modules Describing Capabilities for + Systems and Datastore Update Notifications"; + } + import ietf-yang-structure-ext { + prefix sx; + reference + "RFC 8791: YANG Data Structure Extensions"; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + contact + "WG Web: + WG List: + + Authors: Alex Huang Feng + + Pierre Francois + + Thomas Graf + + Benoit Claise + "; + description + "Defines a notification header for Subscribed Notifications + [RFC8639] and YANG-Push [RFC8641]. When this notification header + is enabled through configuration, the root container of the + notification is encoded as defined in RFC XXXX. + + This module can be used to validate XML-encoded notifications + [RFC7950], JSON-encoded messages [RFC7951], and CBOR-encoded + messages [RFC9254]. Refer to Section 3.1.2 of RFC XXXX for more + details. + + Copyright (c) 2026 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). + + All revisions of IETF and IANA published modules can be found + at the YANG Parameters registry group + (https://www.iana.org/assignments/yang-parameters). + + This version of this YANG module is part of RFC XXXX; see + the RFC itself for full legal notices. + + 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."; + + revision 2026-09-25 { + description + "Initial version."; + reference + "RFC XXXX: Extensible YANG Model for YANG-Push Notifications"; + } + + feature hostname-sequence-number { + description + "This feature indicates that hostname and sequence numbers are + supported."; + } + + identity notification-envelope-changed { + base sn:subscription-terminated-reason; + description + "The subscription was terminated because the value of + 'enable-notification-envelope' was changed."; + reference + "Section 4 of RFC XXXX: Extensible YANG Model for YANG-Push + Notifications"; + } + + grouping notif-env-capabilities { + description + "This grouping defines the capabilities for the + notification-envelope, and the different supported metadata."; + reference + "Section 5 of RFC XXXX: Extensible YANG Model for YANG-Push + Notifications"; + leaf envelope { + type boolean; + default "true"; + description + "Supports YANG-Push to use the notification-envelope. If + set to true, the publisher supports the notification + envelope. If set to false, the notification envelope is not + supported by the publisher."; + } + container metadata { + description + "Container with the supported optional metadata by the + YANG-Push publisher."; + leaf hostname-sequence-number { + if-feature "hostname-sequence-number"; + type boolean; + default "false"; + description + "Supports hostname and sequence-number in the YANG-Push + notifications. If set to true, the publisher supports + sending the hostname and sequence numbers within the + notification envelope. If set to false, the hostname + and sequence numbers are not supported."; + } + } + } + + sx:structure envelope { + description + "This structure defines the Notification Envelope: the + notification metadata ('event-time' and, when supported, + 'hostname' and 'sequence-number') and the notification + payload in 'contents'."; + reference + "Section 3 of RFC XXXX: Extensible YANG Model for YANG-Push + Notifications"; + leaf event-time { + type yang:date-and-time; + mandatory true; + description + "The date and time the notification was generated by the + publisher. This is not necessarily the time at which the + reported data was observed; see Section 3.3.1 of RFC XXXX."; + } + leaf hostname { + if-feature "hostname-sequence-number"; + type inet:host-name; + description + "The hostname of the network node. This value is usually + configured on the node by the administrator to identify + the node in the network uniquely."; + } + leaf sequence-number { + if-feature "hostname-sequence-number"; + type uint32; + description + "Sequence number of the published message, per publisher + process. The initial number is 1 and it counts up by 1 at + every published notification message until it reaches + 4294967295. Then, it wraps around and restarts at 0. The + value 0 is used to detect wrap arounds. When the publisher + process restarts, the sequence number restarts at 1."; + } + anydata contents { + mandatory true; + description + "This contains the values defined by the 'notification' + statement unchanged."; + } + } + + // Subscription container + augment "/sn:subscriptions" { + description + "This augmentation adds the configuration switches for + enabling the notification envelope and metadata."; + leaf enable-notification-envelope { + type boolean; + default "false"; + description + "Enables YANG-Push to use the notification-envelope + instead of RFC 5277 defined notifications. Enabling or + disabling this leaf terminates all existing active dynamic + and configured YANG-Push subscriptions. Before terminating + each subscription, the publisher MUST send a + 'subscription-terminated' notification, with the reason + 'notification-envelope-changed', using the header + configuration that was in effect before the change. + Configured subscriptions are then re-evaluated and + restarted with the new header."; + reference + "Section 4 of RFC XXXX."; + } + container metadata { + description + "This container is intended to be augmented by modules + defining optional notification metadata extensions."; + reference + "Section 4 of RFC XXXX."; + } + } + + // YANG-Push Capabilities extension + augment "/sysc:system-capabilities" + + "/notc:subscription-capabilities" { + description + "Extension to the subscription-capabilities model to enable + clients to learn whether the publisher supports the + notification-envelope"; + container notification-metadata { + description + "Adds the notification metadata capabilities to subscription + capabilities."; + uses notif-env-capabilities; + } + } +} diff --git a/tests/modules/yang/ietf-yp-observation@2026-09-25.yang b/tests/modules/yang/ietf-yp-observation@2026-09-25.yang new file mode 100644 index 000000000..d1606c9fa --- /dev/null +++ b/tests/modules/yang/ietf-yp-observation@2026-09-25.yang @@ -0,0 +1,145 @@ +module ietf-yp-observation { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-yp-observation"; + prefix iypo; + + import ietf-yang-types { + prefix yang; + reference + "RFC 9911: Common YANG Data Types"; + } + import ietf-yang-push { + prefix yp; + reference + "RFC 8641: Subscription to YANG Notifications for Datastore + Updates"; + } + import ietf-system-capabilities { + prefix sysc; + reference + "RFC 9196: YANG Modules Describing Capabilities for + Systems and Datastore Update Notifications"; + } + import ietf-notification-capabilities { + prefix notc; + 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: + + Authors: Thomas Graf + + Benoit Claise + + Alex Huang Feng + "; + description + "Defines the observation time in YANG-Push streaming update + notifications. + + Copyright (c) 2026 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). + + All revisions of IETF and IANA published modules can be found + at the YANG Parameters registry group + (https://www.iana.org/assignments/yang-parameters). + + This version of this YANG module is part of RFC XXXX; see + the RFC itself for full legal notices."; + + revision 2026-09-25 { + description + "Initial version."; + reference + "RFC XXXX: Extensible YANG Model for YANG-Push Notifications"; + } + + grouping yang-push-observation { + description + "This grouping adds the observation timestamp for the + observed metrics."; + reference + "Section 3.3.1 of RFC XXXX."; + leaf timestamp { + type yang:date-and-time; + description + "The point in time at which the reported data was observed. + The 'point-in-time' leaf states which point in time this + is."; + } + leaf point-in-time { + type enumeration { + enum current-accounting { + description + "For periodic subscriptions, the point-in-time + where the metrics are being polled and observed."; + } + enum initial-state { + description + "For the initial 'push-update' notification of an + on-change subscription with 'sync-on-start' set to + 'true', the point in time when the reported state was + last observed to change. This point in time can precede + the establishment of the subscription."; + } + enum state-changed { + description + "For on-change subscriptions, the point in time when the + state change was observed after the subscription was + established."; + } + } + description + "This describes at which point in time the metrics were + observed."; + } + } + + // Event notifications + augment "/yp:push-update" { + description + "This augmentation adds the observation timestamp of the + accounted metrics in the push-update notification."; + uses yang-push-observation; + } + + augment "/yp:push-change-update" { + description + "This augmentation adds the observation timestamp of the + event in the push-change-update notification."; + uses yang-push-observation; + } + + // Event capabilities + augment "/sysc:system-capabilities" + + "/notc:subscription-capabilities" { + description + "Add YANG-Push notification capabilities to system-level + capability container."; + leaf yang-push-observation-time-supported { + type boolean; + default "false"; + description + "Specifies whether the publisher supports exporting + observation-timestamp and point-in-time in notifications. + If set to true, the publisher supports the + observation-timestamp and point-in-time in notifications. + If set to false, these are not supported."; + reference + "Section 3.3.1 of RFC XXXX."; + } + } +} diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index 6e3c851c5..6df236c08 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -73,6 +73,7 @@ ly_add_utest(NAME lyb SOURCES data/test_lyb.c) ly_add_utest(NAME validation SOURCES data/test_validation.c) ly_add_utest(NAME merge SOURCES data/test_merge.c) ly_add_utest(NAME diff SOURCES data/test_diff.c) +ly_add_utest(NAME notif_envelope SOURCES data/test_notif_envelope.c) if(CBOR_FOUND) ly_add_utest(NAME cbor SOURCES data/test_cbor.c) endif() diff --git a/tests/utests/data/test_cbor.c b/tests/utests/data/test_cbor.c index 76b2a875e..df8729acc 100644 --- a/tests/utests/data/test_cbor.c +++ b/tests/utests/data/test_cbor.c @@ -241,6 +241,37 @@ test_operations(void **state) } } +static void +test_structure(void **state) +{ + struct lyd_node *tree; + char *buffer, *json; + struct ly_out *out; + const char *mod, *data; + + mod = "module a {yang-version 1.1; namespace urn:tests:a; prefix a;" + "import ietf-yang-structure-ext {prefix sx;}" + "sx:structure struct {container x {leaf y {type string;} leaf z {type uint8; mandatory true;}}}}"; + UTEST_ADD_MODULE(mod, LYS_IN_YANG, NULL, NULL); + + data = "{\"a:struct\":{\"x\":{\"y\":\"val\",\"z\":25}}}"; + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_JSON, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree)); + assert_int_equal(LY_SUCCESS, ly_out_new_memory(&buffer, 0, &out)); + assert_int_equal(LY_SUCCESS, lyd_print_all(out, tree, LYD_CBOR, 0)); + lyd_free_all(tree); + + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem_len(UTEST_LYCTX, buffer, ly_out_printed(out), LYD_CBOR, + LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree)); + ly_out_free(out, NULL, 0); + free(buffer); + assert_true(tree->flags & LYD_EXT); + + assert_int_equal(LY_SUCCESS, lyd_print_mem(&json, tree, LYD_JSON, LYD_PRINT_SHRINK | LYD_PRINT_SIBLINGS)); + assert_string_equal(json, data); + free(json); + lyd_free_all(tree); +} + static LY_ERR test_ext_data_clb(const struct lysc_ext_instance *ext, const struct lyd_node *UNUSED(parent), void *user_data, void **ext_data, ly_bool *ext_data_free) @@ -479,6 +510,25 @@ test_negint(void **state) CHECK_LOG_CTX("Invalid type uint8 value \"-18446744073709551616\".", "/cbor-test:lf-uintg8", 1); } +static void +test_anydata_strict(void **state) +{ + struct lyd_node *tree; + + /* {"cbor-test:anydata-payload": {"cbor-test:unknown": "v"}} */ + const char data[] = "\xa1\x78\x19" "cbor-test:anydata-payload" "\xa1\x71" "cbor-test:unknown" "\x61" "v"; + + /* unknown anydata content is parsed as opaque nodes */ + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem_len(UTEST_LYCTX, data, sizeof data - 1, LYD_CBOR, 0, + LYD_VALIDATE_PRESENT, &tree)); + lyd_free_all(tree); + + /* but not with strict anydata parsing */ + assert_int_equal(LY_EVALID, lyd_parse_data_mem_len(UTEST_LYCTX, data, sizeof data - 1, LYD_CBOR, + LYD_PARSE_ANYDATA_STRICT, LYD_VALIDATE_PRESENT, &tree)); + CHECK_LOG_CTX("Node \"unknown\" not found in the \"cbor-test\" module.", "/cbor-test:anydata-payload", 1); +} + /* CBOR RESTCONF notification envelope parts */ #define CBOR_ENVP_KEY "\x78\x1a" "ietf-restconf:notification" #define CBOR_EVENT_TIME "\x69" "eventTime" "\x74" "2013-12-21T00:01:00Z" @@ -596,12 +646,14 @@ main(void) UTEST(test_node, setup), UTEST(test_nested_list, setup), UTEST(test_operations, setup), + UTEST(test_structure, setup), UTEST(test_schema_mount, setup), UTEST(test_opaque, setup), UTEST(test_opaque_list, setup), UTEST(test_attributes, setup), UTEST(test_invalid_input, setup), UTEST(test_negint, setup), + UTEST(test_anydata_strict, setup), UTEST(test_restconf_notification, setup), UTEST(test_restconf_notification_invalid, setup), }; diff --git a/tests/utests/data/test_notif_envelope.c b/tests/utests/data/test_notif_envelope.c new file mode 100644 index 000000000..059fbf683 --- /dev/null +++ b/tests/utests/data/test_notif_envelope.c @@ -0,0 +1,476 @@ +/** + * @file test_notif_envelope.c + * @author Roman Janota + * @brief unit tests for YANG-Push notification envelope parsing + * + * Copyright (c) 2026 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. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ +#define _UTEST_MAIN_ +#include "utests.h" + +#include "libyang.h" + +static int +setup(void **state) +{ + const char *feats[] = {"hostname-sequence-number", NULL}; + + UTEST_SETUP; + + assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_DIR_MODULES_YANG)); + assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-yp-notification", "2026-09-25", feats)); + assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-yp-observation", "2026-09-25", NULL)); + assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-interfaces", NULL, NULL)); + assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "iana-if-type", NULL, NULL)); + + return 0; +} + +#define CHECK_PARSE_ENVELOPE(DATA, FORMAT, TREE, OP) \ + { \ + struct ly_in *_in; \ + assert_int_equal(LY_SUCCESS, ly_in_new_memory(DATA, &_in)); \ + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, _in, FORMAT, LYD_TYPE_NOTIF_ENVELOPE, 0, &TREE, &OP)); \ + ly_in_free(_in, 0); \ + } + +#define CHECK_PARSE_ENVELOPE_ERR(DATA, FORMAT, RC) \ + { \ + struct ly_in *_in; \ + struct lyd_node *_tree, *_op; \ + assert_int_equal(LY_SUCCESS, ly_in_new_memory(DATA, &_in)); \ + assert_int_equal(RC, lyd_parse_op(UTEST_LYCTX, NULL, _in, FORMAT, LYD_TYPE_NOTIF_ENVELOPE, 0, &_tree, &_op)); \ + assert_null(_tree); \ + assert_null(_op); \ + ly_in_free(_in, 0); \ + } + +/** + * @brief Put the notification separated by the parser back into the envelope contents, to print the whole message. + * + * @param[in] tree Envelope with an empty contents. + * @param[in] op Separated notification, freed. + */ +static void +envelope_attach(struct lyd_node *tree, struct lyd_node *op) +{ + struct lyd_node *contents, *root; + + assert_int_equal(LY_SUCCESS, lyd_find_path(tree, "contents", 0, &contents)); + assert_null(((struct lyd_node_any *)contents)->child); + for (root = op; lyd_parent(root); root = lyd_parent(root)) {} + assert_int_equal(LY_SUCCESS, lyd_any_copy_value(contents, root, NULL, 0)); + lyd_free_all(op); +} + +static void +test_parse_xml(void **state) +{ + struct lyd_node *tree, *op, *node; + const char *data; + + /* draft Figure 1 */ + data = "\n" + " 2024-10-10T10:59:55.32Z\n" + " \n" + " \n" + " 1011\n" + " \n" + " \n" + " \n" + " eth0\n" + " ianaift:ethernetCsmacd\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + CHECK_PARSE_ENVELOPE(data, LYD_XML, tree, op); + + /* envelope with its schema */ + assert_string_equal(LYD_NAME(tree), "envelope"); + assert_true(tree->flags & LYD_EXT); + assert_null(tree->next); + node = lyd_child(tree); + assert_string_equal(LYD_NAME(node), "event-time"); + assert_int_equal(node->schema->nodetype, LYS_LEAF); + assert_string_equal(LYD_NAME(node->next), "contents"); + + /* notification separated from the contents */ + assert_string_equal(LYD_NAME(op), "push-update"); + assert_int_equal(op->schema->nodetype, LYS_NOTIF); + assert_null(lyd_parent(op)); + assert_null(((struct lyd_node_any *)node->next)->child); + + /* follow-up validation */ + assert_int_equal(LY_SUCCESS, lyd_validate_op(op, NULL, LYD_TYPE_NOTIF_YANG, NULL)); + assert_int_equal(LY_SUCCESS, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT, NULL)); + + envelope_attach(tree, op); + CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, 0); + lyd_free_all(tree); +} + +static void +test_parse_json(void **state) +{ + struct lyd_node *tree, *op, *node; + const char *data; + + /* draft Figure 5 */ + data = "{\n" + " \"ietf-yp-notification:envelope\": {\n" + " \"event-time\": \"2023-03-25T08:30:11.22Z\",\n" + " \"hostname\": \"example-router.example.com\",\n" + " \"sequence-number\": 1,\n" + " \"contents\": {\n" + " \"ietf-yang-push:push-update\": {\n" + " \"id\": 6666,\n" + " \"datastore-contents\": {\n" + " \"ietf-interfaces:interfaces\": {\n" + " \"interface\": [\n" + " {\n" + " \"name\": \"eth0\",\n" + " \"type\": \"iana-if-type:ethernetCsmacd\"\n" + " }\n" + " ]\n" + " }\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}\n"; + CHECK_PARSE_ENVELOPE(data, LYD_JSON, tree, op); + + assert_string_equal(LYD_NAME(tree), "envelope"); + assert_int_equal(LY_SUCCESS, lyd_find_path(tree, "sequence-number", 0, &node)); + assert_int_equal(((struct lyd_node_term *)node)->value.uint32, 1); + assert_string_equal(LYD_NAME(op), "push-update"); + + assert_int_equal(LY_SUCCESS, lyd_validate_op(op, NULL, LYD_TYPE_NOTIF_YANG, NULL)); + assert_int_equal(LY_SUCCESS, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT, NULL)); + + envelope_attach(tree, op); + CHECK_LYD_STRING_PARAM(tree, data, LYD_JSON, 0); + lyd_free_all(tree); +} + +static void +test_create_print(void **state) +{ + const struct lys_module *mod; + struct lyd_node *env, *notif, *tree, *op; + char *str; + const char *xml, *json; + + xml = "" + "2026-11-02T10:00:00Z" + "router" + "7" + "" + "" + "1" + "2026-11-02T09:59:59Z" + "current-accounting" + "" + "" + ""; + json = "{\"ietf-yp-notification:envelope\":{" + "\"event-time\":\"2026-11-02T10:00:00Z\"," + "\"hostname\":\"router\"," + "\"sequence-number\":7," + "\"contents\":{\"ietf-yang-push:push-update\":{" + "\"id\":1," + "\"ietf-yp-observation:timestamp\":\"2026-11-02T09:59:59Z\"," + "\"ietf-yp-observation:point-in-time\":\"current-accounting\"}}}}"; + + /* the notification */ + mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "ietf-yang-push"); + assert_int_equal(LY_SUCCESS, lyd_new_inner(NULL, mod, "push-update", 0, ¬if)); + assert_int_equal(LY_SUCCESS, lyd_new_term(notif, NULL, "id", "1", 0, NULL)); + mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "ietf-yp-observation"); + assert_int_equal(LY_SUCCESS, lyd_new_term(notif, mod, "timestamp", "2026-11-02T09:59:59Z", 0, NULL)); + assert_int_equal(LY_SUCCESS, lyd_new_term(notif, mod, "point-in-time", "current-accounting", 0, NULL)); + + /* the envelope */ + mod = ly_ctx_get_module_implemented(UTEST_LYCTX, "ietf-yp-notification"); + assert_int_equal(LY_SUCCESS, lyd_new_inner(NULL, mod, "envelope", 0, &env)); + assert_int_equal(LY_SUCCESS, lyd_new_term(env, NULL, "event-time", "2026-11-02T10:00:00Z", 0, NULL)); + assert_int_equal(LY_SUCCESS, lyd_new_term(env, NULL, "hostname", "router", 0, NULL)); + assert_int_equal(LY_SUCCESS, lyd_new_term(env, NULL, "sequence-number", "7", 0, NULL)); + assert_int_equal(LY_SUCCESS, lyd_new_any(env, NULL, "contents", notif, NULL, 0, LYD_NEW_ANY_USE_VALUE, NULL)); + assert_int_equal(LY_SUCCESS, lyd_validate_all(&env, NULL, LYD_VALIDATE_PRESENT, NULL)); + + /* print and parse back */ + CHECK_LYD_STRING_PARAM(env, xml, LYD_XML, LYD_PRINT_SHRINK); + CHECK_LYD_STRING_PARAM(env, json, LYD_JSON, LYD_PRINT_SHRINK); + lyd_free_all(env); + + CHECK_PARSE_ENVELOPE(xml, LYD_XML, tree, op); + assert_int_equal(LY_SUCCESS, lyd_validate_op(op, NULL, LYD_TYPE_NOTIF_YANG, NULL)); + envelope_attach(tree, op); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&str, tree, LYD_JSON, LYD_PRINT_SHRINK)); + assert_string_equal(str, json); + free(str); + lyd_free_all(tree); + + CHECK_PARSE_ENVELOPE(json, LYD_JSON, tree, op); + assert_int_equal(LY_SUCCESS, lyd_validate_op(op, NULL, LYD_TYPE_NOTIF_YANG, NULL)); + envelope_attach(tree, op); + CHECK_LYD_STRING_PARAM(tree, xml, LYD_XML, LYD_PRINT_SHRINK); + lyd_free_all(tree); +} + +#ifdef LY_HAVE_CBOR + +static void +test_cbor(void **state) +{ + struct lyd_node *tree, *op; + char *buffer, *json; + struct ly_out *out; + struct ly_in *in; + const char *data; + + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\",\"hostname\":\"router\"," + "\"sequence-number\":7,\"contents\":{\"ietf-yang-push:push-update\":{\"id\":1011}}}}"; + CHECK_PARSE_ENVELOPE(data, LYD_JSON, tree, op); + envelope_attach(tree, op); + + assert_int_equal(LY_SUCCESS, ly_out_new_memory(&buffer, 0, &out)); + assert_int_equal(LY_SUCCESS, lyd_print_all(out, tree, LYD_CBOR, 0)); + lyd_free_all(tree); + + assert_int_equal(LY_SUCCESS, ly_in_new_memory_chunk(buffer, ly_out_printed(out), &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_CBOR, LYD_TYPE_NOTIF_ENVELOPE, 0, &tree, &op)); + ly_in_free(in, 0); + ly_out_free(out, NULL, 0); + free(buffer); + + assert_string_equal(LYD_NAME(op), "push-update"); + assert_int_equal(LY_SUCCESS, lyd_validate_op(op, NULL, LYD_TYPE_NOTIF_YANG, NULL)); + assert_int_equal(LY_SUCCESS, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT, NULL)); + + envelope_attach(tree, op); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&json, tree, LYD_JSON, LYD_PRINT_SHRINK)); + assert_string_equal(json, data); + free(json); + lyd_free_all(tree); +} + +#endif /* LY_HAVE_CBOR */ + +static void +test_nested(void **state) +{ + struct lyd_node *tree, *op; + struct ly_in *in; + const char *mod, *data; + + mod = "module a {yang-version 1.1; namespace urn:tests:a; prefix a;" + "container c {list l {key k; leaf k {type string;} notification n {leaf x {type uint8;}}}}}"; + UTEST_ADD_MODULE(mod, LYS_IN_YANG, NULL, NULL); + + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\"," + "\"contents\":{\"a:c\":{\"l\":[{\"k\":\"key\",\"n\":{\"x\":5}}]}}}}"; + CHECK_PARSE_ENVELOPE(data, LYD_JSON, tree, op); + + assert_string_equal(LYD_NAME(op), "n"); + assert_string_equal(LYD_NAME(lyd_parent(op)), "l"); + assert_int_equal(LY_SUCCESS, lyd_validate_op(op, NULL, LYD_TYPE_NOTIF_YANG, NULL)); + envelope_attach(tree, op); + CHECK_LYD_STRING_PARAM(tree, data, LYD_JSON, LYD_PRINT_SHRINK); + lyd_free_all(tree); + + /* anydata in the notification is still parsed as opaque anydata content, even with strict parsing */ + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\",\"contents\":{" + "\"ietf-yang-push:push-update\":{\"id\":1,\"datastore-contents\":{\"x:unknown\":{\"leaf\":1}}}}}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_NOTIF_ENVELOPE, + LYD_PARSE_STRICT, &tree, &op)); + ly_in_free(in, 0); + assert_string_equal(LYD_NAME(op), "push-update"); + lyd_free_all(tree); + lyd_free_all(op); +} + +static void +test_augment(void **state) +{ + struct lyd_node *tree, *op, *node; + const char *mod, *data; + + mod = "module b {yang-version 1.1; namespace urn:tests:b; prefix b;" + "import ietf-yang-structure-ext {prefix sx;}" + "import ietf-yp-notification {prefix iypn;}" + "sx:augment-structure \"/iypn:envelope\" {leaf foo {type uint16;}}}"; + UTEST_ADD_MODULE(mod, LYS_IN_YANG, NULL, NULL); + + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\"," + "\"contents\":{\"ietf-yang-push:push-update\":{\"id\":1}},\"b:foo\":42}}"; + CHECK_PARSE_ENVELOPE(data, LYD_JSON, tree, op); + + assert_int_equal(LY_SUCCESS, lyd_find_path(tree, "b:foo", 0, &node)); + assert_int_equal(((struct lyd_node_term *)node)->value.uint16, 42); + assert_string_equal(LYD_NAME(op), "push-update"); + assert_int_equal(LY_SUCCESS, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT, NULL)); + envelope_attach(tree, op); + CHECK_LYD_STRING_PARAM(tree, data, LYD_JSON, LYD_PRINT_SHRINK); + lyd_free_all(tree); +} + +static void +test_invalid(void **state) +{ + struct lyd_node *tree, *op; + struct ly_in *in; + const char *data; + + /* no envelope */ + data = "1"; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Unexpected notification element \"push-update\".", "/ietf-yang-push:push-update", 1); + + data = ""; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Unexpected container element \"interfaces\".", "/ietf-interfaces:interfaces", 1); + + /* two notifications */ + data = "" + "2026-11-02T10:00:00Z" + "1" + "2" + ""; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Unexpected notification element \"push-update\", notification \"push-update\" already parsed.", + "/ietf-yang-push:push-update", 1); + + /* RPC instead of the notification */ + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\",\"contents\":{" + "\"ietf-subscribed-notifications:kill-subscription\":{\"id\":1}}}}"; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_JSON, LY_EVALID); + CHECK_LOG_CTX("Unexpected RPC element \"kill-subscription\".", + "/ietf-subscribed-notifications:kill-subscription", 1); + + /* invalid notification value, the contents are not parsed as opaque anydata content */ + data = "" + "2026-11-02T10:00:00Z" + "x" + ""; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Invalid type uint32 value \"x\".", + "/ietf-yp-notification:envelope/contents/ietf-yang-push:push-update/id", 1); + +#ifdef LY_HAVE_CBOR + data = "\xa1\x78\x1d" "ietf-yp-notification:envelope" "\xa2" + "\x6a" "event-time" "\x74" "2026-11-02T10:00:00Z" + "\x68" "contents" "\xa1\x78\x1a" "ietf-yang-push:push-update" "\xa1\x62" "id" "\x61" "x"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory_chunk(data, strlen(data), &in)); + assert_int_equal(LY_EVALID, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_CBOR, LYD_TYPE_NOTIF_ENVELOPE, 0, &tree, &op)); + ly_in_free(in, 0); + CHECK_LOG_CTX("Invalid non-number-encoded uint32 value \"x\".", "/ietf-yang-push:push-update/id", 1); +#endif /* LY_HAVE_CBOR */ + + /* unknown node in the notification with strict parsing */ + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\",\"contents\":{" + "\"ietf-yang-push:push-update\":{\"id\":1,\"unknown\":1}}}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_EVALID, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_NOTIF_ENVELOPE, + LYD_PARSE_STRICT, &tree, &op)); + ly_in_free(in, 0); + CHECK_LOG_CTX("Node \"unknown\" not found as a child of \"push-update\" node.", + "/ietf-yp-notification:envelope/contents/ietf-yang-push:push-update", 1); + + /* no notification */ + data = "" + "2026-11-02T10:00:00Z"; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Missing the operation node.", NULL, 1); + + data = "" + "2026-11-02T10:00:00Z" + ""; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Missing the operation node.", NULL, 1); + + /* sibling of the envelope */ + data = "" + "2026-11-02T10:00:00Z" + "1" + "" + ""; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EVALID); + CHECK_LOG_CTX("Unexpected sibling node.", NULL, 1); + + /* invalid arguments */ + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_EINVAL, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_LYB, LYD_TYPE_NOTIF_ENVELOPE, 0, &tree, &op)); + CHECK_LOG_CTX("Invalid argument (format == LYD_XML) || (format == LYD_JSON) || (format == LYD_CBOR) (lyd_parse_op()).", + NULL, 0); + assert_int_equal(LY_EINVAL, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_NOTIF_ENVELOPE, 0, &tree, NULL)); + CHECK_LOG_CTX("Invalid argument op (lyd_parse_op()).", NULL, 0); + ly_in_free(in, 0); +} + +static void +test_invalid_validate(void **state) +{ + struct lyd_node *tree, *op; + const char *data; + + /* missing event-time */ + data = "{\"ietf-yp-notification:envelope\":{\"contents\":{\"ietf-yang-push:push-update\":{\"id\":1}}}}"; + CHECK_PARSE_ENVELOPE(data, LYD_JSON, tree, op); + assert_int_equal(LY_EVALID, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT, NULL)); + CHECK_LOG_CTX("Mandatory node \"event-time\" instance does not exist.", "/ietf-yp-notification:envelope", 0); + lyd_free_all(tree); + lyd_free_all(op); +} + +static void +test_no_module(void **state) +{ + const char *data; + + assert_int_equal(LY_SUCCESS, ly_ctx_set_searchdir(UTEST_LYCTX, TESTS_DIR_MODULES_YANG)); + assert_non_null(ly_ctx_load_module(UTEST_LYCTX, "ietf-yang-push", NULL, NULL)); + + data = "" + "2026-11-02T10:00:00Z" + "1" + ""; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_XML, LY_EINVAL); + CHECK_LOG_CTX("Module \"ietf-yp-notification\" is not implemented.", NULL, 0); + + data = "{\"ietf-yp-notification:envelope\":{\"event-time\":\"2026-11-02T10:00:00Z\",\"contents\":{" + "\"ietf-yang-push:push-update\":{\"id\":1}}}}"; + CHECK_PARSE_ENVELOPE_ERR(data, LYD_JSON, LY_EINVAL); + CHECK_LOG_CTX("Module \"ietf-yp-notification\" is not implemented.", NULL, 0); +} + +int +main(void) +{ + const struct CMUnitTest tests[] = { + UTEST(test_parse_xml, setup), + UTEST(test_parse_json, setup), + UTEST(test_create_print, setup), +#ifdef LY_HAVE_CBOR + UTEST(test_cbor, setup), +#endif /* LY_HAVE_CBOR */ + UTEST(test_nested, setup), + UTEST(test_augment, setup), + UTEST(test_invalid, setup), + UTEST(test_invalid_validate, setup), + UTEST(test_no_module), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +}