mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
Merge pull request #1322 from kernelkit/upgrade-sysrepo
Upgrade sysrepo
This commit is contained in:
+31
-10
@@ -41,14 +41,31 @@
|
|||||||
"ietf-ip:ipv4": {
|
"ietf-ip:ipv4": {
|
||||||
"infix-dhcp-client:dhcp": {
|
"infix-dhcp-client:dhcp": {
|
||||||
"option": [
|
"option": [
|
||||||
{"id": "netmask"},
|
{
|
||||||
{"id": "broadcast"},
|
"id": "netmask"
|
||||||
{"id": "router"},
|
},
|
||||||
{"id": "domain"},
|
{
|
||||||
{"id": "hostname"},
|
"id": "broadcast"
|
||||||
{"id": "dns-server"},
|
},
|
||||||
{"id": "ntp-server"},
|
{
|
||||||
{"id": "vendor-class", "value": "Raspberry Pi 2 Model B"}
|
"id": "router"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "domain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hostname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dns-server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ntp-server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vendor-class",
|
||||||
|
"value": "Raspberry Pi 2 Model B"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +139,11 @@
|
|||||||
"name": "default-ssh",
|
"name": "default-ssh",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"tcp-server-parameters": {
|
"tcp-server-parameters": {
|
||||||
"local-address": "::"
|
"local-bind": [
|
||||||
|
{
|
||||||
|
"local-address": "::"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ssh-server-parameters": {
|
"ssh-server-parameters": {
|
||||||
"server-identity": {
|
"server-identity": {
|
||||||
@@ -168,7 +189,7 @@
|
|||||||
"infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
|
"infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
|
||||||
},
|
},
|
||||||
"infix-meta:meta": {
|
"infix-meta:meta": {
|
||||||
"version": "1.6"
|
"version": "1.7"
|
||||||
},
|
},
|
||||||
"infix-services:mdns": {
|
"infix-services:mdns": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|||||||
+55
-27
@@ -102,24 +102,49 @@
|
|||||||
"ietf-ip:ipv4": {
|
"ietf-ip:ipv4": {
|
||||||
"infix-dhcp-client:dhcp": {
|
"infix-dhcp-client:dhcp": {
|
||||||
"option": [
|
"option": [
|
||||||
{"id": "ntp-server"},
|
{
|
||||||
{"id": "broadcast"},
|
"id": "ntp-server"
|
||||||
{"id": "domain"},
|
},
|
||||||
{"id": "hostname"},
|
{
|
||||||
{"id": "dns-server"},
|
"id": "broadcast"
|
||||||
{"id": "router"},
|
},
|
||||||
{"id": "netmask"},
|
{
|
||||||
{"id": "vendor-class", "value": "Banana Pi BPI-R3"}
|
"id": "domain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hostname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dns-server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "router"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "netmask"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vendor-class",
|
||||||
|
"value": "Banana Pi BPI-R3"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ietf-ip:ipv6": {
|
"ietf-ip:ipv6": {
|
||||||
"infix-dhcpv6-client:dhcp": {
|
"infix-dhcpv6-client:dhcp": {
|
||||||
"option": [
|
"option": [
|
||||||
{"id": "ntp-server"},
|
{
|
||||||
{"id": "client-fqdn"},
|
"id": "ntp-server"
|
||||||
{"id": "domain-search"},
|
},
|
||||||
{"id": "dns-server"}
|
{
|
||||||
|
"id": "client-fqdn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "domain-search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dns-server"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,18 +160,18 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ietf-keystore:keystore": {
|
"ietf-keystore:keystore": {
|
||||||
"asymmetric-keys": {
|
"asymmetric-keys": {
|
||||||
"asymmetric-key": [
|
"asymmetric-key": [
|
||||||
{
|
{
|
||||||
"name": "genkey",
|
"name": "genkey",
|
||||||
"public-key-format": "infix-crypto-types:ssh-public-key-format",
|
"public-key-format": "infix-crypto-types:ssh-public-key-format",
|
||||||
"public-key": "",
|
"public-key": "",
|
||||||
"private-key-format": "infix-crypto-types:rsa-private-key-format",
|
"private-key-format": "infix-crypto-types:rsa-private-key-format",
|
||||||
"cleartext-private-key": "",
|
"cleartext-private-key": "",
|
||||||
"certificates": {}
|
"certificates": {}
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ietf-netconf-acm:nacm": {
|
"ietf-netconf-acm:nacm": {
|
||||||
"enable-nacm": true,
|
"enable-nacm": true,
|
||||||
@@ -201,7 +226,11 @@
|
|||||||
"name": "default-ssh",
|
"name": "default-ssh",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"tcp-server-parameters": {
|
"tcp-server-parameters": {
|
||||||
"local-address": "::"
|
"local-bind": [
|
||||||
|
{
|
||||||
|
"local-address": "::"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ssh-server-parameters": {
|
"ssh-server-parameters": {
|
||||||
"server-identity": {
|
"server-identity": {
|
||||||
@@ -293,7 +322,7 @@
|
|||||||
"policy": [
|
"policy": [
|
||||||
{
|
{
|
||||||
"name": "lan-to-wan",
|
"name": "lan-to-wan",
|
||||||
"action": "accept",
|
"action": "accept",
|
||||||
"ingress": [
|
"ingress": [
|
||||||
"lan"
|
"lan"
|
||||||
],
|
],
|
||||||
@@ -305,7 +334,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"infix-meta:meta": {
|
"infix-meta:meta": {
|
||||||
"version": "1.6"
|
"version": "1.7"
|
||||||
},
|
},
|
||||||
"infix-services:mdns": {
|
"infix-services:mdns": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
@@ -341,4 +370,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+55
-26
@@ -61,24 +61,49 @@
|
|||||||
"ietf-ip:ipv4": {
|
"ietf-ip:ipv4": {
|
||||||
"infix-dhcp-client:dhcp": {
|
"infix-dhcp-client:dhcp": {
|
||||||
"option": [
|
"option": [
|
||||||
{"id": "ntp-server"},
|
{
|
||||||
{"id": "broadcast"},
|
"id": "ntp-server"
|
||||||
{"id": "domain"},
|
},
|
||||||
{"id": "hostname"},
|
{
|
||||||
{"id": "dns-server"},
|
"id": "broadcast"
|
||||||
{"id": "router"},
|
},
|
||||||
{"id": "netmask"},
|
{
|
||||||
{"id": "vendor-class", "value": "NanoPi R2S"}
|
"id": "domain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hostname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dns-server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "router"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "netmask"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vendor-class",
|
||||||
|
"value": "NanoPi R2S"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ietf-ip:ipv6": {
|
"ietf-ip:ipv6": {
|
||||||
"infix-dhcpv6-client:dhcp": {
|
"infix-dhcpv6-client:dhcp": {
|
||||||
"option": [
|
"option": [
|
||||||
{"id": "ntp-server"},
|
{
|
||||||
{"id": "client-fqdn"},
|
"id": "ntp-server"
|
||||||
{"id": "domain-search"},
|
},
|
||||||
{"id": "dns-server"}
|
{
|
||||||
|
"id": "client-fqdn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "domain-search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dns-server"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,18 +111,18 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ietf-keystore:keystore": {
|
"ietf-keystore:keystore": {
|
||||||
"asymmetric-keys": {
|
"asymmetric-keys": {
|
||||||
"asymmetric-key": [
|
"asymmetric-key": [
|
||||||
{
|
{
|
||||||
"name": "genkey",
|
"name": "genkey",
|
||||||
"public-key-format": "infix-crypto-types:ssh-public-key-format",
|
"public-key-format": "infix-crypto-types:ssh-public-key-format",
|
||||||
"public-key": "",
|
"public-key": "",
|
||||||
"private-key-format": "infix-crypto-types:rsa-private-key-format",
|
"private-key-format": "infix-crypto-types:rsa-private-key-format",
|
||||||
"cleartext-private-key": "",
|
"cleartext-private-key": "",
|
||||||
"certificates": {}
|
"certificates": {}
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ietf-netconf-acm:nacm": {
|
"ietf-netconf-acm:nacm": {
|
||||||
"enable-nacm": true,
|
"enable-nacm": true,
|
||||||
@@ -152,7 +177,11 @@
|
|||||||
"name": "default-ssh",
|
"name": "default-ssh",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"tcp-server-parameters": {
|
"tcp-server-parameters": {
|
||||||
"local-address": "::"
|
"local-bind": [
|
||||||
|
{
|
||||||
|
"local-address": "::"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ssh-server-parameters": {
|
"ssh-server-parameters": {
|
||||||
"server-identity": {
|
"server-identity": {
|
||||||
@@ -244,7 +273,7 @@
|
|||||||
"policy": [
|
"policy": [
|
||||||
{
|
{
|
||||||
"name": "lan-to-wan",
|
"name": "lan-to-wan",
|
||||||
"action": "accept",
|
"action": "accept",
|
||||||
"ingress": [
|
"ingress": [
|
||||||
"lan"
|
"lan"
|
||||||
],
|
],
|
||||||
@@ -256,7 +285,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"infix-meta:meta": {
|
"infix-meta:meta": {
|
||||||
"version": "1.6"
|
"version": "1.7"
|
||||||
},
|
},
|
||||||
"infix-services:mdns": {
|
"infix-services:mdns": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ To configure WiFi as a client, first store your WiFi password in the keystore:
|
|||||||
admin@infix:/> configure
|
admin@infix:/> configure
|
||||||
admin@infix:/config/> edit keystore symmetric-key mywifi
|
admin@infix:/config/> edit keystore symmetric-key mywifi
|
||||||
admin@infix:/config/keystore/…/mywifi/> set key-format wifi-preshared-key-format
|
admin@infix:/config/keystore/…/mywifi/> set key-format wifi-preshared-key-format
|
||||||
admin@infix:/config/keystore/…/mywifi/> set cleartext-key YourWiFiPassword
|
admin@infix:/config/keystore/…/mywifi/> set cleartext-symmetric-key YourWiFiPassword
|
||||||
admin@infix:/config/keystore/…/mywifi/> leave
|
admin@infix:/config/keystore/…/mywifi/> leave
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+31
-10
@@ -41,14 +41,31 @@
|
|||||||
"ietf-ip:ipv4": {
|
"ietf-ip:ipv4": {
|
||||||
"infix-dhcp-client:dhcp": {
|
"infix-dhcp-client:dhcp": {
|
||||||
"option": [
|
"option": [
|
||||||
{"id": "netmask"},
|
{
|
||||||
{"id": "broadcast"},
|
"id": "netmask"
|
||||||
{"id": "router"},
|
},
|
||||||
{"id": "domain"},
|
{
|
||||||
{"id": "hostname"},
|
"id": "broadcast"
|
||||||
{"id": "dns-server"},
|
},
|
||||||
{"id": "ntp-server"},
|
{
|
||||||
{"id": "vendor-class", "value": "Raspberry Pi 4 Model B"}
|
"id": "router"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "domain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hostname"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dns-server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ntp-server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vendor-class",
|
||||||
|
"value": "Raspberry Pi 4 Model B"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +143,11 @@
|
|||||||
"name": "default-ssh",
|
"name": "default-ssh",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"tcp-server-parameters": {
|
"tcp-server-parameters": {
|
||||||
"local-address": "::"
|
"local-bind": [
|
||||||
|
{
|
||||||
|
"local-address": "::"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ssh-server-parameters": {
|
"ssh-server-parameters": {
|
||||||
"server-identity": {
|
"server-identity": {
|
||||||
@@ -172,7 +193,7 @@
|
|||||||
"infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
|
"infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
|
||||||
},
|
},
|
||||||
"infix-meta:meta": {
|
"infix-meta:meta": {
|
||||||
"version": "1.6"
|
"version": "1.7"
|
||||||
},
|
},
|
||||||
"infix-services:mdns": {
|
"infix-services:mdns": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|||||||
+1
-1
Submodule buildroot updated: 5baee16c62...4166c59afd
@@ -9,6 +9,9 @@ All notable changes to the project are documented in this file.
|
|||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Upgrade Linux kernel to 6.12.62 (LTS)
|
- Upgrade Linux kernel to 6.12.62 (LTS)
|
||||||
|
- Upgrade libyang to 4.2.2
|
||||||
|
- Upgrade sysrepo to 4.2.10
|
||||||
|
- Upgrade netopeer2 (NETCONF) to 2.7.0
|
||||||
- Add RIPv2 routing support, issue #582
|
- Add RIPv2 routing support, issue #582
|
||||||
- Add support for configurable OSPF debug logging, issue #1281. Debug options
|
- Add support for configurable OSPF debug logging, issue #1281. Debug options
|
||||||
can now be enabled per category (bfd, packet, ism, nsm, default-information,
|
can now be enabled per category (bfd, packet, ism, nsm, default-information,
|
||||||
|
|||||||
+1
-1
@@ -84,7 +84,7 @@ and 63 characters
|
|||||||
admin@example:/> configure
|
admin@example:/> configure
|
||||||
admin@example:/config/> edit keystore symmetric-key example
|
admin@example:/config/> edit keystore symmetric-key example
|
||||||
admin@example:/config/keystore/…/example/> set key-format wifi-preshared-key-format
|
admin@example:/config/keystore/…/example/> set key-format wifi-preshared-key-format
|
||||||
admin@example:/config/keystore/…/example/> set cleartext-key mysecret
|
admin@example:/config/keystore/…/example/> set cleartext-symmetric-key mysecret
|
||||||
admin@example:/config/keystore/…/example/> leave
|
admin@example:/config/keystore/…/example/> leave
|
||||||
admin@example:/>
|
admin@example:/>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
CONFD_VERSION = 1.6
|
CONFD_VERSION = 1.7
|
||||||
CONFD_SITE_METHOD = local
|
CONFD_SITE_METHOD = local
|
||||||
CONFD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/confd
|
CONFD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/confd
|
||||||
CONFD_LICENSE = BSD-3-Clause
|
CONFD_LICENSE = BSD-3-Clause
|
||||||
CONFD_LICENSE_FILES = LICENSE
|
CONFD_LICENSE_FILES = LICENSE
|
||||||
CONFD_REDISTRIBUTE = NO
|
CONFD_REDISTRIBUTE = NO
|
||||||
CONFD_DEPENDENCIES = host-sysrepo sysrepo netopeer2 jansson libite sysrepo libsrx libglib2
|
CONFD_DEPENDENCIES = host-sysrepo sysrepo rousette netopeer2 jansson libite sysrepo libsrx libglib2
|
||||||
CONFD_AUTORECONF = YES
|
CONFD_AUTORECONF = YES
|
||||||
CONFD_CONF_OPTS += --disable-silent-rules --with-crypt=$(BR2_PACKAGE_CONFD_DEFAULT_CRYPT)
|
CONFD_CONF_OPTS += --disable-silent-rules --with-crypt=$(BR2_PACKAGE_CONFD_DEFAULT_CRYPT)
|
||||||
CONFD_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_confd
|
CONFD_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_confd
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Locally calculated
|
# Locally calculated
|
||||||
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
||||||
sha256 cfe1064849a158f3eb9e82f12a709723115fc9bc4d30056e0e96ea0ba03672a2 klish-plugin-sysrepo-4e86a836c9b09a14c9625ea3d3b56b6578be4833-git4.tar.gz
|
sha256 71802708c96521a8bf43e38d6e5ac36de5481e087eaabdbebbc282ec0e29b8d3 klish-plugin-sysrepo-0846fa6b8d5d665eebfc7b1664542646915a79f7-git4.tar.gz
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
KLISH_PLUGIN_SYSREPO_VERSION = 4e86a836c9b09a14c9625ea3d3b56b6578be4833
|
KLISH_PLUGIN_SYSREPO_VERSION = 0846fa6b8d5d665eebfc7b1664542646915a79f7
|
||||||
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
|
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
|
||||||
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
|
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
|
||||||
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
|
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
From 22b41e7ed8268b94ccc139138e2037c390a3a616 Mon Sep 17 00:00:00 2001
|
From 22b41e7ed8268b94ccc139138e2037c390a3a616 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||||
Date: Thu, 10 Jul 2025 15:00:45 +0200
|
Date: Thu, 10 Jul 2025 15:00:45 +0200
|
||||||
Subject: [PATCH] Implement `SchemaNode::actionRpcs()`
|
Subject: [PATCH 1/7] Implement `SchemaNode::actionRpcs()`
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
From 1f8e502cf0308faa0336b1077308184de6a83ee0 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Mon, 20 Oct 2025 11:45:06 +0200
|
||||||
|
Subject: [PATCH 2/7] CI: pin to libyang v3
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: Idba085ad9c9c137874801f1d4089f8ee6bb390c7
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
.zuul.yaml | 4 ++--
|
||||||
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||||
|
index 7b92766..fd89549 100644
|
||||||
|
--- a/.zuul.yaml
|
||||||
|
+++ b/.zuul.yaml
|
||||||
|
@@ -4,13 +4,13 @@
|
||||||
|
- f38-gcc-cover:
|
||||||
|
required-projects:
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: devel
|
||||||
|
+ override-checkout: cesnet/2025-08-07
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.3.6
|
||||||
|
- f38-clang-asan-ubsan:
|
||||||
|
required-projects: &projects
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: devel
|
||||||
|
+ override-checkout: cesnet/2025-08-07
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.4.11
|
||||||
|
- f38-clang-tsan:
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
From b27a65d5d5f6ffffbd2c26c1b5bea57a6de584d0 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Mon, 20 Oct 2025 18:46:38 +0200
|
||||||
|
Subject: [PATCH 3/7] wrap lyd_validate_op
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
This patch wraps lyd_validate_op from libyang. Downstream users can now
|
||||||
|
validate operations (RPC input, replies, and notifications).
|
||||||
|
|
||||||
|
Change-Id: Ib03070bbc3e1d0dccd6bf38eda82e944db1093b1
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
include/libyang-cpp/DataNode.hpp | 2 +
|
||||||
|
src/DataNode.cpp | 24 +++++++++++
|
||||||
|
tests/context.cpp | 2 +-
|
||||||
|
tests/data_node.cpp | 72 ++++++++++++++++++++++++++++++++
|
||||||
|
tests/example_schema.hpp | 22 ++++++++++
|
||||||
|
5 files changed, 121 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||||
|
index 50d6c0e..e202a96 100644
|
||||||
|
--- a/include/libyang-cpp/DataNode.hpp
|
||||||
|
+++ b/include/libyang-cpp/DataNode.hpp
|
||||||
|
@@ -59,6 +59,7 @@ template <typename Operation, typename Siblings>
|
||||||
|
void handleLyTreeOperation(DataNode* affectedNode, Operation operation, Siblings siblings, std::shared_ptr<internal_refcount> newRefs);
|
||||||
|
|
||||||
|
LIBYANG_CPP_EXPORT void validateAll(std::optional<libyang::DataNode>& node, const std::optional<ValidationOptions>& opts = std::nullopt);
|
||||||
|
+LIBYANG_CPP_EXPORT void validateOp(libyang::DataNode& input, const std::optional<libyang::DataNode>& opsTree, OperationType opType);
|
||||||
|
|
||||||
|
LIBYANG_CPP_EXPORT Set<DataNode> findXPathAt(
|
||||||
|
const std::optional<libyang::DataNode>& contextNode,
|
||||||
|
@@ -147,6 +148,7 @@ public:
|
||||||
|
friend LIBYANG_CPP_EXPORT lyd_node* getRawNode(DataNode node);
|
||||||
|
|
||||||
|
friend LIBYANG_CPP_EXPORT void validateAll(std::optional<libyang::DataNode>& node, const std::optional<ValidationOptions>& opts);
|
||||||
|
+ friend LIBYANG_CPP_EXPORT void validateOp(libyang::DataNode& input, const std::optional<libyang::DataNode>& opsTree, OperationType opType);
|
||||||
|
friend LIBYANG_CPP_EXPORT Set<DataNode> findXPathAt(const std::optional<libyang::DataNode>& contextNode, const libyang::DataNode& forest, const std::string& xpath);
|
||||||
|
|
||||||
|
bool operator==(const DataNode& node) const;
|
||||||
|
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||||
|
index 7e87917..e28ef30 100644
|
||||||
|
--- a/src/DataNode.cpp
|
||||||
|
+++ b/src/DataNode.cpp
|
||||||
|
@@ -1226,6 +1226,30 @@ void validateAll(std::optional<libyang::DataNode>& node, const std::optional<Val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+/** @brief Validate op after parsing with lyd_parse_op.
|
||||||
|
+ *
|
||||||
|
+ * Wraps `lyd_validate_op`.
|
||||||
|
+ *
|
||||||
|
+ * @param input The tree with the op to validate.
|
||||||
|
+ * @param opsTree The optional data tree to validate the input against.
|
||||||
|
+ * @param opType The operation type. Contrary to `lyd_validate_op`, we accept not only YANG but also NETCONF and RESTCONF operation types (and internally convert them to YANG).
|
||||||
|
+ */
|
||||||
|
+void validateOp(libyang::DataNode& input, const std::optional<libyang::DataNode>& opsTree, OperationType opType)
|
||||||
|
+{
|
||||||
|
+ if (opType == OperationType::RpcYang || opType == OperationType::RpcRestconf || opType == OperationType::RpcNetconf) {
|
||||||
|
+ opType = OperationType::RpcYang;
|
||||||
|
+ } else if (opType == OperationType::ReplyYang || opType == OperationType::ReplyRestconf || opType == OperationType::ReplyNetconf) {
|
||||||
|
+ opType = OperationType::ReplyYang;
|
||||||
|
+ } else if (opType == OperationType::NotificationYang || opType == OperationType::NotificationRestconf || opType == OperationType::NotificationNetconf) {
|
||||||
|
+ opType = OperationType::NotificationYang;
|
||||||
|
+ } else {
|
||||||
|
+ throw Error("validateOp: DataYang datatype is not supported");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto ret = lyd_validate_op(input.m_node, opsTree ? opsTree->m_node : nullptr, utils::toOpType(opType), nullptr);
|
||||||
|
+ throwIfError(ret, "libyang:validateOp: lyd_validate_op failed");
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/** @short Find instances matching the provided XPath
|
||||||
|
*
|
||||||
|
* @param contextNode The node which serves as the "context node" for XPath evaluation. Use nullopt to start at root.
|
||||||
|
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||||
|
index 6c5dde8..c0b7e09 100644
|
||||||
|
--- a/tests/context.cpp
|
||||||
|
+++ b/tests/context.cpp
|
||||||
|
@@ -154,7 +154,7 @@ TEST_CASE("context")
|
||||||
|
{
|
||||||
|
auto mod = ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||||
|
auto rpcs = mod.actionRpcs();
|
||||||
|
- REQUIRE(rpcs.size() == 1);
|
||||||
|
+ REQUIRE(rpcs.size() == 2);
|
||||||
|
REQUIRE(rpcs[0].module().name() == "example-schema");
|
||||||
|
REQUIRE(rpcs[0].name() == "myRpc");
|
||||||
|
|
||||||
|
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||||
|
index db5a28e..9215b12 100644
|
||||||
|
--- a/tests/data_node.cpp
|
||||||
|
+++ b/tests/data_node.cpp
|
||||||
|
@@ -2430,6 +2430,78 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
"Can't parse into operation data tree: LY_EVALID", libyang::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ DOCTEST_SUBCASE("Validation")
|
||||||
|
+ {
|
||||||
|
+ DOCTEST_SUBCASE("Valid input")
|
||||||
|
+ {
|
||||||
|
+ std::string rpcInput;
|
||||||
|
+ std::string rpcPath;
|
||||||
|
+ std::string expected;
|
||||||
|
+ std::optional<libyang::DataNode> depTree;
|
||||||
|
+
|
||||||
|
+ DOCTEST_SUBCASE("RPC")
|
||||||
|
+ {
|
||||||
|
+ rpcInput = R"({"example-schema:input": { "number": 42 } })";
|
||||||
|
+ rpcPath = "/example-schema:rpc-with-choice";
|
||||||
|
+ expected = R"({
|
||||||
|
+ "example-schema:rpc-with-choice": {
|
||||||
|
+ "number": 42
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)";
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ DOCTEST_SUBCASE("Action")
|
||||||
|
+ {
|
||||||
|
+ rpcInput = R"({ "example-schema:input": { "friend": "Kuba" } })";
|
||||||
|
+ rpcPath = "/example-schema:person[name='Franta']/poke-a-friend";
|
||||||
|
+ expected = R"({
|
||||||
|
+ "example-schema:poke-a-friend": {
|
||||||
|
+ "friend": "Kuba"
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)";
|
||||||
|
+ depTree = ctx.newPath("/example-schema:person[name='Kuba']");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto [parent, rpcTree] = ctx.newPath2(rpcPath);
|
||||||
|
+ auto rpcOp = rpcTree->parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf);
|
||||||
|
+
|
||||||
|
+ REQUIRE(!rpcOp.op);
|
||||||
|
+ REQUIRE(rpcOp.tree);
|
||||||
|
+
|
||||||
|
+ libyang::validateOp(*rpcTree, depTree, libyang::OperationType::RpcRestconf);
|
||||||
|
+ REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == expected);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ DOCTEST_SUBCASE("Nodes in disjunctive cases defined together")
|
||||||
|
+ {
|
||||||
|
+ auto rpcInput = R"({ "example-schema:input": { "number": 42, "text": "The ultimate answer" } })";
|
||||||
|
+
|
||||||
|
+ auto rpcTree = ctx.newPath("/example-schema:rpc-with-choice");
|
||||||
|
+ auto rpcOp = rpcTree.parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf);
|
||||||
|
+
|
||||||
|
+ REQUIRE(!rpcOp.op);
|
||||||
|
+ REQUIRE(rpcOp.tree);
|
||||||
|
+
|
||||||
|
+ REQUIRE_THROWS_WITH_AS(libyang::validateOp(rpcTree, std::nullopt, libyang::OperationType::RpcRestconf), "libyang:validateOp: lyd_validate_op failed: LY_EVALID", libyang::Error);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ DOCTEST_SUBCASE("Action without the leafref node")
|
||||||
|
+ {
|
||||||
|
+ auto rpcInput = R"({ "example-schema:input": { "friend": "Kuba" } })";
|
||||||
|
+ auto rpcPath = "/example-schema:person[name='Franta']/poke-a-friend";
|
||||||
|
+
|
||||||
|
+ auto [parent, rpcTree] = ctx.newPath2(rpcPath);
|
||||||
|
+ auto rpcOp = rpcTree->parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf);
|
||||||
|
+
|
||||||
|
+ REQUIRE(!rpcOp.op);
|
||||||
|
+ REQUIRE(rpcOp.tree);
|
||||||
|
+
|
||||||
|
+ REQUIRE_THROWS_WITH_AS(libyang::validateOp(*rpcTree, std::nullopt, libyang::OperationType::RpcRestconf), "libyang:validateOp: lyd_validate_op failed: LY_EVALID", libyang::Error);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("comparing") {
|
||||||
|
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||||
|
index 02d3d74..acc6ecc 100644
|
||||||
|
--- a/tests/example_schema.hpp
|
||||||
|
+++ b/tests/example_schema.hpp
|
||||||
|
@@ -108,6 +108,16 @@ module example-schema {
|
||||||
|
}
|
||||||
|
|
||||||
|
action poke { }
|
||||||
|
+
|
||||||
|
+ action poke-a-friend {
|
||||||
|
+ input {
|
||||||
|
+ leaf friend {
|
||||||
|
+ type leafref {
|
||||||
|
+ path '../../../person/name';
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf bossPerson {
|
||||||
|
@@ -284,6 +294,18 @@ module example-schema {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+ rpc rpc-with-choice {
|
||||||
|
+ input {
|
||||||
|
+ choice the-impossible-choice {
|
||||||
|
+ leaf text {
|
||||||
|
+ type string;
|
||||||
|
+ }
|
||||||
|
+ leaf number {
|
||||||
|
+ type int32;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
|
||||||
|
anydata myData {
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
From 4c00c24dee6bbc694d62e7174b7ac14f8aa95f14 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 30 Oct 2025 21:16:13 +0100
|
||||||
|
Subject: [PATCH 4/7] port to libyang v4
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Apart from the usual API/ABI changes, the default values of a `leaf` or
|
||||||
|
a `leaf-list` are no longer canonicalized.
|
||||||
|
|
||||||
|
This used to be missing from libyang v4, but it was added recently.
|
||||||
|
Sill, chances are that the form which was used in the YANG model's
|
||||||
|
source code is actually *the* intended presentation, so let's skip
|
||||||
|
canonicalization altogether.
|
||||||
|
|
||||||
|
Upstream has removed some flags and defines, so let's sync their list
|
||||||
|
with whatever is currently available in the `devel` branch.
|
||||||
|
|
||||||
|
Change-Id: I52dcec88eeee003b45dd6cf400f4d6875abbcc05
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
.zuul.yaml | 4 ++--
|
||||||
|
CMakeLists.txt | 4 ++--
|
||||||
|
README.md | 2 +-
|
||||||
|
include/libyang-cpp/Context.hpp | 6 +++++-
|
||||||
|
include/libyang-cpp/DataNode.hpp | 5 ++++-
|
||||||
|
include/libyang-cpp/Enum.hpp | 15 +++++++++++++--
|
||||||
|
src/Context.cpp | 12 ++++++++++--
|
||||||
|
src/DataNode.cpp | 11 +++++++++--
|
||||||
|
src/SchemaNode.cpp | 6 +++---
|
||||||
|
src/Type.cpp | 2 +-
|
||||||
|
src/utils/enum.hpp | 21 +++++++++++++++++++--
|
||||||
|
tests/context.cpp | 32 +++++++++++++++++---------------
|
||||||
|
tests/data_node.cpp | 2 +-
|
||||||
|
tests/schema_node.cpp | 4 ++--
|
||||||
|
14 files changed, 89 insertions(+), 37 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||||
|
index fd89549..7b92766 100644
|
||||||
|
--- a/.zuul.yaml
|
||||||
|
+++ b/.zuul.yaml
|
||||||
|
@@ -4,13 +4,13 @@
|
||||||
|
- f38-gcc-cover:
|
||||||
|
required-projects:
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: cesnet/2025-08-07
|
||||||
|
+ override-checkout: devel
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.3.6
|
||||||
|
- f38-clang-asan-ubsan:
|
||||||
|
required-projects: &projects
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: cesnet/2025-08-07
|
||||||
|
+ override-checkout: devel
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.4.11
|
||||||
|
- f38-clang-tsan:
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index c6db320..90f9aaa 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake
|
||||||
|
cmake/ProjectGitVersionRunner.cmake
|
||||||
|
)
|
||||||
|
include(cmake/ProjectGitVersion.cmake)
|
||||||
|
-set(LIBYANG_CPP_PKG_VERSION "4")
|
||||||
|
+set(LIBYANG_CPP_PKG_VERSION "5")
|
||||||
|
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||||
|
|
||||||
|
find_package(Doxygen)
|
||||||
|
@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||||
|
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET)
|
||||||
|
|
||||||
|
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||||
|
find_package(date)
|
||||||
|
diff --git a/README.md b/README.md
|
||||||
|
index 8291e00..0cef2ff 100644
|
||||||
|
--- a/README.md
|
||||||
|
+++ b/README.md
|
||||||
|
@@ -8,7 +8,7 @@
|
||||||
|
Object lifetimes are managed automatically via RAII.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
-- [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
|
||||||
|
+- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
|
||||||
|
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||||
|
- CMake 3.19+
|
||||||
|
- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||||
|
diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp
|
||||||
|
index ca89063..e695914 100644
|
||||||
|
--- a/include/libyang-cpp/Context.hpp
|
||||||
|
+++ b/include/libyang-cpp/Context.hpp
|
||||||
|
@@ -108,7 +108,11 @@ public:
|
||||||
|
std::optional<SubmoduleParsed> getSubmodule(const std::string& name, const std::optional<std::string>& revision) const;
|
||||||
|
void registerModuleCallback(std::function<ModuleCallback> callback);
|
||||||
|
|
||||||
|
- ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const;
|
||||||
|
+ ParsedOp parseOp(
|
||||||
|
+ const std::string& input,
|
||||||
|
+ const DataFormat format,
|
||||||
|
+ const OperationType opType,
|
||||||
|
+ const std::optional<ParseOptions> parseOpts = std::nullopt) const;
|
||||||
|
|
||||||
|
DataNode newPath(const std::string& path, const std::optional<std::string>& value = std::nullopt, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||||
|
CreatedNodes newPath2(const std::string& path, const std::optional<std::string>& value = std::nullopt, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||||
|
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||||
|
index e202a96..35e5936 100644
|
||||||
|
--- a/include/libyang-cpp/DataNode.hpp
|
||||||
|
+++ b/include/libyang-cpp/DataNode.hpp
|
||||||
|
@@ -123,7 +123,10 @@ public:
|
||||||
|
Collection<DataNode, IterationType::Sibling> siblings() const;
|
||||||
|
Collection<DataNode, IterationType::Sibling> immediateChildren() const;
|
||||||
|
|
||||||
|
- ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const;
|
||||||
|
+ ParsedOp parseOp(const std::string& input,
|
||||||
|
+ const DataFormat format,
|
||||||
|
+ const OperationType opType,
|
||||||
|
+ const std::optional<ParseOptions> parseOpts = std::nullopt) const;
|
||||||
|
|
||||||
|
void parseSubtree(
|
||||||
|
const std::string& data,
|
||||||
|
diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp
|
||||||
|
index 85dcc83..32142f9 100644
|
||||||
|
--- a/include/libyang-cpp/Enum.hpp
|
||||||
|
+++ b/include/libyang-cpp/Enum.hpp
|
||||||
|
@@ -136,6 +136,7 @@ enum class DuplicationOptions : uint32_t {
|
||||||
|
WithFlags = 0x08,
|
||||||
|
NoExt = 0x10,
|
||||||
|
WithPriv = 0x20,
|
||||||
|
+ NoLyds = 0x40,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NodeType : uint16_t {
|
||||||
|
@@ -167,6 +168,13 @@ enum class ContextOptions : uint16_t {
|
||||||
|
PreferSearchDirs = 0x20,
|
||||||
|
SetPrivParsed = 0x40,
|
||||||
|
ExplicitCompile = 0x80,
|
||||||
|
+ EnableImpFeatures = 0x100,
|
||||||
|
+ CompileObsolete = 0x200,
|
||||||
|
+ LybHashes = 0x400,
|
||||||
|
+ LeafrefExtended = 0x800,
|
||||||
|
+ LeafrefLinking = 0x1000,
|
||||||
|
+ BuiltinPluginsOnly = 0x2000,
|
||||||
|
+ StaticPluginsOnly = 0x4000,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -203,7 +211,6 @@ enum class AnydataValueType : uint32_t {
|
||||||
|
String,
|
||||||
|
XML,
|
||||||
|
JSON,
|
||||||
|
- LYB
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -252,6 +259,7 @@ enum class ValidationOptions {
|
||||||
|
MultiError = 0x0004,
|
||||||
|
Operational = 0x0008,
|
||||||
|
NoDefaults = 0x0010,
|
||||||
|
+ NotFinal = 0x0020,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
@@ -262,11 +270,14 @@ enum class ParseOptions {
|
||||||
|
Strict = 0x020000,
|
||||||
|
Opaque = 0x040000,
|
||||||
|
NoState = 0x080000,
|
||||||
|
- LybModUpdate = 0x100000,
|
||||||
|
+ LybSkipCtxCheck = 0x100000,
|
||||||
|
Ordered = 0x200000,
|
||||||
|
Subtree = 0x400000, /**< Do not use this one for parsing of data subtrees */
|
||||||
|
WhenTrue = 0x800000,
|
||||||
|
NoNew = 0x1000000,
|
||||||
|
+ StoreOnly = 0x2010000,
|
||||||
|
+ JsonNull = 0x4000000,
|
||||||
|
+ JsonStringDataTypes = 0x8000000,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
diff --git a/src/Context.cpp b/src/Context.cpp
|
||||||
|
index fec2f27..f5bdc71 100644
|
||||||
|
--- a/src/Context.cpp
|
||||||
|
+++ b/src/Context.cpp
|
||||||
|
@@ -239,7 +239,7 @@ std::optional<DataNode> Context::parseExtData(
|
||||||
|
* Note: to parse a NETCONF RPC reply, you MUST parse the original NETCONF RPC request (that is, you have to use
|
||||||
|
* this method with OperationType::RpcNetconf).
|
||||||
|
*/
|
||||||
|
-ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const
|
||||||
|
+ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional<ParseOptions> parseOpts) const
|
||||||
|
{
|
||||||
|
auto in = wrap_ly_in_new_memory(input);
|
||||||
|
|
||||||
|
@@ -251,7 +251,15 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con
|
||||||
|
case OperationType::NotificationYang: {
|
||||||
|
lyd_node* op = nullptr;
|
||||||
|
lyd_node* tree = nullptr;
|
||||||
|
- auto err = lyd_parse_op(m_ctx.get(), nullptr, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, &op);
|
||||||
|
+ auto err = lyd_parse_op(
|
||||||
|
+ m_ctx.get(),
|
||||||
|
+ nullptr,
|
||||||
|
+ in.get(),
|
||||||
|
+ utils::toLydFormat(format),
|
||||||
|
+ utils::toOpType(opType),
|
||||||
|
+ parseOpts ? utils::toParseOptions(*parseOpts) : 0,
|
||||||
|
+ &tree,
|
||||||
|
+ &op);
|
||||||
|
|
||||||
|
ParsedOp res;
|
||||||
|
res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt;
|
||||||
|
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||||
|
index e28ef30..edabf9b 100644
|
||||||
|
--- a/src/DataNode.cpp
|
||||||
|
+++ b/src/DataNode.cpp
|
||||||
|
@@ -391,7 +391,7 @@ DataNodeAny DataNode::asAny() const
|
||||||
|
*
|
||||||
|
* Wraps `lyd_parse_op`.
|
||||||
|
*/
|
||||||
|
-ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const
|
||||||
|
+ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional<ParseOptions> parseOpts) const
|
||||||
|
{
|
||||||
|
auto in = wrap_ly_in_new_memory(input);
|
||||||
|
|
||||||
|
@@ -401,7 +401,14 @@ ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, co
|
||||||
|
case OperationType::ReplyRestconf: {
|
||||||
|
lyd_node* op = nullptr;
|
||||||
|
lyd_node* tree = nullptr;
|
||||||
|
- auto err = lyd_parse_op(m_node->schema->module->ctx, m_node, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, nullptr);
|
||||||
|
+ auto err = lyd_parse_op(m_node->schema->module->ctx,
|
||||||
|
+ m_node,
|
||||||
|
+ in.get(),
|
||||||
|
+ utils::toLydFormat(format),
|
||||||
|
+ utils::toOpType(opType),
|
||||||
|
+ parseOpts ? utils::toParseOptions(*parseOpts) : 0,
|
||||||
|
+ &tree,
|
||||||
|
+ nullptr);
|
||||||
|
ParsedOp res{
|
||||||
|
.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt,
|
||||||
|
.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt
|
||||||
|
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||||
|
index ce24203..2e0351a 100644
|
||||||
|
--- a/src/SchemaNode.cpp
|
||||||
|
+++ b/src/SchemaNode.cpp
|
||||||
|
@@ -564,7 +564,7 @@ std::vector<std::string> LeafList::defaultValuesStr() const
|
||||||
|
auto dflts = reinterpret_cast<const lysc_node_leaflist*>(m_node)->dflts;
|
||||||
|
std::vector<std::string> res;
|
||||||
|
for (const auto& it : std::span(dflts, LY_ARRAY_COUNT(dflts))) {
|
||||||
|
- res.emplace_back(lyd_value_get_canonical(m_ctx.get(), it));
|
||||||
|
+ res.emplace_back(it.str);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
@@ -660,8 +660,8 @@ bool LeafList::isUserOrdered() const
|
||||||
|
std::optional<std::string> Leaf::defaultValueStr() const
|
||||||
|
{
|
||||||
|
auto dflt = reinterpret_cast<const lysc_node_leaf*>(m_node)->dflt;
|
||||||
|
- if (dflt) {
|
||||||
|
- return lyd_value_get_canonical(m_ctx.get(), dflt);
|
||||||
|
+ if (dflt.str) {
|
||||||
|
+ return std::string{dflt.str};
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
diff --git a/src/Type.cpp b/src/Type.cpp
|
||||||
|
index 01b2c4f..4d2db46 100644
|
||||||
|
--- a/src/Type.cpp
|
||||||
|
+++ b/src/Type.cpp
|
||||||
|
@@ -240,7 +240,7 @@ std::optional<std::string> Type::description() const
|
||||||
|
*/
|
||||||
|
std::string Type::internalPluginId() const
|
||||||
|
{
|
||||||
|
- return m_type->plugin->id;
|
||||||
|
+ return lysc_get_type_plugin(m_type->plugin_ref)->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
|
||||||
|
index 5e02d24..0b2e2d7 100644
|
||||||
|
--- a/src/utils/enum.hpp
|
||||||
|
+++ b/src/utils/enum.hpp
|
||||||
|
@@ -91,6 +91,7 @@ static_assert(LYD_DUP_WITH_FLAGS == toDuplicationOptions(DuplicationOptions::Wit
|
||||||
|
static_assert(LYD_DUP_WITH_PARENTS == toDuplicationOptions(DuplicationOptions::WithParents));
|
||||||
|
static_assert(LYD_DUP_NO_EXT == toDuplicationOptions(DuplicationOptions::NoExt));
|
||||||
|
static_assert(LYD_DUP_WITH_PRIV == toDuplicationOptions(DuplicationOptions::WithPriv));
|
||||||
|
+static_assert(LYD_DUP_NO_LYDS == toDuplicationOptions(DuplicationOptions::NoLyds));
|
||||||
|
|
||||||
|
static_assert((LYD_DUP_NO_META | LYD_DUP_NO_EXT) ==
|
||||||
|
toDuplicationOptions(DuplicationOptions::NoMeta | DuplicationOptions::NoExt));
|
||||||
|
@@ -131,6 +132,13 @@ static_assert(toContextOptions(ContextOptions::DisableSearchCwd) == LY_CTX_DISAB
|
||||||
|
static_assert(toContextOptions(ContextOptions::PreferSearchDirs) == LY_CTX_PREFER_SEARCHDIRS);
|
||||||
|
static_assert(toContextOptions(ContextOptions::SetPrivParsed) == LY_CTX_SET_PRIV_PARSED);
|
||||||
|
static_assert(toContextOptions(ContextOptions::ExplicitCompile) == LY_CTX_EXPLICIT_COMPILE);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::EnableImpFeatures) == LY_CTX_ENABLE_IMP_FEATURES);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::CompileObsolete) == LY_CTX_COMPILE_OBSOLETE);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::LybHashes) == LY_CTX_LYB_HASHES);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::LeafrefExtended) == LY_CTX_LEAFREF_EXTENDED);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::LeafrefLinking) == LY_CTX_LEAFREF_LINKING);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::BuiltinPluginsOnly) == LY_CTX_BUILTIN_PLUGINS_ONLY);
|
||||||
|
+static_assert(toContextOptions(ContextOptions::StaticPluginsOnly) == LY_CTX_STATIC_PLUGINS_ONLY);
|
||||||
|
|
||||||
|
constexpr uint16_t toLogOptions(const LogOptions options)
|
||||||
|
{
|
||||||
|
@@ -198,6 +206,10 @@ constexpr uint32_t toValidationOptions(const ValidationOptions opts)
|
||||||
|
|
||||||
|
static_assert(toValidationOptions(ValidationOptions::NoState) == LYD_VALIDATE_NO_STATE);
|
||||||
|
static_assert(toValidationOptions(ValidationOptions::Present) == LYD_VALIDATE_PRESENT);
|
||||||
|
+static_assert(toValidationOptions(ValidationOptions::MultiError) == LYD_VALIDATE_MULTI_ERROR);
|
||||||
|
+static_assert(toValidationOptions(ValidationOptions::Operational) == LYD_VALIDATE_OPERATIONAL);
|
||||||
|
+static_assert(toValidationOptions(ValidationOptions::NoDefaults) == LYD_VALIDATE_NO_DEFAULTS);
|
||||||
|
+static_assert(toValidationOptions(ValidationOptions::NotFinal) == LYD_VALIDATE_NOT_FINAL);
|
||||||
|
|
||||||
|
constexpr uint32_t toParseOptions(const ParseOptions opts)
|
||||||
|
{
|
||||||
|
@@ -208,8 +220,14 @@ static_assert(toParseOptions(ParseOptions::ParseOnly) == LYD_PARSE_ONLY);
|
||||||
|
static_assert(toParseOptions(ParseOptions::Strict) == LYD_PARSE_STRICT);
|
||||||
|
static_assert(toParseOptions(ParseOptions::Opaque) == LYD_PARSE_OPAQ);
|
||||||
|
static_assert(toParseOptions(ParseOptions::NoState) == LYD_PARSE_NO_STATE);
|
||||||
|
-static_assert(toParseOptions(ParseOptions::LybModUpdate) == LYD_PARSE_LYB_MOD_UPDATE);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::LybSkipCtxCheck) == LYD_PARSE_LYB_SKIP_CTX_CHECK);
|
||||||
|
static_assert(toParseOptions(ParseOptions::Ordered) == LYD_PARSE_ORDERED);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::Subtree) == LYD_PARSE_SUBTREE);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::WhenTrue) == LYD_PARSE_WHEN_TRUE);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::NoNew) == LYD_PARSE_NO_NEW);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::StoreOnly) == LYD_PARSE_STORE_ONLY);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::JsonNull) == LYD_PARSE_JSON_NULL);
|
||||||
|
+static_assert(toParseOptions(ParseOptions::JsonStringDataTypes) == LYD_PARSE_JSON_STRING_DATATYPES);
|
||||||
|
|
||||||
|
constexpr lyd_type toOpType(const OperationType type)
|
||||||
|
{
|
||||||
|
@@ -242,7 +260,6 @@ static_assert(toAnydataValueType(AnydataValueType::DataTree) == LYD_ANYDATA_DATA
|
||||||
|
static_assert(toAnydataValueType(AnydataValueType::String) == LYD_ANYDATA_STRING);
|
||||||
|
static_assert(toAnydataValueType(AnydataValueType::XML) == LYD_ANYDATA_XML);
|
||||||
|
static_assert(toAnydataValueType(AnydataValueType::JSON) == LYD_ANYDATA_JSON);
|
||||||
|
-static_assert(toAnydataValueType(AnydataValueType::LYB) == LYD_ANYDATA_LYB);
|
||||||
|
|
||||||
|
constexpr LYS_OUTFORMAT toLysOutFormat(const SchemaOutputFormat format)
|
||||||
|
{
|
||||||
|
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||||
|
index c0b7e09..eaedebf 100644
|
||||||
|
--- a/tests/context.cpp
|
||||||
|
+++ b/tests/context.cpp
|
||||||
|
@@ -359,25 +359,27 @@ TEST_CASE("context")
|
||||||
|
ctx->loadModule("mod1", std::nullopt, {});
|
||||||
|
ctx->parseModule(valid_yang_model, libyang::SchemaFormat::YANG);
|
||||||
|
auto modules = ctx->modules();
|
||||||
|
- REQUIRE(modules.size() == 8);
|
||||||
|
+ REQUIRE(modules.size() == 9);
|
||||||
|
REQUIRE(modules.at(0).name() == "ietf-yang-metadata");
|
||||||
|
REQUIRE(modules.at(0).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-metadata");
|
||||||
|
REQUIRE(modules.at(1).name() == "yang");
|
||||||
|
REQUIRE(modules.at(1).ns() == "urn:ietf:params:xml:ns:yang:1");
|
||||||
|
- REQUIRE(modules.at(2).name() == "ietf-inet-types");
|
||||||
|
- REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types");
|
||||||
|
- REQUIRE(modules.at(3).name() == "ietf-yang-types");
|
||||||
|
- REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types");
|
||||||
|
- REQUIRE(modules.at(4).name() == "ietf-yang-schema-mount");
|
||||||
|
- REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount");
|
||||||
|
- REQUIRE(modules.at(5).name() == "ietf-yang-structure-ext");
|
||||||
|
- REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext");
|
||||||
|
- REQUIRE(modules.at(6).name() == "mod1");
|
||||||
|
- REQUIRE(modules.at(6).ns() == "http://example.com");
|
||||||
|
- REQUIRE(*modules.at(6).revision() == "2021-11-15");
|
||||||
|
- REQUIRE(modules.at(7).name() == "test");
|
||||||
|
+ REQUIRE(modules.at(2).name() == "default");
|
||||||
|
+ REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:netconf:default:1.0");
|
||||||
|
+ REQUIRE(modules.at(3).name() == "ietf-inet-types");
|
||||||
|
+ REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types");
|
||||||
|
+ REQUIRE(modules.at(4).name() == "ietf-yang-types");
|
||||||
|
+ REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types");
|
||||||
|
+ REQUIRE(modules.at(5).name() == "ietf-yang-schema-mount");
|
||||||
|
+ REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount");
|
||||||
|
+ REQUIRE(modules.at(6).name() == "ietf-yang-structure-ext");
|
||||||
|
+ REQUIRE(modules.at(6).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext");
|
||||||
|
+ REQUIRE(modules.at(7).name() == "mod1");
|
||||||
|
REQUIRE(modules.at(7).ns() == "http://example.com");
|
||||||
|
- REQUIRE(modules.at(7).revision() == std::nullopt);
|
||||||
|
+ REQUIRE(*modules.at(7).revision() == "2021-11-15");
|
||||||
|
+ REQUIRE(modules.at(8).name() == "test");
|
||||||
|
+ REQUIRE(modules.at(8).ns() == "http://example.com");
|
||||||
|
+ REQUIRE(modules.at(8).revision() == std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("Module comparison")
|
||||||
|
@@ -704,7 +706,7 @@ TEST_CASE("context")
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("schema printing")
|
||||||
|
{
|
||||||
|
- std::optional<libyang::Context> ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed};
|
||||||
|
+ std::optional<libyang::Context> ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::CompileObsolete};
|
||||||
|
auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||||
|
|
||||||
|
REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module
|
||||||
|
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||||
|
index 9215b12..88ca5b9 100644
|
||||||
|
--- a/tests/data_node.cpp
|
||||||
|
+++ b/tests/data_node.cpp
|
||||||
|
@@ -2426,7 +2426,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
"WTF": "foo bar baz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf),
|
||||||
|
+ )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf, libyang::ParseOptions::Strict),
|
||||||
|
"Can't parse into operation data tree: LY_EVALID", libyang::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||||
|
index 65ef462..58152a4 100644
|
||||||
|
--- a/tests/schema_node.cpp
|
||||||
|
+++ b/tests/schema_node.cpp
|
||||||
|
@@ -19,7 +19,7 @@ using namespace std::string_literals;
|
||||||
|
TEST_CASE("SchemaNode")
|
||||||
|
{
|
||||||
|
std::optional<libyang::Context> ctx{std::in_place, std::nullopt,
|
||||||
|
- libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
|
||||||
|
+ libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::CompileObsolete};
|
||||||
|
std::optional<libyang::Context> ctxWithParsed{std::in_place, std::nullopt,
|
||||||
|
libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
|
||||||
|
ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||||
|
@@ -841,7 +841,7 @@ TEST_CASE("SchemaNode")
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("LeafList::defaultValuesStr")
|
||||||
|
{
|
||||||
|
- REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector<std::string>{"-1", "512", "1024", "2048"});
|
||||||
|
+ REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector<std::string>{"-1", "+512", "0x400", "04000"});
|
||||||
|
REQUIRE(ctx->findPath("/type_module:leafListBasic").asLeafList().defaultValuesStr().size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
From 51d7457bd281e34bac92ff8231978766c7784657 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Martin Bohal <mb@mbohal.cz>
|
||||||
|
Date: Tue, 4 Nov 2025 14:26:05 +0100
|
||||||
|
Subject: [PATCH 5/7] Add SchemaNode::isOutput() method
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
This commit introduces the `isOutput()` method to the `SchemaNode`
|
||||||
|
class, which checks whether the schema node is part of an output
|
||||||
|
statement subtree.
|
||||||
|
|
||||||
|
It complements an already existing `isInput()` method.
|
||||||
|
|
||||||
|
Change-Id: I8fe75f2e607c0159d8295d685a59d301155ca92e
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
include/libyang-cpp/SchemaNode.hpp | 1 +
|
||||||
|
src/SchemaNode.cpp | 10 ++++++++++
|
||||||
|
tests/schema_node.cpp | 7 +++++++
|
||||||
|
3 files changed, 18 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
|
||||||
|
index 9f49fd7..db5a63e 100644
|
||||||
|
--- a/include/libyang-cpp/SchemaNode.hpp
|
||||||
|
+++ b/include/libyang-cpp/SchemaNode.hpp
|
||||||
|
@@ -58,6 +58,7 @@ public:
|
||||||
|
Status status() const;
|
||||||
|
Config config() const;
|
||||||
|
bool isInput() const;
|
||||||
|
+ bool isOutput() const;
|
||||||
|
NodeType nodeType() const;
|
||||||
|
// It is possible to cast SchemaNode to another type via the following methods. The types are children classes of
|
||||||
|
// SchemaNode. No problems with slicing can occur, because these types are value-based and aren't constructible
|
||||||
|
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||||
|
index 2e0351a..9241b23 100644
|
||||||
|
--- a/src/SchemaNode.cpp
|
||||||
|
+++ b/src/SchemaNode.cpp
|
||||||
|
@@ -193,6 +193,16 @@ bool SchemaNode::isInput() const
|
||||||
|
return m_node->flags & LYS_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * @brief Checks whether this node is inside a subtree of an output statement.
|
||||||
|
+ *
|
||||||
|
+ * Wraps `LYS_OUTPUT`.
|
||||||
|
+ */
|
||||||
|
+bool SchemaNode::isOutput() const
|
||||||
|
+{
|
||||||
|
+ return m_node->flags & LYS_OUTPUT;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Returns the node type of this node (e.g. leaf, container...).
|
||||||
|
*
|
||||||
|
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||||
|
index 58152a4..01c2f19 100644
|
||||||
|
--- a/tests/schema_node.cpp
|
||||||
|
+++ b/tests/schema_node.cpp
|
||||||
|
@@ -135,6 +135,13 @@ TEST_CASE("SchemaNode")
|
||||||
|
REQUIRE(!ctx->findPath("/type_module:leafString").isInput());
|
||||||
|
}
|
||||||
|
|
||||||
|
+ DOCTEST_SUBCASE("SchemaNode::isOutput")
|
||||||
|
+ {
|
||||||
|
+ REQUIRE(ctx->findPath("/example-schema:myRpc/outputLeaf", libyang::InputOutputNodes::Output).isOutput());
|
||||||
|
+ REQUIRE(!ctx->findPath("/example-schema:myRpc/inputLeaf").isOutput());
|
||||||
|
+ REQUIRE(!ctx->findPath("/type_module:leafString").isOutput());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
DOCTEST_SUBCASE("SchemaNode::module")
|
||||||
|
{
|
||||||
|
REQUIRE(ctx->findPath("/type_module:leafString").module().name() == "type_module");
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
From 38d5c10009df602594e1b9a53bac41111a9ba430 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 20 Nov 2025 15:32:03 +0100
|
||||||
|
Subject: [PATCH 6/7] adapt to changes in libyang v4.2
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
I was *so* tempted to leave the C++ enums as-is, just to save us from
|
||||||
|
this needless sed exercise, but hey, let's not start confusing any
|
||||||
|
possible downstream consumers now that upstream has changed these
|
||||||
|
identifiers.
|
||||||
|
|
||||||
|
Change-Id: I9b34585822b41be0ca1578051f1c0eb8588a0a51
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 4 +--
|
||||||
|
README.md | 2 +-
|
||||||
|
include/libyang-cpp/Enum.hpp | 4 +--
|
||||||
|
src/utils/enum.hpp | 4 +--
|
||||||
|
tests/context.cpp | 10 +++----
|
||||||
|
tests/data_node.cpp | 58 ++++++++++++++++++------------------
|
||||||
|
6 files changed, 41 insertions(+), 41 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 90f9aaa..cbaf82a 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake
|
||||||
|
cmake/ProjectGitVersionRunner.cmake
|
||||||
|
)
|
||||||
|
include(cmake/ProjectGitVersion.cmake)
|
||||||
|
-set(LIBYANG_CPP_PKG_VERSION "5")
|
||||||
|
+set(LIBYANG_CPP_PKG_VERSION "6")
|
||||||
|
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||||
|
|
||||||
|
find_package(Doxygen)
|
||||||
|
@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||||
|
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
-pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(LIBYANG REQUIRED libyang>=4.2.1 IMPORTED_TARGET)
|
||||||
|
|
||||||
|
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||||
|
find_package(date)
|
||||||
|
diff --git a/README.md b/README.md
|
||||||
|
index 0cef2ff..842573f 100644
|
||||||
|
--- a/README.md
|
||||||
|
+++ b/README.md
|
||||||
|
@@ -8,7 +8,7 @@
|
||||||
|
Object lifetimes are managed automatically via RAII.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
-- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
|
||||||
|
+- [libyang v4.2+](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
|
||||||
|
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||||
|
- CMake 3.19+
|
||||||
|
- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||||
|
diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp
|
||||||
|
index 32142f9..096e030 100644
|
||||||
|
--- a/include/libyang-cpp/Enum.hpp
|
||||||
|
+++ b/include/libyang-cpp/Enum.hpp
|
||||||
|
@@ -67,9 +67,9 @@ enum class OperationType : uint32_t {
|
||||||
|
*/
|
||||||
|
enum class PrintFlags : uint32_t {
|
||||||
|
WithDefaultsExplicit = 0x00,
|
||||||
|
- WithSiblings = 0x01,
|
||||||
|
+ Siblings = 0x01,
|
||||||
|
Shrink = 0x02,
|
||||||
|
- KeepEmptyCont = 0x04,
|
||||||
|
+ EmptyContainers = 0x04,
|
||||||
|
WithDefaultsTrim = 0x10,
|
||||||
|
WithDefaultsAll = 0x20,
|
||||||
|
WithDefaultsAllTag = 0x40,
|
||||||
|
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
|
||||||
|
index 0b2e2d7..a8902ff 100644
|
||||||
|
--- a/src/utils/enum.hpp
|
||||||
|
+++ b/src/utils/enum.hpp
|
||||||
|
@@ -33,7 +33,7 @@ constexpr uint32_t toPrintFlags(const PrintFlags flags)
|
||||||
|
}
|
||||||
|
// These tests ensure that I used the right numbers when defining my enum.
|
||||||
|
// TODO: add asserts for operator|(PrintFlags, PrintFlags)
|
||||||
|
-static_assert(LYD_PRINT_KEEPEMPTYCONT == toPrintFlags(PrintFlags::KeepEmptyCont));
|
||||||
|
+static_assert(LYD_PRINT_EMPTY_CONT == toPrintFlags(PrintFlags::EmptyContainers));
|
||||||
|
static_assert(LYD_PRINT_SHRINK == toPrintFlags(PrintFlags::Shrink));
|
||||||
|
static_assert(LYD_PRINT_WD_ALL == toPrintFlags(PrintFlags::WithDefaultsAll));
|
||||||
|
static_assert(LYD_PRINT_WD_ALL_TAG == toPrintFlags(PrintFlags::WithDefaultsAllTag));
|
||||||
|
@@ -41,7 +41,7 @@ static_assert(LYD_PRINT_WD_EXPLICIT == toPrintFlags(PrintFlags::WithDefaultsExpl
|
||||||
|
static_assert(LYD_PRINT_WD_IMPL_TAG == toPrintFlags(PrintFlags::WithDefaultsImplicitTag));
|
||||||
|
static_assert(LYD_PRINT_WD_MASK == toPrintFlags(PrintFlags::WithDefaultsMask));
|
||||||
|
static_assert(LYD_PRINT_WD_TRIM == toPrintFlags(PrintFlags::WithDefaultsTrim));
|
||||||
|
-static_assert(LYD_PRINT_WITHSIBLINGS == toPrintFlags(PrintFlags::WithSiblings));
|
||||||
|
+static_assert(LYD_PRINT_SIBLINGS == toPrintFlags(PrintFlags::Siblings));
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
// MSVC doesn't respect the underlying enum size
|
||||||
|
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||||
|
index eaedebf..355860b 100644
|
||||||
|
--- a/tests/context.cpp
|
||||||
|
+++ b/tests/context.cpp
|
||||||
|
@@ -502,7 +502,7 @@ TEST_CASE("context")
|
||||||
|
auto errorsNode = node->findXPath("/ietf-restconf:errors");
|
||||||
|
REQUIRE(errorsNode.size() == 1);
|
||||||
|
REQUIRE(errorsNode.begin()->path() == "/ietf-restconf:errors");
|
||||||
|
- REQUIRE(*errorsNode.begin()->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*errorsNode.begin()->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"ietf-restconf:errors": {
|
||||||
|
"error": [
|
||||||
|
{
|
||||||
|
@@ -589,7 +589,7 @@ TEST_CASE("context")
|
||||||
|
|
||||||
|
auto firstValue = edits.begin()->findPath("value");
|
||||||
|
REQUIRE(firstValue);
|
||||||
|
- REQUIRE(*firstValue->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*firstValue->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"ietf-yang-patch:value": {
|
||||||
|
"example-schema:person": {
|
||||||
|
"name": "John"
|
||||||
|
@@ -597,7 +597,7 @@ TEST_CASE("context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
- REQUIRE(*firstValue->printStr(libyang::DataFormat::XML, libyang::PrintFlags::KeepEmptyCont) == R"(<value xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
|
||||||
|
+ REQUIRE(*firstValue->printStr(libyang::DataFormat::XML, libyang::PrintFlags::EmptyContainers) == R"(<value xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
|
||||||
|
<person xmlns="http://example.com/coze">
|
||||||
|
<name>John</name>
|
||||||
|
</person>
|
||||||
|
@@ -607,8 +607,8 @@ TEST_CASE("context")
|
||||||
|
auto secondValueNode = (edits.begin() + 1)->findPath("value");
|
||||||
|
REQUIRE(secondValueNode);
|
||||||
|
auto secondValue = std::get<libyang::DataNode>(secondValueNode->asAny().releaseValue().value());
|
||||||
|
- REQUIRE(*secondValue.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == "{\n \"example-schema:dummy\": \"I am a dummy\"\n}\n");
|
||||||
|
- REQUIRE(*secondValue.printStr(libyang::DataFormat::XML, libyang::PrintFlags::KeepEmptyCont) == "<dummy xmlns=\"http://example.com/coze\">I am a dummy</dummy>\n");
|
||||||
|
+ REQUIRE(*secondValue.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == "{\n \"example-schema:dummy\": \"I am a dummy\"\n}\n");
|
||||||
|
+ REQUIRE(*secondValue.printStr(libyang::DataFormat::XML, libyang::PrintFlags::EmptyContainers) == "<dummy xmlns=\"http://example.com/coze\">I am a dummy</dummy>\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||||
|
index 88ca5b9..3f874de 100644
|
||||||
|
--- a/tests/data_node.cpp
|
||||||
|
+++ b/tests/data_node.cpp
|
||||||
|
@@ -137,7 +137,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
DOCTEST_SUBCASE("Printing")
|
||||||
|
{
|
||||||
|
auto node = ctx.parseData(data, libyang::DataFormat::JSON);
|
||||||
|
- auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||||
|
+ auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||||
|
const auto expected = R"({
|
||||||
|
"example-schema:leafInt32": 420,
|
||||||
|
"example-schema:first": {
|
||||||
|
@@ -158,7 +158,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(str == expected);
|
||||||
|
|
||||||
|
auto emptyCont = ctx.newPath("/example-schema:first");
|
||||||
|
- REQUIRE(emptyCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) == std::nullopt);
|
||||||
|
+ REQUIRE(emptyCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) == std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("Overwriting a tree with a different tree")
|
||||||
|
@@ -504,7 +504,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
{
|
||||||
|
auto node = std::optional{ctx.newPath("/example-schema:leafInt32", "420")};
|
||||||
|
libyang::validateAll(node, libyang::ValidationOptions::NoState);
|
||||||
|
- auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||||
|
+ auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||||
|
REQUIRE(str == data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -725,7 +725,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The original tree should still be accesible.
|
||||||
|
- node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
|
||||||
|
+ node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -855,7 +855,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
auto cont = ctx.newPath2("/example-schema2:contWithTwoNodes").createdNode;
|
||||||
|
data->unlinkWithSiblings();
|
||||||
|
cont->insertChild(*data);
|
||||||
|
- REQUIRE(*cont->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == R"({
|
||||||
|
+ REQUIRE(*cont->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == R"({
|
||||||
|
"example-schema2:contWithTwoNodes": {
|
||||||
|
"one": 333,
|
||||||
|
"two": 666
|
||||||
|
@@ -1501,9 +1501,9 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(std::holds_alternative<libyang::DataNode>(rawVal));
|
||||||
|
auto retrieved = std::get<libyang::DataNode>(rawVal);
|
||||||
|
REQUIRE(retrieved.path() == "/key");
|
||||||
|
- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== R"|({"key":"value"})|");
|
||||||
|
- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== R"|(<key>value</key>)|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1525,9 +1525,9 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(std::holds_alternative<libyang::DataNode>(rawVal));
|
||||||
|
auto retrieved = std::get<libyang::DataNode>(rawVal);
|
||||||
|
REQUIRE(retrieved.path() == "/something");
|
||||||
|
- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== R"|(<something>lol</something>)|");
|
||||||
|
- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== R"|({"something":"lol"})|");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1572,9 +1572,9 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
val = jsonAnyXmlNode.createdNode->asAny().releaseValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
- REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== R"|({"example-schema:ax":[1,2,3]})|"s);
|
||||||
|
- REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== R"|(<ax xmlns="http://example.com/coze">)|"s + origJSON + "</ax>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1603,9 +1603,9 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(root);
|
||||||
|
- REQUIRE(*root->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*root->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== origXML);
|
||||||
|
- REQUIRE(*root->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*root->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== origJSON);
|
||||||
|
|
||||||
|
auto node = root->findPath("/example-schema:ax");
|
||||||
|
@@ -1665,9 +1665,9 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
auto retrieved = std::get<libyang::DataNode>(*val);
|
||||||
|
val.reset();
|
||||||
|
REQUIRE(retrieved.path() == "/a");
|
||||||
|
- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== origXML);
|
||||||
|
- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
|
||||||
|
== origJSON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1806,7 +1806,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
|
||||||
|
nodeX.parseSubtree(data, libyang::DataFormat::JSON,
|
||||||
|
libyang::ParseOptions::Strict | libyang::ParseOptions::NoState | libyang::ParseOptions::ParseOnly);
|
||||||
|
- REQUIRE(*nodeX.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == R"({
|
||||||
|
+ REQUIRE(*nodeX.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == R"({
|
||||||
|
"example-schema5:x": {
|
||||||
|
"x_b": {
|
||||||
|
"x_b_leaf": 666
|
||||||
|
@@ -1948,7 +1948,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
{
|
||||||
|
netconfDeletePresenceCont.newMeta(netconf, "operation", "delete");
|
||||||
|
netconfDeletePresenceCont.newMeta(ietfOrigin, "origin", "ietf-origin:default");
|
||||||
|
- REQUIRE(*netconfDeletePresenceCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*netconfDeletePresenceCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings)
|
||||||
|
== R"(<presenceContainer xmlns="http://example.com/coze" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete" xmlns:or="urn:ietf:params:xml:ns:yang:ietf-origin" or:origin="or:default"/>)" "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1957,7 +1957,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque);
|
||||||
|
REQUIRE_THROWS(opaqueLeaf.newMeta(netconf, "operation", "delete"));
|
||||||
|
opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete");
|
||||||
|
- REQUIRE(*opaqueLeaf.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*opaqueLeaf.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
|
||||||
|
== R"({
|
||||||
|
"example-schema:leafInt32": "",
|
||||||
|
"@example-schema:leafInt32": {
|
||||||
|
@@ -1976,7 +1976,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
auto discard2 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", "sysrepo", "discard-items"}, libyang::JSON{"/example-schema:b"});
|
||||||
|
REQUIRE(!!discard2);
|
||||||
|
discard1->insertSibling(*discard2);
|
||||||
|
- REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
|
||||||
|
== R"({
|
||||||
|
"sysrepo:discard-items": "/example-schema:a",
|
||||||
|
"sysrepo:discard-items": "/example-schema:b"
|
||||||
|
@@ -1991,7 +1991,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
|
||||||
|
auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666");
|
||||||
|
leafInt16.insertSibling(*discard1);
|
||||||
|
- REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
|
||||||
|
== R"({
|
||||||
|
"example-schema:leafInt16": 666,
|
||||||
|
"sysrepo:discard-items": "/example-schema:a",
|
||||||
|
@@ -2020,7 +2020,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
|
||||||
|
dummy.insertSibling(*discard3);
|
||||||
|
leafInt16.insertSibling(dummy);
|
||||||
|
- REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||||
|
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
|
||||||
|
== R"({
|
||||||
|
"example-schema:dummy": "blah",
|
||||||
|
"example-schema:leafInt16": 666,
|
||||||
|
@@ -2112,8 +2112,8 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
data->unlinkWithSiblings();
|
||||||
|
out->insertChild(*data);
|
||||||
|
|
||||||
|
- REQUIRE(*out->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == expectedJson);
|
||||||
|
- REQUIRE(*out->printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) == expectedXml);
|
||||||
|
+ REQUIRE(*out->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == expectedJson);
|
||||||
|
+ REQUIRE(*out->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) == expectedXml);
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("libyang internal metadata")
|
||||||
|
@@ -2148,7 +2148,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
auto node = ctx.newExtPath(ext, "/ietf-restconf:errors", std::nullopt, std::nullopt);
|
||||||
|
REQUIRE(node);
|
||||||
|
REQUIRE(node->schema().name() == "errors");
|
||||||
|
- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"ietf-restconf:errors": {}
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
@@ -2157,7 +2157,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(node->newPath("ietf-restconf:error[1]/error-tag", "invalid-attribute"));
|
||||||
|
REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[1]/error-message", "ahoj"));
|
||||||
|
REQUIRE_THROWS_WITH(node->newPath("ietf-restconf:error[1]/error-message", "duplicate create"), "Couldn't create a node with path 'ietf-restconf:error[1]/error-message': LY_EEXIST");
|
||||||
|
- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"ietf-restconf:errors": {
|
||||||
|
"error": [
|
||||||
|
{
|
||||||
|
@@ -2173,7 +2173,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[2]/error-type", "transport"));
|
||||||
|
REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[2]/error-tag", "invalid-attribute"));
|
||||||
|
REQUIRE(node->newPath("ietf-restconf:error[2]/error-message", "aaa"));
|
||||||
|
- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"ietf-restconf:errors": {
|
||||||
|
"error": [
|
||||||
|
{
|
||||||
|
@@ -2397,8 +2397,8 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(response.tree->path() == "/example-schema:output");
|
||||||
|
REQUIRE(response.tree->isOpaque());
|
||||||
|
REQUIRE(!response.tree->child()); // nothing gets "parsed" here, the result is put into the tree that parseOp() operated on (!)
|
||||||
|
- CAPTURE(*response.tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||||
|
- CAPTURE(*replyTree.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||||
|
+ CAPTURE(*response.tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
|
||||||
|
+ CAPTURE(*replyTree.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
|
||||||
|
|
||||||
|
node = replyTree.findPath("/example-schema:myRpc/outputLeaf", libyang::InputOutputNodes::Output);
|
||||||
|
REQUIRE(!!node);
|
||||||
|
@@ -2472,7 +2472,7 @@ TEST_CASE("Data Node manipulation")
|
||||||
|
REQUIRE(rpcOp.tree);
|
||||||
|
|
||||||
|
libyang::validateOp(*rpcTree, depTree, libyang::OperationType::RpcRestconf);
|
||||||
|
- REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == expected);
|
||||||
|
+ REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("Nodes in disjunctive cases defined together")
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
From af187d1bc27a7390b358d7ad3b9f8f44cabe41ad Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 20 Nov 2025 12:46:49 +0100
|
||||||
|
Subject: [PATCH 7/7] don't segfault when listing module's features
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
This is especially relevant when working with the "printed context" from
|
||||||
|
sysrepo, because that context has all the parsed info removed. As a
|
||||||
|
result, it is not possible to query a module's list of features. Let's
|
||||||
|
stop dereferencing a null pointer in that case.
|
||||||
|
|
||||||
|
Bug: https://github.com/sysrepo/sysrepo/issues/3695
|
||||||
|
Change-Id: I6258f580b4e5aa02cae2d60260b84593aefcf587
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 1 +
|
||||||
|
include/libyang-cpp/Utils.hpp | 1 +
|
||||||
|
src/Module.cpp | 3 +++
|
||||||
|
src/utils/exception.cpp | 5 +++++
|
||||||
|
tests/context.cpp | 12 ++++++++++++
|
||||||
|
5 files changed, 22 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index cbaf82a..b9da66b 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -118,6 +118,7 @@ if(BUILD_TESTING)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
libyang_cpp_test(context)
|
||||||
|
+ target_link_libraries(test_context PkgConfig::LIBYANG)
|
||||||
|
libyang_cpp_test(data_node)
|
||||||
|
target_link_libraries(test_data_node PkgConfig::LIBYANG)
|
||||||
|
libyang_cpp_test(schema_node)
|
||||||
|
diff --git a/include/libyang-cpp/Utils.hpp b/include/libyang-cpp/Utils.hpp
|
||||||
|
index 934714d..428b265 100644
|
||||||
|
--- a/include/libyang-cpp/Utils.hpp
|
||||||
|
+++ b/include/libyang-cpp/Utils.hpp
|
||||||
|
@@ -35,6 +35,7 @@ public:
|
||||||
|
class LIBYANG_CPP_EXPORT ParsedInfoUnavailable : public Error {
|
||||||
|
public:
|
||||||
|
explicit ParsedInfoUnavailable();
|
||||||
|
+ explicit ParsedInfoUnavailable(const std::string& what);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
diff --git a/src/Module.cpp b/src/Module.cpp
|
||||||
|
index d6d4023..8eb8fb4 100644
|
||||||
|
--- a/src/Module.cpp
|
||||||
|
+++ b/src/Module.cpp
|
||||||
|
@@ -155,6 +155,9 @@ void Module::setImplemented(const AllFeatures)
|
||||||
|
*/
|
||||||
|
std::vector<Feature> Module::features() const
|
||||||
|
{
|
||||||
|
+ if (!m_module->parsed) {
|
||||||
|
+ throw ParsedInfoUnavailable{"Module::features: lys_module::parsed is not available"};
|
||||||
|
+ }
|
||||||
|
std::vector<Feature> res;
|
||||||
|
for (const auto& feature : std::span(m_module->parsed->features, LY_ARRAY_COUNT(m_module->parsed->features))) {
|
||||||
|
res.emplace_back(Feature{&feature, m_ctx});
|
||||||
|
diff --git a/src/utils/exception.cpp b/src/utils/exception.cpp
|
||||||
|
index ecd2b85..a8d908b 100644
|
||||||
|
--- a/src/utils/exception.cpp
|
||||||
|
+++ b/src/utils/exception.cpp
|
||||||
|
@@ -22,6 +22,11 @@ ParsedInfoUnavailable::ParsedInfoUnavailable()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
+ParsedInfoUnavailable::ParsedInfoUnavailable(const std::string& what)
|
||||||
|
+ : Error(what)
|
||||||
|
+{
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
ErrorCode ErrorWithCode::code() const
|
||||||
|
{
|
||||||
|
return m_errCode;
|
||||||
|
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||||
|
index 355860b..5fce0f4 100644
|
||||||
|
--- a/tests/context.cpp
|
||||||
|
+++ b/tests/context.cpp
|
||||||
|
@@ -10,6 +10,7 @@
|
||||||
|
#include <fstream>
|
||||||
|
#include <libyang-cpp/Context.hpp>
|
||||||
|
#include <libyang-cpp/Utils.hpp>
|
||||||
|
+#include <libyang/context.h>
|
||||||
|
#include "example_schema.hpp"
|
||||||
|
#include "pretty_printers.hpp"
|
||||||
|
#include "test_vars.hpp"
|
||||||
|
@@ -333,6 +334,17 @@ TEST_CASE("context")
|
||||||
|
REQUIRE(enabledFeatures == expectedEnabledFeatures);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ DOCTEST_SUBCASE("printed context")
|
||||||
|
+ {
|
||||||
|
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||||
|
+ auto mod = ctx->loadModule("mod1", std::nullopt, {"*"});
|
||||||
|
+ REQUIRE(mod.features().size() == 3);
|
||||||
|
+ ly_ctx_free_parsed(retrieveContext(*ctx));
|
||||||
|
+ REQUIRE_THROWS_WITH_AS(mod.features(),
|
||||||
|
+ "Module::features: lys_module::parsed is not available",
|
||||||
|
+ libyang::ParsedInfoUnavailable);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
DOCTEST_SUBCASE("Module::setImplemented")
|
||||||
|
{
|
||||||
|
ctx->setSearchDir(TESTS_DIR / "yang");
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# md5, sha256 from https://pypi.org/pypi/libyang/json
|
# md5, sha256 from https://pypi.org/pypi/libyang/json
|
||||||
md5 46332891cac852441b523f7e71a507f7 libyang-3.0.1.tar.gz
|
md5 46332891cac852441b523f7e71a507f7 libyang-3.0.1.tar.gz
|
||||||
sha256 57f8b234131fb073518313351288194e535d74cc8fdff7f0ff7e31c0ab33e663 libyang-3.0.1.tar.gz
|
sha256 97da2c908a8a3607ac834f2acec224ea53a4b9e9770c13e5598513641decf637 libyang-3.3.0.tar.gz
|
||||||
# Locally computed sha256 checksums
|
# Locally computed sha256 checksums
|
||||||
sha256 c8d554f918d32b1467f35829e1cfb753764dd683c6edcecc877a4d7e9f05b2a7 LICENSE
|
sha256 c8d554f918d32b1467f35829e1cfb753764dd683c6edcecc877a4d7e9f05b2a7 LICENSE
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
PYTHON_LIBYANG_VERSION = 3.0.1
|
PYTHON_LIBYANG_VERSION = 3.3.0
|
||||||
PYTHON_LIBYANG_SOURCE = libyang-$(PYTHON_LIBYANG_VERSION).tar.gz
|
PYTHON_LIBYANG_SOURCE = libyang-$(PYTHON_LIBYANG_VERSION).tar.gz
|
||||||
PYTHON_LIBYANG_SITE = https://files.pythonhosted.org/packages/91/2e/ff13ee16c874d232d5d3fdff83f629cbc9ac47f9aaf2b59256b6a1bdbc16
|
PYTHON_LIBYANG_SITE = https://files.pythonhosted.org/packages/34/d8/ebe9de6d29eaf62a684b34d30118294e48a05d7fbd1bb0af002b7627140d
|
||||||
PYTHON_LIBYANG_SETUP_TYPE = setuptools
|
PYTHON_LIBYANG_SETUP_TYPE = setuptools
|
||||||
PYTHON_LIBYANG_LICENSE = MIT
|
PYTHON_LIBYANG_LICENSE = MIT
|
||||||
PYTHON_LIBYANG_LICENSE_FILES = LICENSE
|
PYTHON_LIBYANG_LICENSE_FILES = LICENSE
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
From c0d13b8e426608a30b1eea382f908fb6447fd01d Mon Sep 17 00:00:00 2001
|
From a4136d889237dadb9253ea7eb668a525dd779e6d Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
Date: Thu, 12 Jun 2025 10:33:42 +0100
|
Date: Thu, 12 Jun 2025 10:33:42 +0100
|
||||||
Subject: [PATCH 01/13] Log HTTP headers and the input data payload
|
Subject: [PATCH 01/38] Log HTTP headers and the input data payload
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -11,7 +11,7 @@ Organization: Wires
|
|||||||
Hažlinský just found.
|
Hažlinský just found.
|
||||||
|
|
||||||
Change-Id: I2a930a02c7d30c051390fe73e6af9849edd580b4
|
Change-Id: I2a930a02c7d30c051390fe73e6af9849edd580b4
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/restconf/Server.cpp | 10 ++++++++--
|
src/restconf/Server.cpp | 10 ++++++++--
|
||||||
1 file changed, 8 insertions(+), 2 deletions(-)
|
1 file changed, 8 insertions(+), 2 deletions(-)
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
From 027a47ac3ff6c369caab7c007a5a171a68270829 Mon Sep 17 00:00:00 2001
|
From 6c5b482ea5c9fbc1149a0864b05d1bb1fa7100bf Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Fri, 13 Jun 2025 10:47:55 +0200
|
Date: Fri, 13 Jun 2025 10:47:55 +0200
|
||||||
Subject: [PATCH 02/13] restconf: prevent throwing exception in
|
Subject: [PATCH 02/38] restconf: prevent throwing exception in
|
||||||
withRestconfExceptions wrapper
|
withRestconfExceptions wrapper
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
In case the catch block of withRestconfExceptions is reached and an
|
In case the catch block of withRestconfExceptions is reached and an
|
||||||
@@ -19,7 +22,7 @@ handle such situations.
|
|||||||
|
|
||||||
Bug: https://github.com/CESNET/rousette/issues/19
|
Bug: https://github.com/CESNET/rousette/issues/19
|
||||||
Change-Id: Ifbd74b9bdc0ca66c4e5449a7673ef2f12ae9215e
|
Change-Id: Ifbd74b9bdc0ca66c4e5449a7673ef2f12ae9215e
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/restconf/Server.cpp | 2 +-
|
src/restconf/Server.cpp | 2 +-
|
||||||
tests/restconf-plain-patch.cpp | 24 ++++++++++++++++++++++++
|
tests/restconf-plain-patch.cpp | 24 ++++++++++++++++++++++++
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
From 41c9d9cab47a88ee6c70ab8009b789226c0982fe Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Tue, 17 Jun 2025 12:46:27 +0200
|
||||||
|
Subject: [PATCH 03/38] CI: switch to the new cloud's Swift URL
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: I69f8351394262a2a9b691422592741bfb40a8e38
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
ci/build.sh | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/ci/build.sh b/ci/build.sh
|
||||||
|
index d49bd56..59b31ab 100755
|
||||||
|
--- a/ci/build.sh
|
||||||
|
+++ b/ci/build.sh
|
||||||
|
@@ -67,7 +67,7 @@ if [[ -z "${ARTIFACT_URL}" ]]; then
|
||||||
|
# nothing ahead in the pipeline -> fallback to the latest promoted artifact
|
||||||
|
DEPSRCDIR=$(jq < ~/zuul-env.json -e -r ".projects[] | select(.name == \"CzechLight/dependencies\").src_dir")
|
||||||
|
DEP_SUBMODULE_COMMIT=$(git --git-dir ${HOME}/${DEPSRCDIR}/.git rev-parse HEAD)
|
||||||
|
- ARTIFACT_URL="https://object-store.cloud.muni.cz/swift/v1/ci-artifacts-${ZUUL_TENANT}/${ZUUL_GERRIT_HOSTNAME}/CzechLight/dependencies/deps-${ZUUL_JOB_NAME_NO_PROJECT%%-cover?(-previous)}/${DEP_SUBMODULE_COMMIT}.tar.zst"
|
||||||
|
+ ARTIFACT_URL="https://object-store.brno.openstack.cloud.e-infra.cz/swift/v1/KEY_b637b9c937414b29b3e277b4a85cc658/ci-artifacts-${ZUUL_TENANT}/${ZUUL_GERRIT_HOSTNAME}/CzechLight/dependencies/deps-${ZUUL_JOB_NAME_NO_PROJECT%%-cover?(-previous)}/${DEP_SUBMODULE_COMMIT}.tar.zst"
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl ${ARTIFACT_URL} | unzstd --stdout | tar -C ${PREFIX} -xf -
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
From d1c56f14b54b113df8fba759924bb47cfa27366c Mon Sep 17 00:00:00 2001
|
From ec8673126929b6459fcd99c84a79993a725b40e1 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Fri, 13 Jun 2025 10:47:55 +0200
|
Date: Fri, 13 Jun 2025 10:47:55 +0200
|
||||||
Subject: [PATCH 03/13] restconf: crash instead of a deadlock when the handler
|
Subject: [PATCH 04/38] restconf: crash instead of a deadlock when the handler
|
||||||
throws
|
throws
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
@@ -37,7 +37,7 @@ called twice [3].
|
|||||||
Signed-off-by: Jan Kundrát <jan.kundrat@cesnet.cz>
|
Signed-off-by: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||||
Bug: https://github.com/CESNET/rousette/issues/19
|
Bug: https://github.com/CESNET/rousette/issues/19
|
||||||
Change-Id: I2c090b9a76b062101ba422a7d50e8e699779e203
|
Change-Id: I2c090b9a76b062101ba422a7d50e8e699779e203
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/restconf/Server.cpp | 28 ++++++++++++++++++++++++++++
|
src/restconf/Server.cpp | 28 ++++++++++++++++++++++++++++
|
||||||
src/restconf/Server.h | 4 ++++
|
src/restconf/Server.h | 4 ++++
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
From 37ca95c387d76c3f296a4e44b211772a1ca155ab Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Wed, 18 Jun 2025 12:01:04 +0200
|
||||||
|
Subject: [PATCH 05/38] doc: let's stop calling this "an almost-RESTCONF
|
||||||
|
server"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: If55ace481c78d838a811ded76a564f8fb59f9233
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
README.md | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/README.md b/README.md
|
||||||
|
index 0796dce..a42c64d 100644
|
||||||
|
--- a/README.md
|
||||||
|
+++ b/README.md
|
||||||
|
@@ -1,4 +1,4 @@
|
||||||
|
-# An almost-RESTCONF server
|
||||||
|
+# A RESTCONF server for sysrepo
|
||||||
|
|
||||||
|

|
||||||
|
[](https://gerrit.cesnet.cz/q/project:CzechLight/rousette)
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From db64d7b3fc0a0373067e6c5ac9e41b93351778a1 Mon Sep 17 00:00:00 2001
|
From 4eae6200aa812950ebbac1660a1899f4edf41e11 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
Date: Wed, 7 Aug 2024 19:07:35 +0200
|
Date: Wed, 7 Aug 2024 19:07:35 +0200
|
||||||
Subject: [PATCH 04/13] close long-lived connections on SIGTERM
|
Subject: [PATCH 06/38] close long-lived connections on SIGTERM
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
Without this patch, all the ongoing SSE streams would be left alive for
|
Without this patch, all the ongoing SSE streams would be left alive for
|
||||||
@@ -20,7 +23,7 @@ hope it is).
|
|||||||
|
|
||||||
Change-Id: If442134783ba1d699de47c51a9068378f53e8339
|
Change-Id: If442134783ba1d699de47c51a9068378f53e8339
|
||||||
Co-authored-by: Tomas Pecka <tomas.pecka@cesnet.cz>
|
Co-authored-by: Tomas Pecka <tomas.pecka@cesnet.cz>
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
CMakeLists.txt | 1 +
|
CMakeLists.txt | 1 +
|
||||||
src/clock.cpp | 7 +--
|
src/clock.cpp | 7 +--
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From 5c8a139d04d9b3dfb28b8c0e1a4aa7b945a1e8fc Mon Sep 17 00:00:00 2001
|
From ca2894d4888c673d227fc196a25f83ded20e8f04 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Mon, 30 Jun 2025 15:38:02 +0200
|
Date: Mon, 30 Jun 2025 15:38:02 +0200
|
||||||
Subject: [PATCH 05/13] restconf: refactor server stop
|
Subject: [PATCH 07/38] restconf: refactor server stop
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
In 94b24795 ("server: simplify listening/stopping") we needed to call
|
In 94b24795 ("server: simplify listening/stopping") we needed to call
|
||||||
@@ -17,7 +20,7 @@ does not seem like it should be called from *every* io_service.)
|
|||||||
[2] https://www.boost.org/doc/libs/1_85_0/doc/html/boost_asio/reference/post.html
|
[2] https://www.boost.org/doc/libs/1_85_0/doc/html/boost_asio/reference/post.html
|
||||||
|
|
||||||
Change-Id: I2f33c38a78dce4081a03326c9a9bb25817fc9d2f
|
Change-Id: I2f33c38a78dce4081a03326c9a9bb25817fc9d2f
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/restconf/Server.cpp | 13 +++++--------
|
src/restconf/Server.cpp | 13 +++++--------
|
||||||
1 file changed, 5 insertions(+), 8 deletions(-)
|
1 file changed, 5 insertions(+), 8 deletions(-)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
From 9961430bacdd8dbac64a01b2a2ffb6a4b7e806b6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 13 May 2025 13:48:35 +0200
|
||||||
|
Subject: [PATCH 08/38] tests: use std::string::starts_with
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
We are C++20, so we can use line, which is more readable.
|
||||||
|
|
||||||
|
Change-Id: I40d4038b421f6bc1fcf320f609b50d5ce7018a45
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
tests/restconf_utils.cpp | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||||
|
index cbfce2c..08b9623 100644
|
||||||
|
--- a/tests/restconf_utils.cpp
|
||||||
|
+++ b/tests/restconf_utils.cpp
|
||||||
|
@@ -228,7 +228,7 @@ std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
|
||||||
|
std::string event;
|
||||||
|
|
||||||
|
while (std::getline(iss, line)) {
|
||||||
|
- if (line.compare(0, prefix.size(), prefix) == 0) {
|
||||||
|
+ if (line.starts_with(prefix)) {
|
||||||
|
event += line.substr(prefix.size());
|
||||||
|
} else if (line.empty()) {
|
||||||
|
res.emplace_back(std::move(event));
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
From 746c0cdfef6808f393be7946630b8acbb0636706 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 22 Jul 2025 17:47:50 +0200
|
||||||
|
Subject: [PATCH 09/38] tests: add SSE event watcher for comments
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
I want to test whether client receives even the Server Sent Events
|
||||||
|
which should be ignored, i.e., lines starting with colon.
|
||||||
|
|
||||||
|
Change-Id: If54f0af05b4884aab01325f12fd0a6859791b41b
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
tests/event_watchers.cpp | 7 ++++++-
|
||||||
|
tests/event_watchers.h | 4 +++-
|
||||||
|
tests/restconf_utils.cpp | 36 ++++++++++++++++++++----------------
|
||||||
|
tests/restconf_utils.h | 12 +++++++++---
|
||||||
|
4 files changed, 38 insertions(+), 21 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/tests/event_watchers.cpp b/tests/event_watchers.cpp
|
||||||
|
index 690338e..26ba951 100644
|
||||||
|
--- a/tests/event_watchers.cpp
|
||||||
|
+++ b/tests/event_watchers.cpp
|
||||||
|
@@ -64,7 +64,7 @@ void RestconfNotificationWatcher::setDataFormat(const libyang::DataFormat dataFo
|
||||||
|
this->dataFormat = dataFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
-void RestconfNotificationWatcher::operator()(const std::string& msg) const
|
||||||
|
+void RestconfNotificationWatcher::dataEvent(const std::string& msg) const
|
||||||
|
{
|
||||||
|
spdlog::trace("Client received data: {}", msg);
|
||||||
|
auto notifDataNode = ctx.parseOp(msg,
|
||||||
|
@@ -79,3 +79,8 @@ void RestconfNotificationWatcher::operator()(const std::string& msg) const
|
||||||
|
|
||||||
|
data(*dataRoot->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink));
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+void RestconfNotificationWatcher::commentEvent(const std::string& msg) const
|
||||||
|
+{
|
||||||
|
+ comment(msg);
|
||||||
|
+}
|
||||||
|
diff --git a/tests/event_watchers.h b/tests/event_watchers.h
|
||||||
|
index 3b533ba..7022b64 100644
|
||||||
|
--- a/tests/event_watchers.h
|
||||||
|
+++ b/tests/event_watchers.h
|
||||||
|
@@ -39,8 +39,10 @@ struct RestconfNotificationWatcher {
|
||||||
|
|
||||||
|
RestconfNotificationWatcher(const libyang::Context& ctx);
|
||||||
|
void setDataFormat(const libyang::DataFormat dataFormat);
|
||||||
|
- void operator()(const std::string& msg) const;
|
||||||
|
+ void dataEvent(const std::string& msg) const;
|
||||||
|
+ void commentEvent(const std::string& msg) const;
|
||||||
|
|
||||||
|
+ MAKE_CONST_MOCK1(comment, void(const std::string&));
|
||||||
|
MAKE_CONST_MOCK1(data, void(const std::string&));
|
||||||
|
};
|
||||||
|
|
||||||
|
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||||
|
index 08b9623..5b1ebe3 100644
|
||||||
|
--- a/tests/restconf_utils.cpp
|
||||||
|
+++ b/tests/restconf_utils.cpp
|
||||||
|
@@ -169,10 +169,11 @@ SSEClient::SSEClient(
|
||||||
|
const std::string& server_address,
|
||||||
|
const std::string& server_port,
|
||||||
|
std::binary_semaphore& requestSent,
|
||||||
|
- const RestconfNotificationWatcher& notification,
|
||||||
|
+ const RestconfNotificationWatcher& eventWatcher,
|
||||||
|
const std::string& uri,
|
||||||
|
const std::map<std::string, std::string>& headers,
|
||||||
|
- const boost::posix_time::seconds silenceTimeout)
|
||||||
|
+ const boost::posix_time::seconds silenceTimeout,
|
||||||
|
+ const ReportIgnoredLines reportIgnoredLines)
|
||||||
|
: client(std::make_shared<ng_client::session>(io, server_address, server_port))
|
||||||
|
, t(io, silenceTimeout)
|
||||||
|
{
|
||||||
|
@@ -191,17 +192,15 @@ SSEClient::SSEClient(
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
- client->on_connect([&, uri, reqHeaders, silenceTimeout, server_address, server_port](auto) {
|
||||||
|
+ client->on_connect([&, uri, reqHeaders, silenceTimeout, server_address, server_port, reportIgnoredLines](auto) {
|
||||||
|
boost::system::error_code ec;
|
||||||
|
|
||||||
|
auto req = client->submit(ec, "GET", serverAddressAndPort(server_address, server_port) + uri, "", reqHeaders);
|
||||||
|
- req->on_response([&, silenceTimeout](const ng_client::response& res) {
|
||||||
|
+ req->on_response([&, silenceTimeout, reportIgnoredLines](const ng_client::response& res) {
|
||||||
|
requestSent.release();
|
||||||
|
- res.on_data([&, silenceTimeout](const uint8_t* data, std::size_t len) {
|
||||||
|
+ res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
|
||||||
|
// not a production-ready code. In real-life condition the data received in one callback might probably be incomplete
|
||||||
|
- for (const auto& event : parseEvents(std::string(reinterpret_cast<const char*>(data), len))) {
|
||||||
|
- notification(event);
|
||||||
|
- }
|
||||||
|
+ parseEvents(std::string(reinterpret_cast<const char*>(data), len), eventWatcher, reportIgnoredLines);
|
||||||
|
t.expires_from_now(silenceTimeout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@@ -218,25 +217,30 @@ SSEClient::SSEClient(
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
-std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
|
||||||
|
+void SSEClient::parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
|
||||||
|
{
|
||||||
|
- static const std::string prefix = "data:";
|
||||||
|
+ static const std::string dataPrefix = "data:";
|
||||||
|
+ static const std::string ignorePrefix = ":";
|
||||||
|
|
||||||
|
- std::vector<std::string> res;
|
||||||
|
std::istringstream iss(msg);
|
||||||
|
std::string line;
|
||||||
|
std::string event;
|
||||||
|
|
||||||
|
while (std::getline(iss, line)) {
|
||||||
|
- if (line.starts_with(prefix)) {
|
||||||
|
- event += line.substr(prefix.size());
|
||||||
|
- } else if (line.empty()) {
|
||||||
|
- res.emplace_back(std::move(event));
|
||||||
|
+ if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
|
||||||
|
+ eventWatcher.commentEvent(line);
|
||||||
|
+ } else if (line.starts_with(ignorePrefix)) {
|
||||||
|
+ continue;
|
||||||
|
+ } else if (line.starts_with(dataPrefix)) {
|
||||||
|
+ event += line.substr(dataPrefix.size());
|
||||||
|
+ } else if (line.empty() && !event.empty()) {
|
||||||
|
+ eventWatcher.dataEvent(event);
|
||||||
|
event.clear();
|
||||||
|
+ } else if (line.empty()) {
|
||||||
|
+ continue;
|
||||||
|
} else {
|
||||||
|
CAPTURE(msg);
|
||||||
|
FAIL("Unprefixed response");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- return res;
|
||||||
|
}
|
||||||
|
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
|
||||||
|
index 9efe398..9dde10b 100644
|
||||||
|
--- a/tests/restconf_utils.h
|
||||||
|
+++ b/tests/restconf_utils.h
|
||||||
|
@@ -88,17 +88,23 @@ struct SSEClient {
|
||||||
|
std::shared_ptr<ng_client::session> client;
|
||||||
|
boost::asio::deadline_timer t;
|
||||||
|
|
||||||
|
+ enum class ReportIgnoredLines {
|
||||||
|
+ No,
|
||||||
|
+ Yes,
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
SSEClient(
|
||||||
|
boost::asio::io_service& io,
|
||||||
|
const std::string& server_address,
|
||||||
|
const std::string& server_port,
|
||||||
|
std::binary_semaphore& requestSent,
|
||||||
|
- const RestconfNotificationWatcher& notification,
|
||||||
|
+ const RestconfNotificationWatcher& eventWatcher,
|
||||||
|
const std::string& uri,
|
||||||
|
const std::map<std::string, std::string>& headers,
|
||||||
|
- const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1)); // test code; the server should respond "soon"
|
||||||
|
+ const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
|
||||||
|
+ const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
|
||||||
|
|
||||||
|
- static std::vector<std::string> parseEvents(const std::string& msg);
|
||||||
|
+ static void parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PREPARE_LOOP_WITH_EXCEPTIONS \
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From 086cfe1847e406df90852dc3cb52ece3d0cbe22b Mon Sep 17 00:00:00 2001
|
From 5becffe8a1dd47c8836ce1800a1b72acdf86021f Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Mon, 12 May 2025 14:56:54 +0200
|
Date: Mon, 12 May 2025 14:56:54 +0200
|
||||||
Subject: [PATCH 06/13] http: send keep-alive pings from EventStream
|
Subject: [PATCH 10/38] http: send keep-alive pings from EventStream
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
The nghttp2-asio server disconnects client after 60 seconds of no
|
The nghttp2-asio server disconnects client after 60 seconds of no
|
||||||
@@ -28,7 +31,7 @@ to the server when the server sends these "keep-alive comments".
|
|||||||
[3] https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes
|
[3] https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes
|
||||||
|
|
||||||
Change-Id: I57e510d0b61ac7ed032c582779780c64768b7d53
|
Change-Id: I57e510d0b61ac7ed032c582779780c64768b7d53
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/clock.cpp | 4 +-
|
src/clock.cpp | 4 +-
|
||||||
src/http/EventStream.cpp | 44 +++++++++++++--
|
src/http/EventStream.cpp | 44 +++++++++++++--
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From 06e2751e4da052195db96aaa90549fba398208bd Mon Sep 17 00:00:00 2001
|
From ff4ff1c193083feca76d9f0f4485e4b175c373c2 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Wed, 23 Jul 2025 14:27:26 +0200
|
Date: Wed, 23 Jul 2025 14:27:26 +0200
|
||||||
Subject: [PATCH 07/13] refactor: event streams use named constructors
|
Subject: [PATCH 11/38] refactor: event streams use named constructors
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
This commit refactors the EventStream and NotificationStream to use
|
This commit refactors the EventStream and NotificationStream to use
|
||||||
@@ -14,7 +17,7 @@ named constructors which perform the double initialization themselves.
|
|||||||
This way, the code is more readable and the intention should be clearer.
|
This way, the code is more readable and the intention should be clearer.
|
||||||
|
|
||||||
Change-Id: Iac96c49c20670dfe924d7c8db33328ed9c2fc9dd
|
Change-Id: Iac96c49c20670dfe924d7c8db33328ed9c2fc9dd
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/clock.cpp | 3 +--
|
src/clock.cpp | 3 +--
|
||||||
src/http/EventStream.cpp | 20 ++++++++++++++++++++
|
src/http/EventStream.cpp | 20 ++++++++++++++++++++
|
||||||
+6
-3
@@ -1,13 +1,16 @@
|
|||||||
From e98e079d0fdedf48facbfcf187950946c1ffed7a Mon Sep 17 00:00:00 2001
|
From f4602a03adc9134a9b7a9d338e900b40557da6c4 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
Date: Thu, 7 Aug 2025 12:14:02 +0200
|
Date: Thu, 7 Aug 2025 12:14:02 +0200
|
||||||
Subject: [PATCH 08/13] refactor: a better convention for weak_from_this->lock
|
Subject: [PATCH 12/38] refactor: a better convention for weak_from_this->lock
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
It is not a "client", so let's stop calling it a "client". My bad.
|
It is not a "client", so let's stop calling it a "client". My bad.
|
||||||
|
|
||||||
Change-Id: Id8dc4d92c3ade8d86697366d0102e84bd466f504
|
Change-Id: Id8dc4d92c3ade8d86697366d0102e84bd466f504
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/http/EventStream.cpp | 44 ++++++++++++++++++++--------------------
|
src/http/EventStream.cpp | 44 ++++++++++++++++++++--------------------
|
||||||
1 file changed, 22 insertions(+), 22 deletions(-)
|
1 file changed, 22 insertions(+), 22 deletions(-)
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
From ba43271df7cc83806a08d6937719544786fd331d Mon Sep 17 00:00:00 2001
|
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
|
||||||
Date: Sun, 28 Sep 2025 06:04:12 +0200
|
|
||||||
Subject: [PATCH 13/13] Add '--log-level LEVEL' command line option
|
|
||||||
Organization: Wires
|
|
||||||
|
|
||||||
The default 'trace' log level is quite verbose for production systems.
|
|
||||||
This commit changes the default to 'info' and adds a command line option
|
|
||||||
to control the log level.
|
|
||||||
|
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
|
||||||
---
|
|
||||||
src/restconf/main.cpp | 27 +++++++++++++++++++++++++--
|
|
||||||
1 file changed, 25 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/src/restconf/main.cpp b/src/restconf/main.cpp
|
|
||||||
index 477e846..f253421 100644
|
|
||||||
--- a/src/restconf/main.cpp
|
|
||||||
+++ b/src/restconf/main.cpp
|
|
||||||
@@ -26,11 +26,12 @@
|
|
||||||
static const char usage[] =
|
|
||||||
R"(Rousette - RESTCONF server
|
|
||||||
Usage:
|
|
||||||
- rousette [--syslog] [--timeout <SECONDS>] [--help]
|
|
||||||
+ rousette [--syslog] [--timeout <SECONDS>] [--log-level <LEVEL>] [--help]
|
|
||||||
Options:
|
|
||||||
-h --help Show this screen.
|
|
||||||
-t --timeout <SECONDS> Change default timeout in sysrepo (if not set, use sysrepo internal).
|
|
||||||
--syslog Log to syslog.
|
|
||||||
+ --log-level <LEVEL> Set log level (trace, debug, info, warn, error, critical, off) [default: info].
|
|
||||||
)";
|
|
||||||
#ifdef HAVE_SYSTEMD
|
|
||||||
|
|
||||||
@@ -75,10 +76,32 @@ int main(int argc, char* argv [])
|
|
||||||
{
|
|
||||||
auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true,""/* version */, true);
|
|
||||||
auto timeout = std::chrono::milliseconds{0};
|
|
||||||
+ auto logLevel = spdlog::level::info;
|
|
||||||
|
|
||||||
if (args["--timeout"]) {
|
|
||||||
timeout = std::chrono::milliseconds{args["--timeout"].asLong() * 1000};
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ if (args["--log-level"]) {
|
|
||||||
+ auto levelStr = args["--log-level"].asString();
|
|
||||||
+ if (levelStr == "trace") {
|
|
||||||
+ logLevel = spdlog::level::trace;
|
|
||||||
+ } else if (levelStr == "debug") {
|
|
||||||
+ logLevel = spdlog::level::debug;
|
|
||||||
+ } else if (levelStr == "info") {
|
|
||||||
+ logLevel = spdlog::level::info;
|
|
||||||
+ } else if (levelStr == "warn" || levelStr == "warning") {
|
|
||||||
+ logLevel = spdlog::level::warn;
|
|
||||||
+ } else if (levelStr == "error" || levelStr == "err") {
|
|
||||||
+ logLevel = spdlog::level::err;
|
|
||||||
+ } else if (levelStr == "critical") {
|
|
||||||
+ logLevel = spdlog::level::critical;
|
|
||||||
+ } else if (levelStr == "off") {
|
|
||||||
+ logLevel = spdlog::level::off;
|
|
||||||
+ } else {
|
|
||||||
+ throw std::runtime_error("Invalid log level: " + levelStr + ". Valid levels: trace, debug, info, warn, error, critical, off");
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
if (args["--syslog"].asBool()) {
|
|
||||||
auto syslog_sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("rousette", LOG_PID, LOG_USER, true);
|
|
||||||
auto logger = std::make_shared<spdlog::logger>("rousette", syslog_sink);
|
|
||||||
@@ -94,7 +117,7 @@ int main(int argc, char* argv [])
|
|
||||||
auto logger = std::make_shared<spdlog::logger>("rousette", stdout_sink);
|
|
||||||
spdlog::set_default_logger(logger);
|
|
||||||
}
|
|
||||||
- spdlog::set_level(spdlog::level::trace);
|
|
||||||
+ spdlog::set_level(logLevel);
|
|
||||||
|
|
||||||
/* We will parse URIs using boost::spirit's alnum/alpha/... matchers which are locale-dependent.
|
|
||||||
* Let's use something stable no matter what the system is using
|
|
||||||
--
|
|
||||||
2.43.0
|
|
||||||
|
|
||||||
+6
-3
@@ -1,12 +1,15 @@
|
|||||||
From de7c55a1e8d3dda20ee3dc408612f2ddb309e888 Mon Sep 17 00:00:00 2001
|
From 4e9b535a59861f25c0602eaa1fc39126d7cd9899 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
Date: Thu, 7 Aug 2025 12:21:54 +0200
|
Date: Thu, 7 Aug 2025 12:21:54 +0200
|
||||||
Subject: [PATCH 09/13] fix a possible bad_weak_ptr exception
|
Subject: [PATCH 13/38] fix a possible bad_weak_ptr exception
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
Change-Id: I8c7f7a943a1d848f15527988cb76c2a0a10089e6
|
Change-Id: I8c7f7a943a1d848f15527988cb76c2a0a10089e6
|
||||||
Fixes: 4eae6200 (close long-lived connections on SIGTERM)
|
Fixes: 4eae6200 (close long-lived connections on SIGTERM)
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/http/EventStream.cpp | 2 +-
|
src/http/EventStream.cpp | 2 +-
|
||||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From 0f034d6da3e6d0a91bbe09717cb33aaf3a3ed747 Mon Sep 17 00:00:00 2001
|
From 6bca750f866b5b14c4d9c3da68e5c8f1e4eee36c Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Mon, 19 May 2025 12:11:09 +0200
|
Date: Mon, 19 May 2025 12:11:09 +0200
|
||||||
Subject: [PATCH 10/13] http: add optional callbacks to EventStream
|
Subject: [PATCH 14/38] http: add optional callbacks to EventStream
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
In some cases, it might be useful to have a callback that is called when
|
In some cases, it might be useful to have a callback that is called when
|
||||||
@@ -11,7 +14,7 @@ implementation where we would like to clean up stuff after client
|
|||||||
disconnects.
|
disconnects.
|
||||||
|
|
||||||
Change-Id: Icfc2959e38b812b7c18f45976415209b29151c7b
|
Change-Id: Icfc2959e38b812b7c18f45976415209b29151c7b
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/http/EventStream.cpp | 18 +++++++++++++++---
|
src/http/EventStream.cpp | 18 +++++++++++++++---
|
||||||
src/http/EventStream.h | 10 ++++++++--
|
src/http/EventStream.h | 10 ++++++++--
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From d72adb3da31cb509e64c8f33174e375fa9468b0b Mon Sep 17 00:00:00 2001
|
From f6ee629de8abef42a24c42b185052b0c8e78bd6b Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Mon, 19 May 2025 12:23:16 +0200
|
Date: Mon, 19 May 2025 12:23:16 +0200
|
||||||
Subject: [PATCH 11/13] restconf: add internal RPC handler dispatcher
|
Subject: [PATCH 15/38] restconf: add internal RPC handler dispatcher
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
This code adds a new internal RPC handler dispatcher that checks if user
|
This code adds a new internal RPC handler dispatcher that checks if user
|
||||||
@@ -11,7 +14,7 @@ So far, there are no RPCs processed, but support for the
|
|||||||
ietf-subscribed-notifications:establish-subscription RPC is coming soon.
|
ietf-subscribed-notifications:establish-subscription RPC is coming soon.
|
||||||
|
|
||||||
Change-Id: I99121a511011229e4098f95e91601b39d333444a
|
Change-Id: I99121a511011229e4098f95e91601b39d333444a
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/restconf/Server.cpp | 24 ++++++++++++++++++++----
|
src/restconf/Server.cpp | 24 ++++++++++++++++++++----
|
||||||
tests/restconf-rpc.cpp | 18 ++++++++++++++++++
|
tests/restconf-rpc.cpp | 18 ++++++++++++++++++
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
From b7966613b43b01402c9f0af286a0b3237161779d Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 2 Sep 2025 15:33:43 +0200
|
||||||
|
Subject: [PATCH 16/38] tests: processing incomplete events in SSE client
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
During my testing I noticed that I obtained incomplete message in
|
||||||
|
on_data callback. The message was split into two callbacks.
|
||||||
|
This commit fixes the FIXME by buffering the data and processing
|
||||||
|
a complete event, once available.
|
||||||
|
|
||||||
|
Change-Id: Ied07e69e8b518f20fcc82134a4c041e7ec3a06d6
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
tests/restconf_utils.cpp | 53 +++++++++++++++++++++++-----------------
|
||||||
|
tests/restconf_utils.h | 3 ++-
|
||||||
|
2 files changed, 33 insertions(+), 23 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||||
|
index 5b1ebe3..740b1fa 100644
|
||||||
|
--- a/tests/restconf_utils.cpp
|
||||||
|
+++ b/tests/restconf_utils.cpp
|
||||||
|
@@ -199,8 +199,8 @@ SSEClient::SSEClient(
|
||||||
|
req->on_response([&, silenceTimeout, reportIgnoredLines](const ng_client::response& res) {
|
||||||
|
requestSent.release();
|
||||||
|
res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
|
||||||
|
- // not a production-ready code. In real-life condition the data received in one callback might probably be incomplete
|
||||||
|
- parseEvents(std::string(reinterpret_cast<const char*>(data), len), eventWatcher, reportIgnoredLines);
|
||||||
|
+ dataBuffer.append(std::string(reinterpret_cast<const char*>(data), len));
|
||||||
|
+ parseEvents(eventWatcher, reportIgnoredLines);
|
||||||
|
t.expires_from_now(silenceTimeout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@@ -217,30 +217,39 @@ SSEClient::SSEClient(
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
-void SSEClient::parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
|
||||||
|
+void SSEClient::parseEvents(const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
|
||||||
|
{
|
||||||
|
static const std::string dataPrefix = "data:";
|
||||||
|
static const std::string ignorePrefix = ":";
|
||||||
|
|
||||||
|
- std::istringstream iss(msg);
|
||||||
|
- std::string line;
|
||||||
|
- std::string event;
|
||||||
|
-
|
||||||
|
- while (std::getline(iss, line)) {
|
||||||
|
- if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
|
||||||
|
- eventWatcher.commentEvent(line);
|
||||||
|
- } else if (line.starts_with(ignorePrefix)) {
|
||||||
|
- continue;
|
||||||
|
- } else if (line.starts_with(dataPrefix)) {
|
||||||
|
- event += line.substr(dataPrefix.size());
|
||||||
|
- } else if (line.empty() && !event.empty()) {
|
||||||
|
- eventWatcher.dataEvent(event);
|
||||||
|
- event.clear();
|
||||||
|
- } else if (line.empty()) {
|
||||||
|
- continue;
|
||||||
|
- } else {
|
||||||
|
- CAPTURE(msg);
|
||||||
|
- FAIL("Unprefixed response");
|
||||||
|
+ std::size_t pos = 0;
|
||||||
|
+ constexpr auto EVENT_SEPARATOR = "\n\n"; // FIXME: Not a production-ready code; does not deal with all possible newline combinations of CR and LF
|
||||||
|
+
|
||||||
|
+ while ((pos = dataBuffer.find(EVENT_SEPARATOR)) != std::string::npos) {
|
||||||
|
+ // extract event
|
||||||
|
+ auto rawEvent = dataBuffer.substr(0, pos + std::char_traits<char>::length(EVENT_SEPARATOR));
|
||||||
|
+ std::istringstream stream(rawEvent);
|
||||||
|
+ dataBuffer.erase(0, pos + std::char_traits<char>::length(EVENT_SEPARATOR));
|
||||||
|
+
|
||||||
|
+ // split on newlines
|
||||||
|
+ std::string line;
|
||||||
|
+ std::string event;
|
||||||
|
+ while (std::getline(stream, line)) {
|
||||||
|
+ if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
|
||||||
|
+ eventWatcher.commentEvent(line);
|
||||||
|
+ } else if (line.starts_with(ignorePrefix)) {
|
||||||
|
+ continue;
|
||||||
|
+ } else if (line.starts_with(dataPrefix)) {
|
||||||
|
+ event += line.substr(dataPrefix.size());
|
||||||
|
+ } else if (line.empty() && !event.empty()) {
|
||||||
|
+ eventWatcher.dataEvent(event);
|
||||||
|
+ event.clear();
|
||||||
|
+ } else if (line.empty()) {
|
||||||
|
+ continue;
|
||||||
|
+ } else {
|
||||||
|
+ CAPTURE(rawEvent);
|
||||||
|
+ FAIL("Unprefixed response");
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
|
||||||
|
index 9dde10b..c35df54 100644
|
||||||
|
--- a/tests/restconf_utils.h
|
||||||
|
+++ b/tests/restconf_utils.h
|
||||||
|
@@ -87,6 +87,7 @@ void setupRealNacm(sysrepo::Session session);
|
||||||
|
struct SSEClient {
|
||||||
|
std::shared_ptr<ng_client::session> client;
|
||||||
|
boost::asio::deadline_timer t;
|
||||||
|
+ std::string dataBuffer;
|
||||||
|
|
||||||
|
enum class ReportIgnoredLines {
|
||||||
|
No,
|
||||||
|
@@ -104,7 +105,7 @@ struct SSEClient {
|
||||||
|
const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
|
||||||
|
const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
|
||||||
|
|
||||||
|
- static void parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||||
|
+ void parseEvents(const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PREPARE_LOOP_WITH_EXCEPTIONS \
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+6
-3
@@ -1,7 +1,10 @@
|
|||||||
From 86ea8a1fdc2cb8baa1edbd32bd4f8b45acdef816 Mon Sep 17 00:00:00 2001
|
From 1067e05674633b97d64b428686aff44822230c5f Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
Date: Mon, 8 Sep 2025 20:24:15 +0200
|
Date: Mon, 8 Sep 2025 20:24:15 +0200
|
||||||
Subject: [PATCH 12/13] EventStream: fix possible heap-use-after-free
|
Subject: [PATCH 17/38] EventStream: fix possible heap-use-after-free
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
Organization: Wires
|
Organization: Wires
|
||||||
|
|
||||||
I have started getting heap-use-after-free errors from sanitizers (see
|
I have started getting heap-use-after-free errors from sanitizers (see
|
||||||
@@ -61,7 +64,7 @@ before actually resuming the response.
|
|||||||
(...)
|
(...)
|
||||||
|
|
||||||
Change-Id: Ifdb1f8610cacffca3bb49da17aa9b1d267cdd472
|
Change-Id: Ifdb1f8610cacffca3bb49da17aa9b1d267cdd472
|
||||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
---
|
---
|
||||||
src/http/EventStream.cpp | 17 ++++++++++++++++-
|
src/http/EventStream.cpp | 17 ++++++++++++++++-
|
||||||
1 file changed, 16 insertions(+), 1 deletion(-)
|
1 file changed, 16 insertions(+), 1 deletion(-)
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
From fff342945eb3c8deb6f7aa67aca54f9c9f87f3ec Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 11 Sep 2025 18:56:31 +0200
|
||||||
|
Subject: [PATCH 18/38] Use a working version of nghttp2-asio
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
The latest upstream version does not build on Boost 1.87, and there's a
|
||||||
|
nasty crash in the destructor which led to a nice little heap of hair on
|
||||||
|
our desks. It's time to fork that thing.
|
||||||
|
|
||||||
|
Link: https://github.com/nghttp2/nghttp2-asio/pull/9
|
||||||
|
Link: https://github.com/nghttp2/nghttp2-asio/pull/25
|
||||||
|
Change-Id: Iec9619ce45e7e76f0781d39966d0b6c7cc6fa778
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
.zuul.yaml | 2 +-
|
||||||
|
README.md | 3 ++-
|
||||||
|
2 files changed, 3 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||||
|
index e5a87b6..a994f34 100644
|
||||||
|
--- a/.zuul.yaml
|
||||||
|
+++ b/.zuul.yaml
|
||||||
|
@@ -6,7 +6,7 @@
|
||||||
|
required-projects: &projects
|
||||||
|
- CzechLight/dependencies
|
||||||
|
- name: github/nghttp2/nghttp2-asio
|
||||||
|
- override-checkout: main
|
||||||
|
+ override-checkout: cesnet/main
|
||||||
|
vars:
|
||||||
|
zuul_work_dir: "{{ zuul.projects[zuul.project.canonical_hostname + '/CzechLight/rousette'].src_dir }}"
|
||||||
|
|
||||||
|
diff --git a/README.md b/README.md
|
||||||
|
index a42c64d..437afd3 100644
|
||||||
|
--- a/README.md
|
||||||
|
+++ b/README.md
|
||||||
|
@@ -84,7 +84,8 @@ In practical terms, this means that the NACM access rules for the following XPat
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
-- [nghttp2-asio](https://github.com/nghttp2/nghttp2-asio) - asynchronous C++ library for HTTP/2
|
||||||
|
+- [nghttp2-asio](https://github.com/CESNET/nghttp2-asio) - asynchronous C++ library for HTTP/2
|
||||||
|
+ - we strongly recommend using [our fork](https://github.com/CESNET/nghttp2-asio) which appears to be better maintained, and which contains important bugfixes which have not been merged upstream yet
|
||||||
|
- [sysrepo-cpp](https://github.com/sysrepo/sysrepo-cpp) - object-oriented bindings of the [*sysrepo*](https://github.com/sysrepo/sysrepo) library
|
||||||
|
- [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang*
|
||||||
|
- [PAM](http://www.linux-pam.org/) - for authentication
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
From ffc52b2e454ae4ac4fa1948cb76a9469026d453e Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Mon, 6 Oct 2025 22:35:27 +0200
|
||||||
|
Subject: [PATCH 19/38] ping: use a monotonic timer
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
The deadline_timer uses system's UTC clock which is not monotonic (i.e.,
|
||||||
|
it jumps when the wall clock is adjusted). Let's switch to a monotonic
|
||||||
|
timer.
|
||||||
|
|
||||||
|
Given that boost::asio::steady_timer::expires_from_now() is marked as
|
||||||
|
deprecated, use the other function.
|
||||||
|
|
||||||
|
Change-Id: I40383721ecb0f12bfcb3f638124a32150a29bf48
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/http/EventStream.cpp | 2 +-
|
||||||
|
src/http/EventStream.h | 4 ++--
|
||||||
|
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||||
|
index f97d953..280addd 100644
|
||||||
|
--- a/src/http/EventStream.cpp
|
||||||
|
+++ b/src/http/EventStream.cpp
|
||||||
|
@@ -180,7 +180,7 @@ void EventStream::enqueue(const std::string& fieldName, const std::string& what)
|
||||||
|
|
||||||
|
void EventStream::start_ping()
|
||||||
|
{
|
||||||
|
- ping.expires_from_now(boost::posix_time::seconds(m_keepAlivePingInterval.count()));
|
||||||
|
+ ping.expires_after(m_keepAlivePingInterval);
|
||||||
|
ping.async_wait([weak = weak_from_this()](const boost::system::error_code& ec) {
|
||||||
|
auto myself = weak.lock();
|
||||||
|
if (!myself) {
|
||||||
|
diff --git a/src/http/EventStream.h b/src/http/EventStream.h
|
||||||
|
index 87d8c82..f9f0dd6 100644
|
||||||
|
--- a/src/http/EventStream.h
|
||||||
|
+++ b/src/http/EventStream.h
|
||||||
|
@@ -7,7 +7,7 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
-#include <boost/asio/deadline_timer.hpp>
|
||||||
|
+#include <boost/asio/steady_timer.hpp>
|
||||||
|
#include <boost/signals2.hpp>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
@@ -50,7 +50,7 @@ private:
|
||||||
|
};
|
||||||
|
|
||||||
|
State state = WaitingForEvents;
|
||||||
|
- boost::asio::deadline_timer ping;
|
||||||
|
+ boost::asio::steady_timer ping;
|
||||||
|
std::list<std::string> queue;
|
||||||
|
mutable std::mutex mtx; // for `state` and `queue`
|
||||||
|
boost::signals2::scoped_connection eventSub, terminateSub;
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
From 4493316404f31354ea18bc92a1706a3834ad972b Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Mon, 6 Oct 2025 22:50:11 +0200
|
||||||
|
Subject: [PATCH 20/38] tests: use a monotonic timer
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: I89cdd08082b025643dac81788ca174d3c2177089
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
tests/restconf-eventstream.cpp | 2 +-
|
||||||
|
tests/restconf_utils.cpp | 6 +++---
|
||||||
|
tests/restconf_utils.h | 4 ++--
|
||||||
|
3 files changed, 6 insertions(+), 6 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/tests/restconf-eventstream.cpp b/tests/restconf-eventstream.cpp
|
||||||
|
index ad1040d..ecb86e3 100644
|
||||||
|
--- a/tests/restconf-eventstream.cpp
|
||||||
|
+++ b/tests/restconf-eventstream.cpp
|
||||||
|
@@ -101,7 +101,7 @@ TEST_CASE("Event stream tests")
|
||||||
|
netconfWatcher,
|
||||||
|
"/streams/NETCONF/JSON",
|
||||||
|
std::map<std::string, std::string>{AUTH_ROOT},
|
||||||
|
- boost::posix_time::seconds{5},
|
||||||
|
+ std::chrono::seconds{5},
|
||||||
|
SSEClient::ReportIgnoredLines::Yes);
|
||||||
|
|
||||||
|
RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||||
|
index 740b1fa..cb4e26a 100644
|
||||||
|
--- a/tests/restconf_utils.cpp
|
||||||
|
+++ b/tests/restconf_utils.cpp
|
||||||
|
@@ -172,10 +172,10 @@ SSEClient::SSEClient(
|
||||||
|
const RestconfNotificationWatcher& eventWatcher,
|
||||||
|
const std::string& uri,
|
||||||
|
const std::map<std::string, std::string>& headers,
|
||||||
|
- const boost::posix_time::seconds silenceTimeout,
|
||||||
|
+ const std::chrono::seconds silenceTimeout,
|
||||||
|
const ReportIgnoredLines reportIgnoredLines)
|
||||||
|
: client(std::make_shared<ng_client::session>(io, server_address, server_port))
|
||||||
|
- , t(io, silenceTimeout)
|
||||||
|
+ , t(io, std::chrono::seconds(silenceTimeout))
|
||||||
|
{
|
||||||
|
ng::header_map reqHeaders;
|
||||||
|
for (const auto& [name, value] : headers) {
|
||||||
|
@@ -201,7 +201,7 @@ SSEClient::SSEClient(
|
||||||
|
res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
|
||||||
|
dataBuffer.append(std::string(reinterpret_cast<const char*>(data), len));
|
||||||
|
parseEvents(eventWatcher, reportIgnoredLines);
|
||||||
|
- t.expires_from_now(silenceTimeout);
|
||||||
|
+ t.expires_after(std::chrono::seconds(silenceTimeout));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
|
||||||
|
index c35df54..a5676f4 100644
|
||||||
|
--- a/tests/restconf_utils.h
|
||||||
|
+++ b/tests/restconf_utils.h
|
||||||
|
@@ -86,7 +86,7 @@ void setupRealNacm(sysrepo::Session session);
|
||||||
|
|
||||||
|
struct SSEClient {
|
||||||
|
std::shared_ptr<ng_client::session> client;
|
||||||
|
- boost::asio::deadline_timer t;
|
||||||
|
+ boost::asio::steady_timer t;
|
||||||
|
std::string dataBuffer;
|
||||||
|
|
||||||
|
enum class ReportIgnoredLines {
|
||||||
|
@@ -102,7 +102,7 @@ struct SSEClient {
|
||||||
|
const RestconfNotificationWatcher& eventWatcher,
|
||||||
|
const std::string& uri,
|
||||||
|
const std::map<std::string, std::string>& headers,
|
||||||
|
- const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
|
||||||
|
+ const std::chrono::seconds silenceTimeout = std::chrono::seconds{1}, // test code; the server should respond "soon"
|
||||||
|
const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
|
||||||
|
|
||||||
|
void parseEvents(const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
From 8077a63ac97529d56782d2315f23c44f6036b85f Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 23 Oct 2025 18:17:37 +0200
|
||||||
|
Subject: [PATCH 21/38] port to libyang v4
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
- the prefix of the "ietf-netconf-with-defaults" module has changed,
|
||||||
|
- RPC parsing needs a flag to reject unknown input data nodes,
|
||||||
|
- due to the "printed context" mess, we now need an extra flag to ensure
|
||||||
|
that we can still access the YANG schema source.
|
||||||
|
|
||||||
|
The last item is interesting. We could also read directly from the
|
||||||
|
on-disk location, but that looks a bit meh. It's also a bit annoying
|
||||||
|
that one has to change this thing over a lot of places, but I think that
|
||||||
|
this is much nicer than trying to force a change over an existing
|
||||||
|
connection (e.g., via a connection-specific call in the Server::Server
|
||||||
|
ctor which might hit some internal locks depending on how the sysrepo
|
||||||
|
state look like).
|
||||||
|
|
||||||
|
I was considering adding a wrapper for `ly_ctx_get_options()` and a
|
||||||
|
check in Server::Server, but then I decided that I won't bother.
|
||||||
|
|
||||||
|
Oh, and this "disable printed context" is also needed for the RESTCONF
|
||||||
|
*client* which is used in the unit test. The `lyd_parse_op` cannot parse
|
||||||
|
notifications *iff* the `notifications` module from RFC 5277 is missing
|
||||||
|
*and* when the libyang context does not have the "parsed" info from the
|
||||||
|
`ietf-yang-types` available. The printed context, as provided by
|
||||||
|
sysrepo, does not have that data available. This is all papered over as
|
||||||
|
long as the context flag setting works, which is the case with
|
||||||
|
post-4.2.4 version of sysrepo (still untagged). Yay.
|
||||||
|
|
||||||
|
We still set these flags to a single value in all of the unit tests and
|
||||||
|
in the main() as well to maintain the remaining bits of our sanity, of
|
||||||
|
course.
|
||||||
|
|
||||||
|
Change-Id: I142f650affb53411b5c1dd81311400a2aa6dec20
|
||||||
|
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8990
|
||||||
|
Bug: https://github.com/CESNET/libyang/pull/2448
|
||||||
|
Depends-on: https://github.com/sysrepo/sysrepo/commit/6dc5641762962b93d54a54443e2fd43aa319a7a6
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 6 +++---
|
||||||
|
src/restconf/Server.cpp | 2 +-
|
||||||
|
src/restconf/main.cpp | 4 ++++
|
||||||
|
tests/restconf-defaults.cpp | 2 ++
|
||||||
|
tests/restconf-delete.cpp | 2 ++
|
||||||
|
tests/restconf-eventstream.cpp | 1 +
|
||||||
|
tests/restconf-nacm.cpp | 2 ++
|
||||||
|
tests/restconf-notifications.cpp | 1 +
|
||||||
|
tests/restconf-plain-patch.cpp | 2 ++
|
||||||
|
tests/restconf-reading.cpp | 12 +++++++-----
|
||||||
|
tests/restconf-rpc.cpp | 2 ++
|
||||||
|
tests/restconf-writing.cpp | 2 ++
|
||||||
|
tests/restconf-yang-patch.cpp | 2 ++
|
||||||
|
tests/restconf-yang-schema.cpp | 2 ++
|
||||||
|
14 files changed, 33 insertions(+), 9 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index c0304af..1223ded 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -74,9 +74,9 @@ find_package(PkgConfig)
|
||||||
|
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||||
|
find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||||
|
|
||||||
|
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||||
|
-pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=6)
|
||||||
|
-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=4)
|
||||||
|
+pkg_check_modules(SYSREPO REQUIRED sysrepo IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=7)
|
||||||
|
+pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=5)
|
||||||
|
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||||
|
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||||
|
pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt)
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 74c06f7..633f0bb 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -487,7 +487,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||||
|
auto [parent, rpcNode] = ctx.newPath2(requestCtx->restconfRequest.path);
|
||||||
|
|
||||||
|
if (!requestCtx->payload.empty()) {
|
||||||
|
- rpcNode->parseOp(requestCtx->payload, *requestCtx->dataFormat.request, libyang::OperationType::RpcRestconf);
|
||||||
|
+ rpcNode->parseOp(requestCtx->payload, *requestCtx->dataFormat.request, libyang::OperationType::RpcRestconf, libyang::ParseOptions::Strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<libyang::DataNode> rpcReply;
|
||||||
|
diff --git a/src/restconf/main.cpp b/src/restconf/main.cpp
|
||||||
|
index 477e846..b1e2bc6 100644
|
||||||
|
--- a/src/restconf/main.cpp
|
||||||
|
+++ b/src/restconf/main.cpp
|
||||||
|
@@ -22,6 +22,7 @@
|
||||||
|
#include <docopt.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <sysrepo-cpp/Session.hpp>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
static const char usage[] =
|
||||||
|
R"(Rousette - RESTCONF server
|
||||||
|
@@ -103,6 +104,9 @@ int main(int argc, char* argv [])
|
||||||
|
throw std::runtime_error("Could not set locale C.UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
+ // schema access is required
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
+
|
||||||
|
auto conn = sysrepo::Connection{};
|
||||||
|
auto server = rousette::restconf::Server{conn, "::1", "10080", timeout};
|
||||||
|
|
||||||
|
diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp
|
||||||
|
index 89083f1..e783e59 100644
|
||||||
|
--- a/tests/restconf-defaults.cpp
|
||||||
|
+++ b/tests/restconf-defaults.cpp
|
||||||
|
@@ -9,6 +9,7 @@
|
||||||
|
static const auto SERVER_PORT = "10087";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
@@ -18,6 +19,7 @@ TEST_CASE("default handling")
|
||||||
|
trompeloeil::sequence seq1;
|
||||||
|
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
auto nacmGuard = manageNacm(srSess);
|
||||||
|
diff --git a/tests/restconf-delete.cpp b/tests/restconf-delete.cpp
|
||||||
|
index 4818ff3..3b716bd 100644
|
||||||
|
--- a/tests/restconf-delete.cpp
|
||||||
|
+++ b/tests/restconf-delete.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
static const auto SERVER_PORT = "10086";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10086";
|
||||||
|
TEST_CASE("deleting data")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
auto nacmGuard = manageNacm(srSess);
|
||||||
|
diff --git a/tests/restconf-eventstream.cpp b/tests/restconf-eventstream.cpp
|
||||||
|
index ecb86e3..a099f54 100644
|
||||||
|
--- a/tests/restconf-eventstream.cpp
|
||||||
|
+++ b/tests/restconf-eventstream.cpp
|
||||||
|
@@ -29,6 +29,7 @@ TEST_CASE("Event stream tests")
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||||
|
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||||
|
index 7799fe8..0e06d56 100644
|
||||||
|
--- a/tests/restconf-nacm.cpp
|
||||||
|
+++ b/tests/restconf-nacm.cpp
|
||||||
|
@@ -10,11 +10,13 @@ static const auto SERVER_PORT = "10082";
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
|
||||||
|
TEST_CASE("NACM")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
auto nacmGuard = manageNacm(srSess);
|
||||||
|
diff --git a/tests/restconf-notifications.cpp b/tests/restconf-notifications.cpp
|
||||||
|
index 04131d7..ac355d8 100644
|
||||||
|
--- a/tests/restconf-notifications.cpp
|
||||||
|
+++ b/tests/restconf-notifications.cpp
|
||||||
|
@@ -29,6 +29,7 @@ TEST_CASE("NETCONF notification streams")
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||||
|
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
diff --git a/tests/restconf-plain-patch.cpp b/tests/restconf-plain-patch.cpp
|
||||||
|
index 34813d1..c7e035d 100644
|
||||||
|
--- a/tests/restconf-plain-patch.cpp
|
||||||
|
+++ b/tests/restconf-plain-patch.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
static const auto SERVER_PORT = "10089";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10089";
|
||||||
|
TEST_CASE("Plain patch")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
auto nacmGuard = manageNacm(srSess);
|
||||||
|
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||||
|
index 4f0d2ee..b32a598 100644
|
||||||
|
--- a/tests/restconf-reading.cpp
|
||||||
|
+++ b/tests/restconf-reading.cpp
|
||||||
|
@@ -10,12 +10,14 @@ static const auto SERVER_PORT = "10081";
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
|
||||||
|
TEST_CASE("reading data")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
@@ -794,7 +796,7 @@ TEST_CASE("reading data")
|
||||||
|
"c": {
|
||||||
|
"enabled": true,
|
||||||
|
"@enabled": {
|
||||||
|
- "ietf-netconf-with-defaults:default": true
|
||||||
|
+ "default:default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@@ -803,7 +805,7 @@ TEST_CASE("reading data")
|
||||||
|
"c": {
|
||||||
|
"enabled": true,
|
||||||
|
"@enabled": {
|
||||||
|
- "ietf-netconf-with-defaults:default": true
|
||||||
|
+ "default:default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -868,7 +870,7 @@ TEST_CASE("reading data")
|
||||||
|
"c": {
|
||||||
|
"enabled": true,
|
||||||
|
"@enabled": {
|
||||||
|
- "ietf-netconf-with-defaults:default": true
|
||||||
|
+ "default:default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
@@ -877,7 +879,7 @@ TEST_CASE("reading data")
|
||||||
|
"c": {
|
||||||
|
"enabled": true,
|
||||||
|
"@enabled": {
|
||||||
|
- "ietf-netconf-with-defaults:default": true
|
||||||
|
+ "default:default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -932,7 +934,7 @@ TEST_CASE("reading data")
|
||||||
|
"c": {
|
||||||
|
"enabled": true,
|
||||||
|
"@enabled": {
|
||||||
|
- "ietf-netconf-with-defaults:default": true
|
||||||
|
+ "default:default": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp
|
||||||
|
index 43aaed4..bcbd397 100644
|
||||||
|
--- a/tests/restconf-rpc.cpp
|
||||||
|
+++ b/tests/restconf-rpc.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
static const auto SERVER_PORT = "10084";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
@@ -33,6 +34,7 @@ std::map<std::string, std::string> nodesToMap(libyang::DataNode node)
|
||||||
|
TEST_CASE("invoking actions and rpcs")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
SUBSCRIBE_MODULE(sub1, srSess, "example");
|
||||||
|
diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||||
|
index d37ab03..3797b72 100644
|
||||||
|
--- a/tests/restconf-writing.cpp
|
||||||
|
+++ b/tests/restconf-writing.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
static const auto SERVER_PORT = "10083";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10083";
|
||||||
|
TEST_CASE("writing data")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
auto nacmGuard = manageNacm(srSess);
|
||||||
|
diff --git a/tests/restconf-yang-patch.cpp b/tests/restconf-yang-patch.cpp
|
||||||
|
index 2b35c59..19e0405 100644
|
||||||
|
--- a/tests/restconf-yang-patch.cpp
|
||||||
|
+++ b/tests/restconf-yang-patch.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
static const auto SERVER_PORT = "10090";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
#include "tests/event_watchers.h"
|
||||||
|
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10090";
|
||||||
|
TEST_CASE("YANG patch")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
auto nacmGuard = manageNacm(srSess);
|
||||||
|
diff --git a/tests/restconf-yang-schema.cpp b/tests/restconf-yang-schema.cpp
|
||||||
|
index 6e374b1..b56367d 100644
|
||||||
|
--- a/tests/restconf-yang-schema.cpp
|
||||||
|
+++ b/tests/restconf-yang-schema.cpp
|
||||||
|
@@ -9,12 +9,14 @@
|
||||||
|
static const auto SERVER_PORT = "10085";
|
||||||
|
#include <nghttp2/asio_http2.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
#include "restconf/Server.h"
|
||||||
|
#include "tests/aux-utils.h"
|
||||||
|
|
||||||
|
TEST_CASE("obtaining YANG schemas")
|
||||||
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
From b4bf655502d949a5f351e6acd313f47eecefb062 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Mon, 13 Oct 2025 12:26:46 +0200
|
||||||
|
Subject: [PATCH 22/38] restconf: prevent lock order inversion
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
The locked section in EventStream's implementation of nghttp's on_close
|
||||||
|
callback. Without doing that, we might get a lock order inversion in
|
||||||
|
case somebody tries to lock the mutex inside onClientDisconnectedCb. And
|
||||||
|
this would indeed happen with the next patch. If that is applied, then
|
||||||
|
there are indeed two different mutexes involved and the lock order
|
||||||
|
inversion might happen.
|
||||||
|
|
||||||
|
- Case 1: A thread acquires the mutex (let's call it M1) in
|
||||||
|
DynamicNotificationHTTPStream::awaitNextNotification and
|
||||||
|
then sends the notification signal, which acquires the mutex inside
|
||||||
|
EventStream (mutex M2).
|
||||||
|
|
||||||
|
- Case 2: the nghttp's on_close callback acquired M2 and
|
||||||
|
then proceeds to call onClientDisconnectedCb which, in our case,
|
||||||
|
acquires M1.
|
||||||
|
|
||||||
|
One can view this as a fixup of commit 6bca750 ("http: add optional
|
||||||
|
callbacks to EventStream"), I am not sure.
|
||||||
|
|
||||||
|
See-also: 6bca750f866b5b14c4d9c3da68e5c8f1e4eee36c ("http: add optional callbacks to EventStream")
|
||||||
|
Change-Id: Ib6626c127ff2eb5feb1d17dece28e70265749df4
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/http/EventStream.cpp | 14 +++++++++-----
|
||||||
|
1 file changed, 9 insertions(+), 5 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||||
|
index 280addd..9f19030 100644
|
||||||
|
--- a/src/http/EventStream.cpp
|
||||||
|
+++ b/src/http/EventStream.cpp
|
||||||
|
@@ -82,11 +82,15 @@ void EventStream::activate()
|
||||||
|
|
||||||
|
res.on_close([myself](const auto ec) {
|
||||||
|
spdlog::debug("{}: closed ({})", myself->peer, nghttp2_http2_strerror(ec));
|
||||||
|
- std::lock_guard lock{myself->mtx};
|
||||||
|
- myself->ping.cancel();
|
||||||
|
- myself->eventSub.disconnect();
|
||||||
|
- myself->terminateSub.disconnect();
|
||||||
|
- myself->state = Closed;
|
||||||
|
+
|
||||||
|
+ {
|
||||||
|
+ std::lock_guard lock{myself->mtx};
|
||||||
|
+ myself->ping.cancel();
|
||||||
|
+ myself->eventSub.disconnect();
|
||||||
|
+ myself->terminateSub.disconnect();
|
||||||
|
+ myself->state = Closed;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
if (myself->onClientDisconnectedCb) {
|
||||||
|
myself->onClientDisconnectedCb();
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
From 27cbe6750bdfc9f346421e55ebe555b8c036d77c Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 11 Nov 2025 15:01:21 +0100
|
||||||
|
Subject: [PATCH 23/38] refactor: do not pollute scope with unnecessary
|
||||||
|
variables
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
The parent variable was not used in the function, it contributed
|
||||||
|
nothing.
|
||||||
|
|
||||||
|
Change-Id: I5b9b1a077d44d088084c3b2b93359ab7464ec8aa
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/Server.cpp | 6 +++---
|
||||||
|
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 633f0bb..77ae1be 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -446,9 +446,9 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, cons
|
||||||
|
const auto rpcPath = rpcInput.path();
|
||||||
|
|
||||||
|
// Is the user authorized to call the operation?
|
||||||
|
- auto [parent, rpcNode] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||||
|
- if (!sess.checkNacmOperation(*rpcNode)) {
|
||||||
|
- throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode->path());
|
||||||
|
+ auto rpcNode = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||||
|
+ if (!sess.checkNacmOperation(*rpcNode.createdNode)) {
|
||||||
|
+ throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode.createdNode->path());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto it = handlers.find(rpcPath); it != handlers.end()) {
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
From 5592ae49cd7607fdcb2c9e554a521b53545e761b Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 21 Oct 2025 17:23:44 +0200
|
||||||
|
Subject: [PATCH 24/38] restconf: validate input for internal rpcs
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Jan found an issue where invalid input to internal rpcs was accepted. It
|
||||||
|
turns out that the issue was in parsing, but not validating input for
|
||||||
|
internally processed RPCs (those RPCs for subscribed notifications).
|
||||||
|
|
||||||
|
This patch adds the validation using a wrapper for lyd_validate_op from
|
||||||
|
libyang-cpp.
|
||||||
|
|
||||||
|
I shuffled a little bit of the code around together with this change to
|
||||||
|
make it cleaner.
|
||||||
|
Also, to make the code generic, we store the name of the internal RPC
|
||||||
|
and a XPath for fetching data required for validation (see
|
||||||
|
lyd_validate_op docs). The code validates the input data before processing
|
||||||
|
the RPC.
|
||||||
|
|
||||||
|
Change-Id: I55296895e6993b4ca27d21ce2a64ec2d159a35fc
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/Server.cpp | 36 +++++++++++++++++++++++++++++-------
|
||||||
|
1 file changed, 29 insertions(+), 7 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 77ae1be..7e8db58 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -438,9 +438,12 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||||
|
return {editNode, replacementNode};
|
||||||
|
}
|
||||||
|
|
||||||
|
-std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, const libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||||
|
+std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||||
|
{
|
||||||
|
- using InternalRPCHandler = std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)>;
|
||||||
|
+ struct InternalRPCHandler {
|
||||||
|
+ std::optional<std::string> validationDataXPath; ///< XPath to data used for RPC input validation
|
||||||
|
+ std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)> rpcHandler; // The function that processes the RPC
|
||||||
|
+ };
|
||||||
|
const std::map<std::string, InternalRPCHandler> handlers;
|
||||||
|
|
||||||
|
const auto rpcPath = rpcInput.path();
|
||||||
|
@@ -451,13 +454,32 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, cons
|
||||||
|
throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode.createdNode->path());
|
||||||
|
}
|
||||||
|
|
||||||
|
- if (auto it = handlers.find(rpcPath); it != handlers.end()) {
|
||||||
|
- auto [parent, rpcOutput] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||||
|
- it->second(sess, requestEncoding, rpcInput, *rpcOutput);
|
||||||
|
- return *parent;
|
||||||
|
+ auto handlerIt = handlers.find(rpcPath);
|
||||||
|
+ if (handlerIt == handlers.end()) {
|
||||||
|
+ throw ErrorResponse(501, "application", "operation-not-supported", "Unsupported RPC call to " + rpcPath, rpcPath);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // RPC input validation.
|
||||||
|
+ sess.switchDatastore(sysrepo::Datastore::Operational);
|
||||||
|
+ auto validationData = handlerIt->second.validationDataXPath ? sess.getData(*handlerIt->second.validationDataXPath) : std::nullopt;
|
||||||
|
+
|
||||||
|
+ try {
|
||||||
|
+ libyang::validateOp(rpcInput, validationData, libyang::OperationType::RpcRestconf);
|
||||||
|
+ } catch (const libyang::ErrorWithCode& exc) {
|
||||||
|
+ const auto errors = sess.getContext().getErrors();
|
||||||
|
+
|
||||||
|
+ if (!errors.empty()) {
|
||||||
|
+ const auto& error = errors.back();
|
||||||
|
+ throw ErrorResponse(400, "protocol", "invalid-value", error.message, error.dataPath.value_or(rpcInput.path()));
|
||||||
|
+ } else {
|
||||||
|
+ // Fallback if no detailed error is available
|
||||||
|
+ throw ErrorResponse(400, "protocol", "invalid-value", "Input validation failed"s, rpcInput.path());
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
- throw ErrorResponse(501, "application", "operation-not-supported", "Unsupported RPC call to " + rpcPath, rpcPath);
|
||||||
|
+ auto [parent, rpcOutput] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||||
|
+ handlerIt->second.rpcHandler(sess, requestEncoding, rpcInput, *rpcOutput);
|
||||||
|
+ return *parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
From c2413237501eea3125fe31a9701919d14402737f Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 11 Nov 2025 17:30:33 +0100
|
||||||
|
Subject: [PATCH 25/38] tests: allow read from parts
|
||||||
|
ietf-subscribed-notifications for anonymous user
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
The subsequent commits will add more tests for subscribed notifications.
|
||||||
|
Because the validation of
|
||||||
|
ietf-subscribed-notifications:establish-subscription will require read
|
||||||
|
access to ietf-subscribed-notifications:filters, we need to allow the
|
||||||
|
access for users.
|
||||||
|
|
||||||
|
Change-Id: I50e49c8c60b7e6be47c51f3d8cd546fbb6f49294
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
tests/restconf-reading.cpp | 5 ++++-
|
||||||
|
tests/restconf_utils.cpp | 4 ++++
|
||||||
|
2 files changed, 8 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||||
|
index b32a598..b11bc48 100644
|
||||||
|
--- a/tests/restconf-reading.cpp
|
||||||
|
+++ b/tests/restconf-reading.cpp
|
||||||
|
@@ -110,6 +110,7 @@ TEST_CASE("reading data")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
+ "ietf-subscribed-notifications:filters": {},
|
||||||
|
"ietf-system:system": {
|
||||||
|
"contact": "contact",
|
||||||
|
"hostname": "hostname",
|
||||||
|
@@ -168,6 +169,7 @@ TEST_CASE("reading data")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
+ "ietf-subscribed-notifications:filters": {},
|
||||||
|
"ietf-system:system": {
|
||||||
|
"contact": "contact",
|
||||||
|
"hostname": "hostname",
|
||||||
|
@@ -195,7 +197,8 @@ TEST_CASE("reading data")
|
||||||
|
"example:config-nonconfig": {
|
||||||
|
"config-node": "foo-config-true"
|
||||||
|
},
|
||||||
|
- "example:ordered-lists": {}
|
||||||
|
+ "example:ordered-lists": {},
|
||||||
|
+ "ietf-subscribed-notifications:filters": {}
|
||||||
|
}
|
||||||
|
)"});
|
||||||
|
}
|
||||||
|
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||||
|
index cb4e26a..80dca05 100644
|
||||||
|
--- a/tests/restconf_utils.cpp
|
||||||
|
+++ b/tests/restconf_utils.cpp
|
||||||
|
@@ -156,6 +156,10 @@ void setupRealNacm(sysrepo::Session session)
|
||||||
|
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/action", "permit");
|
||||||
|
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/access-operations", "exec");
|
||||||
|
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/rpc-name", "establish-subscription");
|
||||||
|
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/module-name", "ietf-subscribed-notifications");
|
||||||
|
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/action", "permit");
|
||||||
|
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/access-operations", "read");
|
||||||
|
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/path", "/ietf-subscribed-notifications:filters");
|
||||||
|
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/module-name", "*");
|
||||||
|
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/action", "deny");
|
||||||
|
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,679 @@
|
|||||||
|
From aa04b0a99fb51aed998aa9f1ab0a527cc09e647f Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Mon, 19 May 2025 12:11:09 +0200
|
||||||
|
Subject: [PATCH 26/38] restconf: establish-subscription RPC for subscribed
|
||||||
|
notifications
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
This is a very bare-bones implementation of the subscribed
|
||||||
|
notifications concept from RFC 8639 [1]. We only support the
|
||||||
|
establish-subscription RPC to the NETCONF event stream.
|
||||||
|
|
||||||
|
This patch *does not* ship the code which implements the endpoint for
|
||||||
|
client connection because this patch is already too large and the
|
||||||
|
endpoint is not strictly necessary for the RPC to work. It will come in
|
||||||
|
the follow-up patch.
|
||||||
|
|
||||||
|
[1] https://datatracker.ietf.org/doc/rfc8639/
|
||||||
|
|
||||||
|
Change-Id: I0217e5abc56cfd73859dbcc610fb1f342dc33a10
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 8 +-
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 177 ++++++++++++++
|
||||||
|
src/restconf/DynamicSubscriptions.h | 57 +++++
|
||||||
|
src/restconf/Server.cpp | 25 +-
|
||||||
|
src/restconf/Server.h | 2 +
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 253 ++++++++++++++++++++
|
||||||
|
6 files changed, 513 insertions(+), 9 deletions(-)
|
||||||
|
create mode 100644 src/restconf/DynamicSubscriptions.cpp
|
||||||
|
create mode 100644 src/restconf/DynamicSubscriptions.h
|
||||||
|
create mode 100644 tests/restconf-subscribed-notifications.cpp
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 1223ded..18af5ba 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -110,6 +110,7 @@ add_library(rousette-auth STATIC
|
||||||
|
target_link_libraries(rousette-auth PUBLIC spdlog::spdlog PkgConfig::SYSREPO-CPP PkgConfig::PAM rousette-auth-pam PkgConfig::nghttp2)
|
||||||
|
|
||||||
|
add_library(rousette-restconf STATIC
|
||||||
|
+ src/restconf/DynamicSubscriptions.cpp
|
||||||
|
src/restconf/NotificationStream.cpp
|
||||||
|
src/restconf/Server.cpp
|
||||||
|
src/restconf/YangSchemaLocations.cpp
|
||||||
|
@@ -211,7 +212,11 @@ if(BUILD_TESTING)
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf@2017-01-26.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-monitoring@2017-01-26.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-yang-patch@2017-02-22.yang
|
||||||
|
- --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/iana-if-type@2014-05-08.yang
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||||
|
@@ -228,6 +233,7 @@ if(BUILD_TESTING)
|
||||||
|
rousette_test(NAME restconf-plain-patch LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||||
|
rousette_test(NAME restconf-yang-patch LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||||
|
rousette_test(NAME restconf-eventstream LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||||
|
+ rousette_test(NAME restconf-subscribed-notifications LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||||
|
set(nested-models
|
||||||
|
${common-models}
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/root-mod.yang)
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..7b13990
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -0,0 +1,177 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||||
|
+ *
|
||||||
|
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||||
|
+ *
|
||||||
|
+*/
|
||||||
|
+#include <boost/uuid/uuid_io.hpp>
|
||||||
|
+#include <fmt/ostream.h>
|
||||||
|
+#include <libyang-cpp/Time.hpp>
|
||||||
|
+#include <nghttp2/asio_http2_server.h>
|
||||||
|
+#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/exception.hpp>
|
||||||
|
+#include "restconf/DynamicSubscriptions.h"
|
||||||
|
+#include "restconf/Exceptions.h"
|
||||||
|
+
|
||||||
|
+namespace {
|
||||||
|
+
|
||||||
|
+/** @brief Parses the YANG date-and-time attribute from the RPC input, if present
|
||||||
|
+ *
|
||||||
|
+ * @param rpcInput The RPC input node.
|
||||||
|
+ * @param path The path to the YANG leaf.
|
||||||
|
+ */
|
||||||
|
+std::optional<sysrepo::NotificationTimeStamp> optionalTime(const libyang::DataNode& rpcInput, const std::string& path)
|
||||||
|
+{
|
||||||
|
+ if (auto stopTimeNode = rpcInput.findPath(path)) {
|
||||||
|
+ return libyang::fromYangTimeFormat<sysrepo::NotificationTimeStamp::clock>(stopTimeNode->asTerm().valueStr());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return std::nullopt;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+libyang::DataFormat getEncoding(const libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||||
|
+{
|
||||||
|
+ /* FIXME: So far we allow only encode-json or encode-xml encoding values and not their derived values.
|
||||||
|
+ * We do not know what those derived values might mean and how do they change the meaning of the encoding leaf.
|
||||||
|
+ */
|
||||||
|
+ if (auto encodingNode = rpcInput.findPath("encoding")) {
|
||||||
|
+ const auto encodingStr = encodingNode->asTerm().valueStr();
|
||||||
|
+ if (encodingStr == "ietf-subscribed-notifications:encode-json") {
|
||||||
|
+ return libyang::DataFormat::JSON;
|
||||||
|
+ } else if (encodingStr == "ietf-subscribed-notifications:encode-xml") {
|
||||||
|
+ return libyang::DataFormat::XML;
|
||||||
|
+ } else {
|
||||||
|
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Unsupported encoding in establish-subscription: '" + encodingStr + "'. Currently we support only 'encode-xml' and 'encode-json' identities.");
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return requestEncoding;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, const libyang::DataNode& rpcInput)
|
||||||
|
+{
|
||||||
|
+ auto streamNode = rpcInput.findPath("stream");
|
||||||
|
+
|
||||||
|
+ if (!streamNode) {
|
||||||
|
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream is required");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (rpcInput.findPath("stream-filter-name")) {
|
||||||
|
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream filtering is not supported");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto stopTime = optionalTime(rpcInput, "stop-time");
|
||||||
|
+
|
||||||
|
+ return session.subscribeNotifications(
|
||||||
|
+ std::nullopt /* TODO xpath filter */,
|
||||||
|
+ streamNode->asTerm().valueStr(),
|
||||||
|
+ stopTime,
|
||||||
|
+ std::nullopt /* TODO replayStart */);
|
||||||
|
+}
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+namespace rousette::restconf {
|
||||||
|
+
|
||||||
|
+DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri)
|
||||||
|
+ : m_restconfStreamUri(streamRootUri)
|
||||||
|
+ , m_uuidGenerator(boost::uuids::random_generator())
|
||||||
|
+{
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+DynamicSubscriptions::~DynamicSubscriptions() = default;
|
||||||
|
+
|
||||||
|
+void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput)
|
||||||
|
+{
|
||||||
|
+ // Generate a new UUID associated with the subscription. The UUID will be used as a part of the URI so that the URI is not predictable (RFC 8650, section 5)
|
||||||
|
+ auto uuid = makeUUID();
|
||||||
|
+
|
||||||
|
+ auto dataFormat = getEncoding(rpcInput, requestEncoding);
|
||||||
|
+
|
||||||
|
+ try {
|
||||||
|
+ auto sub = makeStreamSubscription(session, rpcInput);
|
||||||
|
+
|
||||||
|
+ rpcOutput.newPath("id", std::to_string(sub.subscriptionId()), libyang::CreationOptions::Output);
|
||||||
|
+ rpcOutput.newPath("ietf-restconf-subscribed-notifications:uri", m_restconfStreamUri + "subscribed/" + boost::uuids::to_string(uuid), libyang::CreationOptions::Output);
|
||||||
|
+
|
||||||
|
+ std::lock_guard lock(m_mutex);
|
||||||
|
+ m_subscriptions[uuid] = std::make_shared<SubscriptionData>(
|
||||||
|
+ std::move(sub),
|
||||||
|
+ dataFormat,
|
||||||
|
+ uuid,
|
||||||
|
+ *session.getNacmUser());
|
||||||
|
+ } catch (const sysrepo::ErrorWithCode& e) {
|
||||||
|
+ throw ErrorResponse(400, "application", "invalid-attribute", e.what());
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void DynamicSubscriptions::terminateSubscription(const uint32_t subId)
|
||||||
|
+{
|
||||||
|
+ std::lock_guard lock(m_mutex);
|
||||||
|
+
|
||||||
|
+ auto it = std::find_if(m_subscriptions.begin(), m_subscriptions.end(), [subId](const auto& entry) {
|
||||||
|
+ return entry.second->subscription.subscriptionId() == subId;
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ if (it == m_subscriptions.end()) {
|
||||||
|
+ spdlog::warn("Requested termination of subscription with id {}, but subscription not found", subId);
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const auto& [uuid, subscriptionData] = *it;
|
||||||
|
+ spdlog::debug("{}: termination requested", fmt::streamed(*subscriptionData));
|
||||||
|
+ subscriptionData->subscription.terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||||
|
+ m_subscriptions.erase(uuid);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/** @brief Returns the subscription data for the given UUID and user.
|
||||||
|
+ *
|
||||||
|
+ * @param uuid The UUID of the subscription.
|
||||||
|
+ * @return A shared pointer to the SubscriptionData object if found and user is the one who established the subscription (or NACM recovery user), otherwise nullptr.
|
||||||
|
+ */
|
||||||
|
+std::shared_ptr<DynamicSubscriptions::SubscriptionData> DynamicSubscriptions::getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user)
|
||||||
|
+{
|
||||||
|
+ std::lock_guard lock(m_mutex);
|
||||||
|
+ if (auto it = m_subscriptions.find(uuid); it != m_subscriptions.end() && (it->second->user == user || user == sysrepo::Session::getNacmRecoveryUser())) {
|
||||||
|
+ return it->second;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return nullptr;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+boost::uuids::uuid DynamicSubscriptions::makeUUID()
|
||||||
|
+{
|
||||||
|
+ // uuid generator instance accesses must be synchronized (https://www.boost.org/doc/libs/1_88_0/libs/uuid/doc/html/uuid.html#design_notes)
|
||||||
|
+ std::lock_guard lock(m_mutex);
|
||||||
|
+ return m_uuidGenerator();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+DynamicSubscriptions::SubscriptionData::SubscriptionData(
|
||||||
|
+ sysrepo::DynamicSubscription sub,
|
||||||
|
+ libyang::DataFormat format,
|
||||||
|
+ boost::uuids::uuid uuid,
|
||||||
|
+ const std::string& user)
|
||||||
|
+ : subscription(std::move(sub))
|
||||||
|
+ , dataFormat(format)
|
||||||
|
+ , uuid(uuid)
|
||||||
|
+ , user(user)
|
||||||
|
+{
|
||||||
|
+ spdlog::debug("{}: created", fmt::streamed(*this));
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+DynamicSubscriptions::SubscriptionData::~SubscriptionData()
|
||||||
|
+{
|
||||||
|
+ try {
|
||||||
|
+ subscription.terminate();
|
||||||
|
+ } catch (const sysrepo::ErrorWithCode& e) { // Maybe it was already terminated (stop-time).
|
||||||
|
+ spdlog::warn("Failed to terminate {}: {}", fmt::streamed(*this), e.what());
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+std::ostream& operator<<(std::ostream& os, const DynamicSubscriptions::SubscriptionData& sub)
|
||||||
|
+{
|
||||||
|
+ return os << "dynamic subscription (id " << sub.subscription.subscriptionId()
|
||||||
|
+ << ", user " << sub.user
|
||||||
|
+ << ", uuid " << boost::uuids::to_string(sub.uuid)
|
||||||
|
+ << ")";
|
||||||
|
+}
|
||||||
|
+}
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..366d1a1
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.h
|
||||||
|
@@ -0,0 +1,57 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||||
|
+ *
|
||||||
|
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||||
|
+ *
|
||||||
|
+*/
|
||||||
|
+#pragma once
|
||||||
|
+
|
||||||
|
+#include <boost/asio.hpp>
|
||||||
|
+#include <boost/asio/io_context.hpp>
|
||||||
|
+#include <boost/uuid/random_generator.hpp>
|
||||||
|
+#include <map>
|
||||||
|
+#include <memory>
|
||||||
|
+#include <sysrepo-cpp/Subscription.hpp>
|
||||||
|
+
|
||||||
|
+namespace libyang {
|
||||||
|
+enum class DataFormat;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+namespace rousette::restconf {
|
||||||
|
+
|
||||||
|
+/** Dynamic subscriptions manager.
|
||||||
|
+ *
|
||||||
|
+ * Stores all dynamic subscriptions and provides a way to retrieve them by the UUID.
|
||||||
|
+ * */
|
||||||
|
+class DynamicSubscriptions {
|
||||||
|
+public:
|
||||||
|
+ struct SubscriptionData : public std::enable_shared_from_this<SubscriptionData> {
|
||||||
|
+ sysrepo::DynamicSubscription subscription;
|
||||||
|
+ libyang::DataFormat dataFormat; ///< Encoding of the notification stream
|
||||||
|
+ boost::uuids::uuid uuid; ///< UUID is part of the GET URI, it identifies subscriptions for clients
|
||||||
|
+ std::string user; ///< User who initiated the establish-subscription RPC
|
||||||
|
+
|
||||||
|
+ SubscriptionData(
|
||||||
|
+ sysrepo::DynamicSubscription sub,
|
||||||
|
+ libyang::DataFormat format,
|
||||||
|
+ boost::uuids::uuid uuid,
|
||||||
|
+ const std::string& user);
|
||||||
|
+ ~SubscriptionData();
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ DynamicSubscriptions(const std::string& streamRootUri);
|
||||||
|
+ ~DynamicSubscriptions();
|
||||||
|
+ std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||||
|
+ void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||||
|
+
|
||||||
|
+private:
|
||||||
|
+ std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||||
|
+ std::string m_restconfStreamUri;
|
||||||
|
+ std::map<boost::uuids::uuid, std::shared_ptr<SubscriptionData>> m_subscriptions;
|
||||||
|
+ boost::uuids::random_generator m_uuidGenerator;
|
||||||
|
+
|
||||||
|
+ void terminateSubscription(const uint32_t subId);
|
||||||
|
+
|
||||||
|
+ boost::uuids::uuid makeUUID();
|
||||||
|
+};
|
||||||
|
+}
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 7e8db58..7287976 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -438,13 +438,16 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||||
|
return {editNode, replacementNode};
|
||||||
|
}
|
||||||
|
|
||||||
|
-std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||||
|
+std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding, DynamicSubscriptions& dynamicSubscriptions)
|
||||||
|
{
|
||||||
|
struct InternalRPCHandler {
|
||||||
|
std::optional<std::string> validationDataXPath; ///< XPath to data used for RPC input validation
|
||||||
|
std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)> rpcHandler; // The function that processes the RPC
|
||||||
|
};
|
||||||
|
- const std::map<std::string, InternalRPCHandler> handlers;
|
||||||
|
+ const std::map<std::string, InternalRPCHandler> handlers{
|
||||||
|
+ {"/ietf-subscribed-notifications:establish-subscription",
|
||||||
|
+ {"/ietf-subscribed-notifications:filters", [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.establishSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||||
|
+ };
|
||||||
|
|
||||||
|
const auto rpcPath = rpcInput.path();
|
||||||
|
|
||||||
|
@@ -482,7 +485,7 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, liby
|
||||||
|
return *parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
-void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||||
|
+void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout, DynamicSubscriptions& dynamicSubscriptions)
|
||||||
|
{
|
||||||
|
requestCtx->sess.switchDatastore(sysrepo::Datastore::Operational);
|
||||||
|
auto ctx = requestCtx->sess.getContext();
|
||||||
|
@@ -516,7 +519,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||||
|
if (requestCtx->restconfRequest.type == RestconfRequest::Type::Execute) {
|
||||||
|
rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout);
|
||||||
|
} else if (requestCtx->restconfRequest.type == RestconfRequest::Type::ExecuteInternal) {
|
||||||
|
- rpcReply = processInternalRPC(requestCtx->sess, *rpcNode, *requestCtx->dataFormat.request);
|
||||||
|
+ rpcReply = processInternalRPC(requestCtx->sess, *rpcNode, *requestCtx->dataFormat.request, dynamicSubscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rpcReply || rpcReply->immediateChildren().empty()) {
|
||||||
|
@@ -902,10 +905,16 @@ std::vector<std::shared_ptr<boost::asio::io_context>> Server::io_services() cons
|
||||||
|
return server->io_services();
|
||||||
|
}
|
||||||
|
|
||||||
|
-Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout, const std::chrono::seconds keepAlivePingInterval)
|
||||||
|
+Server::Server(
|
||||||
|
+ sysrepo::Connection conn,
|
||||||
|
+ const std::string& address,
|
||||||
|
+ const std::string& port,
|
||||||
|
+ const std::chrono::milliseconds timeout,
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval)
|
||||||
|
: m_monitoringSession(conn.sessionStart(sysrepo::Datastore::Operational))
|
||||||
|
, nacm(conn)
|
||||||
|
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||||
|
+ , m_dynamicSubscriptions(netconfStreamRoot)
|
||||||
|
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||||
|
{
|
||||||
|
server->num_threads(1); // we only use one thread for the server, so we can call join() right away
|
||||||
|
@@ -917,7 +926,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||||
|
{"ietf-netconf", "", {}},
|
||||||
|
{"ietf-yang-library", "2019-01-04", {}},
|
||||||
|
{"ietf-yang-patch", "2017-02-22", {}},
|
||||||
|
- {"ietf-subscribed-notifications", "2019-09-09", {}},
|
||||||
|
+ {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json"}},
|
||||||
|
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||||
|
}) {
|
||||||
|
if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||||
|
@@ -1217,12 +1226,12 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||||
|
case RestconfRequest::Type::ExecuteInternal: {
|
||||||
|
auto requestCtx = std::make_shared<RequestContext>(req, res, dataFormat, sess, restconfRequest);
|
||||||
|
|
||||||
|
- req.on_data([requestCtx, timeout, peer=http::peer_from_request(req)](const uint8_t* data, std::size_t length) {
|
||||||
|
+ req.on_data([this, requestCtx, timeout, peer=http::peer_from_request(req)](const uint8_t* data, std::size_t length) {
|
||||||
|
if (length > 0) {
|
||||||
|
requestCtx->payload.append(reinterpret_cast<const char*>(data), length);
|
||||||
|
} else {
|
||||||
|
spdlog::trace("{}: HTTP payload: {}", peer, requestCtx->payload);
|
||||||
|
- WITH_RESTCONF_EXCEPTIONS(processActionOrRPC, rejectWithError)(requestCtx, timeout);
|
||||||
|
+ WITH_RESTCONF_EXCEPTIONS(processActionOrRPC, rejectWithError)(requestCtx, timeout, m_dynamicSubscriptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||||
|
index 0720c8d..c98f736 100644
|
||||||
|
--- a/src/restconf/Server.h
|
||||||
|
+++ b/src/restconf/Server.h
|
||||||
|
@@ -10,6 +10,7 @@
|
||||||
|
#include <sysrepo-cpp/Subscription.hpp>
|
||||||
|
#include "auth/Nacm.h"
|
||||||
|
#include "http/EventStream.h"
|
||||||
|
+#include "restconf/DynamicSubscriptions.h"
|
||||||
|
|
||||||
|
namespace nghttp2::asio_http2::server {
|
||||||
|
class http2;
|
||||||
|
@@ -43,6 +44,7 @@ private:
|
||||||
|
std::optional<sysrepo::Subscription> m_monitoringOperSub;
|
||||||
|
auth::Nacm nacm;
|
||||||
|
std::unique_ptr<nghttp2::asio_http2::server::http2> server;
|
||||||
|
+ DynamicSubscriptions m_dynamicSubscriptions;
|
||||||
|
std::unique_ptr<sr::OpticalEvents> dwdmEvents;
|
||||||
|
using JsonDiffSignal = boost::signals2::signal<void(const std::string& json)>;
|
||||||
|
JsonDiffSignal opticsChange;
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..3e8e011
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -0,0 +1,253 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||||
|
+ *
|
||||||
|
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||||
|
+ *
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+#include "trompeloeil_doctest.h"
|
||||||
|
+static const auto SERVER_PORT = "10092";
|
||||||
|
+#include <libyang-cpp/Time.hpp>
|
||||||
|
+#include <nghttp2/asio_http2.h>
|
||||||
|
+#include <regex>
|
||||||
|
+#include <spdlog/spdlog.h>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
+#include "restconf/Server.h"
|
||||||
|
+#include "tests/aux-utils.h"
|
||||||
|
+#include "tests/event_watchers.h"
|
||||||
|
+#include "tests/pretty_printers.h"
|
||||||
|
+
|
||||||
|
+#define SEND_NOTIFICATION(DATA) notifSession.sendNotification(*ctx.parseOp(DATA, libyang::DataFormat::JSON, libyang::OperationType::NotificationYang).op, sysrepo::Wait::No);
|
||||||
|
+
|
||||||
|
+constexpr auto uuidV4Regex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}";
|
||||||
|
+
|
||||||
|
+using namespace std::chrono_literals;
|
||||||
|
+using namespace std::string_literals;
|
||||||
|
+
|
||||||
|
+/** @brief Calls establish-subscription rpc, returns the url of the stream associated with the created subscription */
|
||||||
|
+std::string establishSubscription(
|
||||||
|
+ const libyang::Context& ctx,
|
||||||
|
+ const libyang::DataFormat rpcEncoding,
|
||||||
|
+ const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||||
|
+ const std::optional<std::string>& encodingLeafValue)
|
||||||
|
+{
|
||||||
|
+ constexpr auto jsonPrefix = "ietf-subscribed-notifications";
|
||||||
|
+ constexpr auto xmlNamespace = "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications";
|
||||||
|
+
|
||||||
|
+ auto stopTime = libyang::yangTimeFormat(std::chrono::system_clock::now() + 5s, libyang::TimezoneInterpretation::Local);
|
||||||
|
+ std::map<std::string, std::string> requestHeaders;
|
||||||
|
+ ng::header_map expectedHeaders;
|
||||||
|
+
|
||||||
|
+ if (rpcRequestAuthHeader) {
|
||||||
|
+ requestHeaders.insert(*rpcRequestAuthHeader);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ std::optional<libyang::DataNode> envelope;
|
||||||
|
+ auto rpcTree = ctx.newPath("/ietf-subscribed-notifications:establish-subscription");
|
||||||
|
+ rpcTree.newPath("stream", "NETCONF");
|
||||||
|
+ rpcTree.newPath("stop-time", stopTime);
|
||||||
|
+
|
||||||
|
+ if (encodingLeafValue) {
|
||||||
|
+ rpcTree.newPath("encoding", *encodingLeafValue);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ switch (rpcEncoding) {
|
||||||
|
+ case libyang::DataFormat::JSON:
|
||||||
|
+ requestHeaders.insert(CONTENT_TYPE_JSON);
|
||||||
|
+ expectedHeaders = jsonHeaders;
|
||||||
|
+ envelope = ctx.newOpaqueJSON({jsonPrefix, jsonPrefix, "input"}, std::nullopt);
|
||||||
|
+ break;
|
||||||
|
+ case libyang::DataFormat::XML:
|
||||||
|
+ requestHeaders.insert(CONTENT_TYPE_XML);
|
||||||
|
+ expectedHeaders = xmlHeaders;
|
||||||
|
+ envelope = ctx.newOpaqueXML({xmlNamespace, jsonPrefix, "input"}, std::nullopt);
|
||||||
|
+ break;
|
||||||
|
+ default:
|
||||||
|
+ FAIL("Unhandled libyang DataFormat");
|
||||||
|
+ break;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // reconnect everything
|
||||||
|
+ auto data = rpcTree.child();
|
||||||
|
+ data->unlinkWithSiblings();
|
||||||
|
+ envelope->insertChild(*data);
|
||||||
|
+
|
||||||
|
+ auto body = *envelope->printStr(rpcEncoding, libyang::PrintFlags::WithSiblings);
|
||||||
|
+ auto resp = post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", requestHeaders, body);
|
||||||
|
+ REQUIRE(resp.equalStatusCodeAndHeaders(Response{200, expectedHeaders, ""}));
|
||||||
|
+
|
||||||
|
+ auto reply = ctx.newPath("/ietf-subscribed-notifications:establish-subscription");
|
||||||
|
+ REQUIRE(reply.parseOp(resp.data, rpcEncoding, libyang::OperationType::ReplyRestconf).tree);
|
||||||
|
+
|
||||||
|
+ auto urlNode = reply.findPath("ietf-restconf-subscribed-notifications:uri", libyang::InputOutputNodes::Output);
|
||||||
|
+ REQUIRE(urlNode);
|
||||||
|
+
|
||||||
|
+ return urlNode->asTerm().valueStr();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
+{
|
||||||
|
+ trompeloeil::sequence seq1, seq2;
|
||||||
|
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||||
|
+ spdlog::set_level(spdlog::level::trace);
|
||||||
|
+
|
||||||
|
+ auto srConn = sysrepo::Connection{};
|
||||||
|
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
+
|
||||||
|
+ auto nacmGuard = manageNacm(srSess);
|
||||||
|
+ auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||||
|
+ setupRealNacm(srSess);
|
||||||
|
+
|
||||||
|
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||||
|
+
|
||||||
|
+ libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ std::optional<std::string> rpcSubscriptionEncoding;
|
||||||
|
+ std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
+
|
||||||
|
+ SECTION("NACM authorization")
|
||||||
|
+ {
|
||||||
|
+ SECTION("Anonymous access for establish-subscription is disabled")
|
||||||
|
+ {
|
||||||
|
+ /* Remove anonymous user's permission to execute RPCs in ietf-subscribed-notifications.
|
||||||
|
+ * Intentionally a SECTION. We can remove NACM rule for a while in order to test access denied.
|
||||||
|
+ * The rule gets automatically restored for the rest of the tests.
|
||||||
|
+ */
|
||||||
|
+ srSess.switchDatastore(sysrepo::Datastore::Running);
|
||||||
|
+ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']");
|
||||||
|
+ srSess.applyChanges();
|
||||||
|
+
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF" } })###")
|
||||||
|
+ == Response{403, jsonHeaders, R"###({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "access-denied",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:establish-subscription",
|
||||||
|
+ "error-message": "Access denied."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)###"});
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Invalid establish-subscription requests")
|
||||||
|
+ {
|
||||||
|
+ // stop-time in the past
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "stop-time": "1999-09-09T09:09:09Z" } })###")
|
||||||
|
+ == Response{400, jsonHeaders, R"###({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "invalid-attribute",
|
||||||
|
+ "error-message": "Couldn't create notification subscription: SR_ERR_INVAL_ARG\u000A Specified \"stop-time\" is in the past. (SR_ERR_INVAL_ARG)"
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)###"});
|
||||||
|
+
|
||||||
|
+ // invalid stream
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "ajsdhauisds" } })###")
|
||||||
|
+ == Response{400, jsonHeaders, R"###({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "invalid-attribute",
|
||||||
|
+ "error-message": "Couldn't create notification subscription: SR_ERR_NOT_FOUND\u000A Failed to collect modules to subscribe to, invalid stream and/or XPath filter (Item not found). (SR_ERR_NOT_FOUND)"
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)###"});
|
||||||
|
+
|
||||||
|
+ // stream-filter-name is unsupported, but leafref validation triggers first
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "stream-filter-name": "xyz" } })###")
|
||||||
|
+ == Response{400, jsonHeaders, R"###({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "protocol",
|
||||||
|
+ "error-tag": "invalid-value",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:establish-subscription/stream-filter-name",
|
||||||
|
+ "error-message": "Invalid leafref value \"xyz\" - no target instance \"/sn:filters/sn:stream-filter/sn:name\" with the same value."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)###"});
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Valid requests")
|
||||||
|
+ {
|
||||||
|
+ SECTION("XML stream")
|
||||||
|
+ {
|
||||||
|
+ netconfWatcher.setDataFormat(libyang::DataFormat::XML);
|
||||||
|
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
+
|
||||||
|
+ SECTION("Stream encoding inferred from request content-type")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::XML;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Explicitly asked for XML stream encoding")
|
||||||
|
+ {
|
||||||
|
+ rpcSubscriptionEncoding = "encode-xml";
|
||||||
|
+
|
||||||
|
+ SECTION("Request content-type JSON")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Request content-type XML")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::XML;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("JSON stream")
|
||||||
|
+ {
|
||||||
|
+ netconfWatcher.setDataFormat(libyang::DataFormat::JSON);
|
||||||
|
+
|
||||||
|
+ SECTION("NACM: anonymous user cannot read example-notif module")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestAuthHeader = std::nullopt;
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ rpcSubscriptionEncoding = "encode-json";
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Content-type with set encode leaf")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
+
|
||||||
|
+ SECTION("Stream encoding inferred from request content-type")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Explicitly asked for JSON stream encoding")
|
||||||
|
+ {
|
||||||
|
+ rpcSubscriptionEncoding = "encode-json";
|
||||||
|
+ SECTION("Request content-type JSON")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Request content-type XML")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::XML;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto uri = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
From e2c8036130a2265476579ffa9f4354741b54bbd5 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Mon, 13 Oct 2025 16:10:51 +0200
|
||||||
|
Subject: [PATCH 27/38] refactor: restconf stream request types
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
In commit 97ceef1 ("restconf: refactor uri parser for stream URIs") we
|
||||||
|
prepared the URI parser for the new subscribed stream notifications.
|
||||||
|
What I did not think through was that the subscribed notifications do
|
||||||
|
not have query parameters. However, I refactored the types in a way
|
||||||
|
that the query parameters member is now in the common base type.
|
||||||
|
|
||||||
|
This patch fixes that by editing the types in a way that only the
|
||||||
|
stream notifications will have the query parameters member. The
|
||||||
|
type for subscribed notifications (which will come in a followup patch)
|
||||||
|
will not have it.
|
||||||
|
|
||||||
|
See-also: 97ceef119c900c37bbaa27860c3b43cfa6d69f95 ("restconf: refactor uri parser for stream URIs")
|
||||||
|
Change-Id: Idc1e05f7ff05cea84b5806f60e217605f75e7a9f
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/Server.cpp | 2 +-
|
||||||
|
src/restconf/uri.cpp | 15 ++++++++-------
|
||||||
|
src/restconf/uri.h | 14 +++++++-------
|
||||||
|
src/restconf/uri_impl.h | 2 +-
|
||||||
|
tests/uri-parser.cpp | 12 ++++++------
|
||||||
|
5 files changed, 23 insertions(+), 22 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 7287976..616d7d4 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -1020,7 +1020,7 @@ Server::Server(
|
||||||
|
shutdownRequested,
|
||||||
|
keepAlivePingInterval,
|
||||||
|
sess,
|
||||||
|
- streamRequest.type.encoding,
|
||||||
|
+ streamRequest.encoding,
|
||||||
|
xpathFilter,
|
||||||
|
startTime,
|
||||||
|
stopTime);
|
||||||
|
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||||
|
index 956c155..6ee011c 100644
|
||||||
|
--- a/src/restconf/uri.cpp
|
||||||
|
+++ b/src/restconf/uri.cpp
|
||||||
|
@@ -58,11 +58,11 @@ const auto resources = x3::rule<class resources, URIPath>{"resources"} =
|
||||||
|
const auto uriGrammar = x3::rule<class grammar, URIPath>{"grammar"} = x3::lit("/restconf") >> resources;
|
||||||
|
|
||||||
|
|
||||||
|
-const auto netconfStream = x3::rule<class netconfStream, RestconfStreamRequest::NetconfStream>{"netconfStream"} =
|
||||||
|
+const auto netconfStream = x3::rule<class netconfStream, NetconfStreamRequest>{"netconfStream"} =
|
||||||
|
x3::lit("/NETCONF") >>
|
||||||
|
((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) |
|
||||||
|
(x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON)));
|
||||||
|
-const auto streamUriGrammar = x3::rule<class grammar, RestconfStreamRequest::NetconfStream>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||||
|
+const auto streamUriGrammar = x3::rule<class grammar, NetconfStreamRequest>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
@@ -204,9 +204,9 @@ std::optional<queryParams::QueryParams> parseQueryParams(const std::string& quer
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
-std::optional<RestconfStreamRequest::NetconfStream> parseStreamUri(const std::string& uriPath)
|
||||||
|
+std::optional<RestconfStreamRequest> parseStreamUri(const std::string& uriPath)
|
||||||
|
{
|
||||||
|
- std::optional<RestconfStreamRequest::NetconfStream> ret;
|
||||||
|
+ std::optional<RestconfStreamRequest> ret;
|
||||||
|
|
||||||
|
if (!x3::parse(std::begin(uriPath), std::end(uriPath), streamUriGrammar >> x3::eoi, ret)) {
|
||||||
|
return std::nullopt;
|
||||||
|
@@ -668,8 +668,8 @@ std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> asYangMod
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
-RestconfStreamRequest::NetconfStream::NetconfStream() = default;
|
||||||
|
-RestconfStreamRequest::NetconfStream::NetconfStream(const libyang::DataFormat& encoding)
|
||||||
|
+NetconfStreamRequest::NetconfStreamRequest() = default;
|
||||||
|
+NetconfStreamRequest::NetconfStreamRequest(const libyang::DataFormat& encoding)
|
||||||
|
: encoding(encoding)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
@@ -692,7 +692,8 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
|
||||||
|
|
||||||
|
validateQueryParametersForStream(*queryParameters);
|
||||||
|
|
||||||
|
- return {*type, *queryParameters};
|
||||||
|
+ type->queryParams = *queryParameters;
|
||||||
|
+ return *type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Returns a set of allowed HTTP methods for given URI. Usable for the 'allow' header */
|
||||||
|
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||||
|
index d9dd29f..f6c63a7 100644
|
||||||
|
--- a/src/restconf/uri.h
|
||||||
|
+++ b/src/restconf/uri.h
|
||||||
|
@@ -197,16 +197,16 @@ struct RestconfRequest {
|
||||||
|
RestconfRequest(Type type, const boost::optional<ApiIdentifier>& datastore, const std::string& path, const queryParams::QueryParams& queryParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
-struct RestconfStreamRequest {
|
||||||
|
- struct NetconfStream {
|
||||||
|
- libyang::DataFormat encoding;
|
||||||
|
-
|
||||||
|
- NetconfStream();
|
||||||
|
- NetconfStream(const libyang::DataFormat& encoding);
|
||||||
|
- } type;
|
||||||
|
+struct NetconfStreamRequest {
|
||||||
|
+ libyang::DataFormat encoding;
|
||||||
|
queryParams::QueryParams queryParams;
|
||||||
|
+
|
||||||
|
+ NetconfStreamRequest();
|
||||||
|
+ NetconfStreamRequest(const libyang::DataFormat& encoding);
|
||||||
|
};
|
||||||
|
|
||||||
|
+using RestconfStreamRequest = NetconfStreamRequest;
|
||||||
|
+
|
||||||
|
RestconfRequest asRestconfRequest(const libyang::Context& ctx, const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString = "");
|
||||||
|
std::optional<libyang::SchemaNode> asLibyangSchemaNode(const libyang::Context& ctx, const std::vector<PathSegment>& pathSegments);
|
||||||
|
std::pair<std::string, PathSegment> asLibyangPathSplit(const libyang::Context& ctx, const std::string& uriPath);
|
||||||
|
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||||
|
index 00b22f6..bc36cca 100644
|
||||||
|
--- a/src/restconf/uri_impl.h
|
||||||
|
+++ b/src/restconf/uri_impl.h
|
||||||
|
@@ -66,7 +66,7 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::YangModule, name, revision);
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
|
||||||
|
|
||||||
|
-BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::RestconfStreamRequest::NetconfStream, encoding);
|
||||||
|
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::NetconfStreamRequest, encoding);
|
||||||
|
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
|
||||||
|
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||||
|
index 23c2b79..38ca80f 100644
|
||||||
|
--- a/tests/uri-parser.cpp
|
||||||
|
+++ b/tests/uri-parser.cpp
|
||||||
|
@@ -1082,15 +1082,15 @@ TEST_CASE("URI path parser")
|
||||||
|
using rousette::restconf::RestconfStreamRequest;
|
||||||
|
|
||||||
|
{
|
||||||
|
- auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||||
|
- REQUIRE(type.encoding == libyang::DataFormat::XML);
|
||||||
|
- REQUIRE(queryParams.empty());
|
||||||
|
+ auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||||
|
+ REQUIRE(req.encoding == libyang::DataFormat::XML);
|
||||||
|
+ REQUIRE(req.queryParams.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
- auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||||
|
- REQUIRE(type.encoding == libyang::DataFormat::JSON);
|
||||||
|
- REQUIRE(queryParams.empty());
|
||||||
|
+ auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||||
|
+ REQUIRE(req.encoding == libyang::DataFormat::JSON);
|
||||||
|
+ REQUIRE(req.queryParams.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF", ""),
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,893 @@
|
|||||||
|
From b3a55ae0a1ef9e092b0c8f0d7c3dbd7baa708cc8 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 29 Jul 2025 11:39:21 +0200
|
||||||
|
Subject: [PATCH 28/38] restconf: endpoint for subscribed notifications
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
This patch creates a new endpoint for the subscribed notification
|
||||||
|
streams in the RESTCONF server. The previous commit added the
|
||||||
|
establish-subscription RPC [1], which returned a stream URI, that was
|
||||||
|
not accessible in that patch. The patch was extra large even without
|
||||||
|
that functionality so I have split it into two commits.
|
||||||
|
|
||||||
|
There is one more catch worth mentioning both here and in the comments:
|
||||||
|
In case sysrepo sends events fast enough it is possible that the
|
||||||
|
function responsible for reading them blocks the event loop and the
|
||||||
|
function responsible for sending the actual data to the client is not
|
||||||
|
called. To mitigate that, I added limit to the number of events read
|
||||||
|
in one go.
|
||||||
|
However, we are not sure if that is enough. Is it possible that the
|
||||||
|
reading function might get called several times in a row? My knowledge
|
||||||
|
of boost::asio is not enough.
|
||||||
|
|
||||||
|
[1] https://datatracker.ietf.org/doc/rfc8639/
|
||||||
|
|
||||||
|
Change-Id: I07dcdd3fb9ef4f05f93ae6dda0e0d71094a0bbfc
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 1 +
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 127 ++++++++++++-
|
||||||
|
src/restconf/DynamicSubscriptions.h | 46 +++++
|
||||||
|
src/restconf/Server.cpp | 51 +++--
|
||||||
|
src/restconf/uri.cpp | 46 ++++-
|
||||||
|
src/restconf/uri.h | 10 +-
|
||||||
|
src/restconf/uri_impl.h | 1 +
|
||||||
|
src/restconf/utils/io.cpp | 30 +++
|
||||||
|
src/restconf/utils/io.h | 12 ++
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 199 ++++++++++++++++++++
|
||||||
|
tests/uri-parser.cpp | 41 +++-
|
||||||
|
11 files changed, 527 insertions(+), 37 deletions(-)
|
||||||
|
create mode 100644 src/restconf/utils/io.cpp
|
||||||
|
create mode 100644 src/restconf/utils/io.h
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 18af5ba..5d3b7ce 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -116,6 +116,7 @@ add_library(rousette-restconf STATIC
|
||||||
|
src/restconf/YangSchemaLocations.cpp
|
||||||
|
src/restconf/uri.cpp
|
||||||
|
src/restconf/utils/dataformat.cpp
|
||||||
|
+ src/restconf/utils/io.cpp
|
||||||
|
src/restconf/utils/sysrepo.cpp
|
||||||
|
src/restconf/utils/yang.cpp
|
||||||
|
)
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
index 7b13990..7f4b171 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -12,6 +12,8 @@
|
||||||
|
#include <sysrepo-cpp/utils/exception.hpp>
|
||||||
|
#include "restconf/DynamicSubscriptions.h"
|
||||||
|
#include "restconf/Exceptions.h"
|
||||||
|
+#include "restconf/utils/io.h"
|
||||||
|
+#include "restconf/utils/yang.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
@@ -119,7 +121,7 @@ void DynamicSubscriptions::terminateSubscription(const uint32_t subId)
|
||||||
|
|
||||||
|
const auto& [uuid, subscriptionData] = *it;
|
||||||
|
spdlog::debug("{}: termination requested", fmt::streamed(*subscriptionData));
|
||||||
|
- subscriptionData->subscription.terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||||
|
+ subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||||
|
m_subscriptions.erase(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -154,14 +156,54 @@ DynamicSubscriptions::SubscriptionData::SubscriptionData(
|
||||||
|
, dataFormat(format)
|
||||||
|
, uuid(uuid)
|
||||||
|
, user(user)
|
||||||
|
+ , state(State::Start)
|
||||||
|
{
|
||||||
|
spdlog::debug("{}: created", fmt::streamed(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicSubscriptions::SubscriptionData::~SubscriptionData()
|
||||||
|
{
|
||||||
|
+ terminate();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::clientDisconnected()
|
||||||
|
+{
|
||||||
|
+ spdlog::debug("{}: client disconnected", fmt::streamed(*this));
|
||||||
|
+
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+ if (state == State::Terminating) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ state = State::Start;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::clientConnected()
|
||||||
|
+{
|
||||||
|
+ spdlog::debug("{}: client connected", fmt::streamed(*this));
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+ state = State::ReceiverActive;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+bool DynamicSubscriptions::SubscriptionData::isReadyToAcceptClient() const
|
||||||
|
+{
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+ return state == State::Start;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::string>& reason)
|
||||||
|
+{
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+
|
||||||
|
+ // already terminating, do nothing
|
||||||
|
+ if (state == State::Terminating) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ state = State::Terminating;
|
||||||
|
+ spdlog::debug("{}: terminating subscription ({})", fmt::streamed(*this), reason.value_or("<no reason>"));
|
||||||
|
try {
|
||||||
|
- subscription.terminate();
|
||||||
|
+ subscription.terminate(reason);
|
||||||
|
} catch (const sysrepo::ErrorWithCode& e) { // Maybe it was already terminated (stop-time).
|
||||||
|
spdlog::warn("Failed to terminate {}: {}", fmt::streamed(*this), e.what());
|
||||||
|
}
|
||||||
|
@@ -174,4 +216,85 @@ std::ostream& operator<<(std::ostream& os, const DynamicSubscriptions::Subscript
|
||||||
|
<< ", uuid " << boost::uuids::to_string(sub.uuid)
|
||||||
|
<< ")";
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+DynamicSubscriptionHttpStream::DynamicSubscriptionHttpStream(
|
||||||
|
+ const nghttp2::asio_http2::server::request& req,
|
||||||
|
+ const nghttp2::asio_http2::server::response& res,
|
||||||
|
+ rousette::http::EventStream::Termination& termination,
|
||||||
|
+ std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval,
|
||||||
|
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData)
|
||||||
|
+ : EventStream(
|
||||||
|
+ req,
|
||||||
|
+ res,
|
||||||
|
+ termination,
|
||||||
|
+ *signal,
|
||||||
|
+ keepAlivePingInterval,
|
||||||
|
+ std::nullopt /* no initial event */,
|
||||||
|
+ [this]() { m_subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription"); },
|
||||||
|
+ [this]() { m_subscriptionData->clientDisconnected(); })
|
||||||
|
+ , m_subscriptionData(subscriptionData)
|
||||||
|
+ , m_signal(signal)
|
||||||
|
+ , m_stream(res.io_service(), m_subscriptionData->subscription.fd())
|
||||||
|
+{
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+DynamicSubscriptionHttpStream::~DynamicSubscriptionHttpStream()
|
||||||
|
+{
|
||||||
|
+ // The stream does not own the file descriptor, sysrepo does. It will be closed when the subscription terminates.
|
||||||
|
+ m_stream.release();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/** @brief Waits for the next notifications and process them */
|
||||||
|
+void DynamicSubscriptionHttpStream::awaitNextNotification()
|
||||||
|
+{
|
||||||
|
+ constexpr auto MAX_EVENTS = 50;
|
||||||
|
+
|
||||||
|
+ m_stream.async_wait(boost::asio::posix::stream_descriptor::wait_read, [this](const boost::system::error_code& err) {
|
||||||
|
+ // Unfortunately wait_read does not return operation_aborted when the file descriptor is closed and poll results in POLLHUP
|
||||||
|
+ if (err == boost::asio::error::operation_aborted || utils::pipeIsClosedAndNoData(m_subscriptionData->subscription.fd())) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ size_t eventsProcessed = 0;
|
||||||
|
+ /* Process all the available notifications, but at most N
|
||||||
|
+ * In case sysrepo is providing the events fast enough, this loop would still run inside the event loop
|
||||||
|
+ * and the event responsible for sending the data to the client would not get to be processed.
|
||||||
|
+ * TODO: Is this enough? What if this async_wait keeps getting called and nothing gets sent?
|
||||||
|
+ */
|
||||||
|
+ while (++eventsProcessed < MAX_EVENTS && utils::pipeHasData(m_subscriptionData->subscription.fd())) {
|
||||||
|
+ std::lock_guard lock(m_subscriptionData->mutex); // sysrepo-cpp's processEvent and terminate is not thread safe
|
||||||
|
+ m_subscriptionData->subscription.processEvent([&](const std::optional<libyang::DataNode>& notificationTree, const sysrepo::NotificationTimeStamp& time) {
|
||||||
|
+ (*m_signal)(rousette::restconf::as_restconf_notification(
|
||||||
|
+ m_subscriptionData->subscription.getSession().getContext(),
|
||||||
|
+ m_subscriptionData->dataFormat,
|
||||||
|
+ *notificationTree,
|
||||||
|
+ time));
|
||||||
|
+ });
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // and wait for more
|
||||||
|
+ awaitNextNotification();
|
||||||
|
+ });
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void DynamicSubscriptionHttpStream::activate()
|
||||||
|
+{
|
||||||
|
+ m_subscriptionData->clientConnected();
|
||||||
|
+ EventStream::activate();
|
||||||
|
+ awaitNextNotification();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+std::shared_ptr<DynamicSubscriptionHttpStream> DynamicSubscriptionHttpStream::create(
|
||||||
|
+ const nghttp2::asio_http2::server::request& req,
|
||||||
|
+ const nghttp2::asio_http2::server::response& res,
|
||||||
|
+ rousette::http::EventStream::Termination& termination,
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval,
|
||||||
|
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData)
|
||||||
|
+{
|
||||||
|
+ auto signal = std::make_shared<rousette::http::EventStream::EventSignal>();
|
||||||
|
+ auto stream = std::shared_ptr<DynamicSubscriptionHttpStream>(new DynamicSubscriptionHttpStream(req, res, termination, signal, keepAlivePingInterval, subscriptionData));
|
||||||
|
+ stream->activate();
|
||||||
|
+ return stream;
|
||||||
|
+}
|
||||||
|
}
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||||
|
index 366d1a1..1874390 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.h
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.h
|
||||||
|
@@ -12,6 +12,7 @@
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <sysrepo-cpp/Subscription.hpp>
|
||||||
|
+#include "http/EventStream.h"
|
||||||
|
|
||||||
|
namespace libyang {
|
||||||
|
enum class DataFormat;
|
||||||
|
@@ -26,17 +27,28 @@ namespace rousette::restconf {
|
||||||
|
class DynamicSubscriptions {
|
||||||
|
public:
|
||||||
|
struct SubscriptionData : public std::enable_shared_from_this<SubscriptionData> {
|
||||||
|
+ mutable std::mutex mutex;
|
||||||
|
sysrepo::DynamicSubscription subscription;
|
||||||
|
libyang::DataFormat dataFormat; ///< Encoding of the notification stream
|
||||||
|
boost::uuids::uuid uuid; ///< UUID is part of the GET URI, it identifies subscriptions for clients
|
||||||
|
std::string user; ///< User who initiated the establish-subscription RPC
|
||||||
|
|
||||||
|
+ enum class State {
|
||||||
|
+ Start, ///< Subscription is ready to be consumed by a client
|
||||||
|
+ ReceiverActive, ///< Subscription is being consumed by a client
|
||||||
|
+ Terminating, ///< Subscription is being terminated by the server shutdown
|
||||||
|
+ } state;
|
||||||
|
+
|
||||||
|
SubscriptionData(
|
||||||
|
sysrepo::DynamicSubscription sub,
|
||||||
|
libyang::DataFormat format,
|
||||||
|
boost::uuids::uuid uuid,
|
||||||
|
const std::string& user);
|
||||||
|
~SubscriptionData();
|
||||||
|
+ void clientDisconnected();
|
||||||
|
+ void clientConnected();
|
||||||
|
+ void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||||
|
+ bool isReadyToAcceptClient() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DynamicSubscriptions(const std::string& streamRootUri);
|
||||||
|
@@ -54,4 +66,38 @@ private:
|
||||||
|
|
||||||
|
boost::uuids::uuid makeUUID();
|
||||||
|
};
|
||||||
|
+
|
||||||
|
+/** @brief Subscribes to sysrepo's subscribed notification and sends the notifications via HTTP/2 Event stream.
|
||||||
|
+ *
|
||||||
|
+ * @see rousette::http::EventStream
|
||||||
|
+ * @see rousette::http::NotificationStream
|
||||||
|
+ * */
|
||||||
|
+class DynamicSubscriptionHttpStream : public http::EventStream {
|
||||||
|
+public:
|
||||||
|
+ ~DynamicSubscriptionHttpStream();
|
||||||
|
+
|
||||||
|
+ static std::shared_ptr<DynamicSubscriptionHttpStream> create(
|
||||||
|
+ const nghttp2::asio_http2::server::request& req,
|
||||||
|
+ const nghttp2::asio_http2::server::response& res,
|
||||||
|
+ rousette::http::EventStream::Termination& termination,
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval,
|
||||||
|
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData);
|
||||||
|
+
|
||||||
|
+private:
|
||||||
|
+ std::shared_ptr<DynamicSubscriptions::SubscriptionData> m_subscriptionData;
|
||||||
|
+ std::shared_ptr<rousette::http::EventStream::EventSignal> m_signal;
|
||||||
|
+ boost::asio::posix::stream_descriptor m_stream;
|
||||||
|
+
|
||||||
|
+ void awaitNextNotification();
|
||||||
|
+
|
||||||
|
+protected:
|
||||||
|
+ DynamicSubscriptionHttpStream(
|
||||||
|
+ const nghttp2::asio_http2::server::request& req,
|
||||||
|
+ const nghttp2::asio_http2::server::response& res,
|
||||||
|
+ rousette::http::EventStream::Termination& termination,
|
||||||
|
+ std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval,
|
||||||
|
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData);
|
||||||
|
+ void activate();
|
||||||
|
+};
|
||||||
|
}
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 616d7d4..2a8ad33 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -1003,27 +1003,40 @@ Server::Server(
|
||||||
|
|
||||||
|
auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
|
||||||
|
|
||||||
|
- if (auto it = streamRequest.queryParams.find("filter"); it != streamRequest.queryParams.end()) {
|
||||||
|
- xpathFilter = std::get<std::string>(it->second);
|
||||||
|
- }
|
||||||
|
+ if (auto *request = std::get_if<SubscribedStreamRequest>(&streamRequest)) {
|
||||||
|
+ if (auto sub = m_dynamicSubscriptions.getSubscriptionForUser(request->uuid, sess.getNacmUser())) {
|
||||||
|
+ if (!sub->isReadyToAcceptClient()) {
|
||||||
|
+ throw ErrorResponse(409, "application", "resource-denied", "There is already another GET request on this subscription.");
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- if (auto it = streamRequest.queryParams.find("start-time"); it != streamRequest.queryParams.end()) {
|
||||||
|
- startTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||||
|
- }
|
||||||
|
- if (auto it = streamRequest.queryParams.find("stop-time"); it != streamRequest.queryParams.end()) {
|
||||||
|
- stopTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||||
|
- }
|
||||||
|
+ DynamicSubscriptionHttpStream::create(req, res, shutdownRequested, keepAlivePingInterval, sub);
|
||||||
|
+ } else {
|
||||||
|
+ throw ErrorResponse(404, "application", "invalid-value", "Subscription not found.");
|
||||||
|
+ }
|
||||||
|
+ } else if (auto *request = std::get_if<NetconfStreamRequest>(&streamRequest)) {
|
||||||
|
+ if (auto it = request->queryParams.find("filter"); it != request->queryParams.end()) {
|
||||||
|
+ xpathFilter = std::get<std::string>(it->second);
|
||||||
|
+ }
|
||||||
|
+ if (auto it = request->queryParams.find("start-time"); it != request->queryParams.end()) {
|
||||||
|
+ startTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||||
|
+ }
|
||||||
|
+ if (auto it = request->queryParams.find("stop-time"); it != request->queryParams.end()) {
|
||||||
|
+ stopTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- NotificationStream::create(
|
||||||
|
- req,
|
||||||
|
- res,
|
||||||
|
- shutdownRequested,
|
||||||
|
- keepAlivePingInterval,
|
||||||
|
- sess,
|
||||||
|
- streamRequest.encoding,
|
||||||
|
- xpathFilter,
|
||||||
|
- startTime,
|
||||||
|
- stopTime);
|
||||||
|
+ NotificationStream::create(
|
||||||
|
+ req,
|
||||||
|
+ res,
|
||||||
|
+ shutdownRequested,
|
||||||
|
+ keepAlivePingInterval,
|
||||||
|
+ sess,
|
||||||
|
+ request->encoding,
|
||||||
|
+ xpathFilter,
|
||||||
|
+ startTime,
|
||||||
|
+ stopTime);
|
||||||
|
+ } else {
|
||||||
|
+ throw ErrorResponse(500, "protocol", "invalid-value", "Invalid request."); // should not happen
|
||||||
|
+ }
|
||||||
|
} catch (const auth::Error& e) {
|
||||||
|
processAuthError(req, res, e, [&res]() {
|
||||||
|
res.write_head(401, {TEXT_PLAIN, CORS});
|
||||||
|
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||||
|
index 6ee011c..99c3d1f 100644
|
||||||
|
--- a/src/restconf/uri.cpp
|
||||||
|
+++ b/src/restconf/uri.cpp
|
||||||
|
@@ -8,6 +8,8 @@
|
||||||
|
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
|
||||||
|
#include <boost/fusion/include/std_pair.hpp>
|
||||||
|
#include <boost/spirit/home/x3/support/ast/variant.hpp>
|
||||||
|
+#include <boost/uuid/nil_generator.hpp>
|
||||||
|
+#include <boost/uuid/string_generator.hpp>
|
||||||
|
#include <experimental/iterator>
|
||||||
|
#include <libyang-cpp/Enum.hpp>
|
||||||
|
#include <map>
|
||||||
|
@@ -57,12 +59,30 @@ const auto resources = x3::rule<class resources, URIPath>{"resources"} =
|
||||||
|
((x3::lit("/") | x3::eps) /* /restconf/ and /restconf */ >> x3::attr(URIPrefix{URIPrefix::Type::RestconfRoot}) >> x3::attr(std::vector<PathSegment>{}));
|
||||||
|
const auto uriGrammar = x3::rule<class grammar, URIPath>{"grammar"} = x3::lit("/restconf") >> resources;
|
||||||
|
|
||||||
|
+const auto string_to_uuid = [](auto& ctx) {
|
||||||
|
+ try {
|
||||||
|
+ _val(ctx) = boost::uuids::string_generator()(_attr(ctx));
|
||||||
|
+ _pass(ctx) = true;
|
||||||
|
+ } catch (const std::runtime_error&) {
|
||||||
|
+ _pass(ctx) = false;
|
||||||
|
+ }
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+const auto uuid_impl = x3::rule<class uuid_impl, std::string>{"uuid_impl"} =
|
||||||
|
+ x3::repeat(8)[x3::xdigit] >> x3::char_('-') >>
|
||||||
|
+ x3::repeat(4)[x3::xdigit] >> x3::char_('-') >>
|
||||||
|
+ x3::repeat(4)[x3::xdigit] >> x3::char_('-') >>
|
||||||
|
+ x3::repeat(4)[x3::xdigit] >> x3::char_('-') >>
|
||||||
|
+ x3::repeat(12)[x3::xdigit];
|
||||||
|
+const auto uuid = x3::rule<class uuid, boost::uuids::uuid>{"uuid"} = uuid_impl[string_to_uuid];
|
||||||
|
+const auto subscribedStream = x3::rule<class subscribedStream, SubscribedStreamRequest>{"subscribedStream"} = x3::lit("/subscribed") >> "/" >> uuid;
|
||||||
|
|
||||||
|
const auto netconfStream = x3::rule<class netconfStream, NetconfStreamRequest>{"netconfStream"} =
|
||||||
|
x3::lit("/NETCONF") >>
|
||||||
|
((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) |
|
||||||
|
(x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON)));
|
||||||
|
-const auto streamUriGrammar = x3::rule<class grammar, NetconfStreamRequest>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||||
|
+const auto streamUriGrammar = x3::rule<class grammar, std::variant<NetconfStreamRequest, SubscribedStreamRequest>>{"streamsGrammar"} =
|
||||||
|
+ x3::lit("/streams") >> (netconfStream | subscribedStream);
|
||||||
|
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
@@ -674,6 +694,16 @@ NetconfStreamRequest::NetconfStreamRequest(const libyang::DataFormat& encoding)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
+SubscribedStreamRequest::SubscribedStreamRequest()
|
||||||
|
+ : uuid(boost::uuids::nil_uuid())
|
||||||
|
+{
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+SubscribedStreamRequest::SubscribedStreamRequest(const boost::uuids::uuid& uuid)
|
||||||
|
+ : uuid(uuid)
|
||||||
|
+{
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
|
||||||
|
{
|
||||||
|
if (httpMethod != "GET" && httpMethod != "HEAD") {
|
||||||
|
@@ -685,14 +715,16 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
|
||||||
|
throw ErrorResponse(404, "application", "invalid-value", "Invalid stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto queryParameters = impl::parseQueryParams(uriQueryString);
|
||||||
|
- if (!queryParameters) {
|
||||||
|
- throw ErrorResponse(400, "protocol", "invalid-value", "Query parameters syntax error");
|
||||||
|
- }
|
||||||
|
+ if (auto* request = std::get_if<NetconfStreamRequest>(&type.value())) {
|
||||||
|
+ auto queryParameters = impl::parseQueryParams(uriQueryString);
|
||||||
|
+ if (!queryParameters) {
|
||||||
|
+ throw ErrorResponse(400, "protocol", "invalid-value", "Query parameters syntax error");
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- validateQueryParametersForStream(*queryParameters);
|
||||||
|
+ validateQueryParametersForStream(*queryParameters);
|
||||||
|
+ request->queryParams = *queryParameters;
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- type->queryParams = *queryParameters;
|
||||||
|
return *type;
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||||
|
index f6c63a7..7893c6f 100644
|
||||||
|
--- a/src/restconf/uri.h
|
||||||
|
+++ b/src/restconf/uri.h
|
||||||
|
@@ -6,6 +6,7 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
+#include <boost/uuid/uuid.hpp>
|
||||||
|
#include <boost/variant.hpp>
|
||||||
|
#include <libyang-cpp/Module.hpp>
|
||||||
|
#include <libyang-cpp/SchemaNode.hpp>
|
||||||
|
@@ -205,7 +206,14 @@ struct NetconfStreamRequest {
|
||||||
|
NetconfStreamRequest(const libyang::DataFormat& encoding);
|
||||||
|
};
|
||||||
|
|
||||||
|
-using RestconfStreamRequest = NetconfStreamRequest;
|
||||||
|
+struct SubscribedStreamRequest {
|
||||||
|
+ boost::uuids::uuid uuid;
|
||||||
|
+
|
||||||
|
+ SubscribedStreamRequest();
|
||||||
|
+ SubscribedStreamRequest(const boost::uuids::uuid& uuid);
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+using RestconfStreamRequest = std::variant<NetconfStreamRequest, SubscribedStreamRequest>;
|
||||||
|
|
||||||
|
RestconfRequest asRestconfRequest(const libyang::Context& ctx, const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString = "");
|
||||||
|
std::optional<libyang::SchemaNode> asLibyangSchemaNode(const libyang::Context& ctx, const std::vector<PathSegment>& pathSegments);
|
||||||
|
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||||
|
index bc36cca..cf57859 100644
|
||||||
|
--- a/src/restconf/uri_impl.h
|
||||||
|
+++ b/src/restconf/uri_impl.h
|
||||||
|
@@ -67,6 +67,7 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
|
||||||
|
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::NetconfStreamRequest, encoding);
|
||||||
|
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::SubscribedStreamRequest, uuid);
|
||||||
|
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
|
||||||
|
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
|
||||||
|
diff --git a/src/restconf/utils/io.cpp b/src/restconf/utils/io.cpp
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..d657096
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/restconf/utils/io.cpp
|
||||||
|
@@ -0,0 +1,30 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||||
|
+ *
|
||||||
|
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+#include <poll.h>
|
||||||
|
+#include "restconf/utils/io.h"
|
||||||
|
+
|
||||||
|
+namespace rousette::restconf::utils {
|
||||||
|
+bool pipeHasData(const int fd)
|
||||||
|
+{
|
||||||
|
+ pollfd fds = {
|
||||||
|
+ .fd = fd,
|
||||||
|
+ .events = POLLIN | POLLHUP,
|
||||||
|
+ .revents = 0};
|
||||||
|
+
|
||||||
|
+ return poll(&fds, 1, 0) == 1 && fds.revents & POLLIN;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+bool pipeIsClosedAndNoData(const int fd)
|
||||||
|
+{
|
||||||
|
+ pollfd fds = {
|
||||||
|
+ .fd = fd,
|
||||||
|
+ .events = POLLIN | POLLHUP,
|
||||||
|
+ .revents = 0};
|
||||||
|
+
|
||||||
|
+ return poll(&fds, 1, 0) == 1 && fds.revents & POLLHUP && !(fds.revents & POLLIN);
|
||||||
|
+}
|
||||||
|
+}
|
||||||
|
diff --git a/src/restconf/utils/io.h b/src/restconf/utils/io.h
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..ac9f2f8
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/src/restconf/utils/io.h
|
||||||
|
@@ -0,0 +1,12 @@
|
||||||
|
+/*
|
||||||
|
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||||
|
+ *
|
||||||
|
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||||
|
+ */
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+namespace rousette::restconf::utils {
|
||||||
|
+
|
||||||
|
+bool pipeHasData(const int fd);
|
||||||
|
+bool pipeIsClosedAndNoData(const int fd);
|
||||||
|
+}
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
index 3e8e011..11dd992 100644
|
||||||
|
--- a/tests/restconf-subscribed-notifications.cpp
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -91,6 +91,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
auto srConn = sysrepo::Connection{};
|
||||||
|
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
@@ -99,6 +100,19 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||||
|
setupRealNacm(srSess);
|
||||||
|
|
||||||
|
+ // parent for nested notification
|
||||||
|
+ srSess.switchDatastore(sysrepo::Datastore::Operational);
|
||||||
|
+ srSess.setItem("/example:tlc/list[name='k1']/choice1", "something must me here");
|
||||||
|
+ srSess.applyChanges();
|
||||||
|
+ std::vector<std::string> notificationsJSON{
|
||||||
|
+ R"({"example:eventA":{"message":"blabla","progress":11}})",
|
||||||
|
+ R"({"example:eventB":{}})",
|
||||||
|
+ R"({"example-notif:something-happened":{}})",
|
||||||
|
+ R"({"example:eventA":{"message":"almost finished","progress":99}})",
|
||||||
|
+ R"({"example:tlc":{"list":[{"name":"k1","notif":{"message":"nested"}}]}})",
|
||||||
|
+ };
|
||||||
|
+ std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||||
|
+
|
||||||
|
RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||||
|
|
||||||
|
libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
@@ -132,6 +146,78 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
}
|
||||||
|
)###"});
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ SECTION("User DWDM establishes subscription")
|
||||||
|
+ {
|
||||||
|
+ std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||||
|
+ auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||||
|
+
|
||||||
|
+ std::map<std::string, std::string> headers;
|
||||||
|
+
|
||||||
|
+ SECTION("Users who cannot GET")
|
||||||
|
+ {
|
||||||
|
+ SECTION("anonymous") { }
|
||||||
|
+ SECTION("norules") { headers.insert(AUTH_NORULES); }
|
||||||
|
+ REQUIRE(get(uri, headers) == Response{404, plaintextHeaders, "Subscription not found."});
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Users who can GET")
|
||||||
|
+ {
|
||||||
|
+ SECTION("root") { headers.insert(AUTH_ROOT); }
|
||||||
|
+ SECTION("dwdm") { headers.insert(AUTH_DWDM); }
|
||||||
|
+ REQUIRE(head(uri, headers) == Response{200, eventStreamHeaders, ""});
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("GET on the same subscription concurently")
|
||||||
|
+ {
|
||||||
|
+ std::map<std::string, std::string> headers;
|
||||||
|
+ std::string response;
|
||||||
|
+ int status;
|
||||||
|
+
|
||||||
|
+ SECTION("Allowed users")
|
||||||
|
+ {
|
||||||
|
+ response = "There is already another GET request on this subscription.";
|
||||||
|
+ status = 409;
|
||||||
|
+
|
||||||
|
+ SECTION("Same user")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_DWDM);
|
||||||
|
+ }
|
||||||
|
+ SECTION("Different user")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_ROOT);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ SECTION("Disallowed user")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_NORULES);
|
||||||
|
+ response = "Subscription not found.";
|
||||||
|
+ status = 404;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ PREPARE_LOOP_WITH_EXCEPTIONS
|
||||||
|
+ auto thr = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||||
|
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||||
|
+ REQUIRE(get(uri, headers) == Response{status, plaintextHeaders, response});
|
||||||
|
+ }));
|
||||||
|
+
|
||||||
|
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_DWDM});
|
||||||
|
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("GET on the same subscription sequentially")
|
||||||
|
+ {
|
||||||
|
+ boost::asio::io_service io;
|
||||||
|
+ {
|
||||||
|
+ std::binary_semaphore requestSent(0); // SSEClient needs to notify when the request is sent, but we don't care about it here.
|
||||||
|
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_DWDM});
|
||||||
|
+ }
|
||||||
|
+ {
|
||||||
|
+ std::binary_semaphore requestSent(0);
|
||||||
|
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_DWDM});
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Invalid establish-subscription requests")
|
||||||
|
@@ -209,6 +295,12 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
rpcRequestEncoding = libyang::DataFormat::XML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[2], seq2);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("JSON stream")
|
||||||
|
@@ -220,6 +312,11 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
rpcRequestAuthHeader = std::nullopt;
|
||||||
|
rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
rpcSubscriptionEncoding = "encode-json";
|
||||||
|
+
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Content-type with set encode leaf")
|
||||||
|
@@ -244,10 +341,112 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
rpcRequestEncoding = libyang::DataFormat::XML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[2], seq2);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uri = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||||
|
+
|
||||||
|
+ PREPARE_LOOP_WITH_EXCEPTIONS
|
||||||
|
+
|
||||||
|
+ // Here's how these two threads work together.
|
||||||
|
+ //
|
||||||
|
+ // The main test thread (this one):
|
||||||
|
+ // - sets up all the expectations
|
||||||
|
+ // - has an HTTP client which calls/spends the expectations based on the incoming SSE data
|
||||||
|
+ // - blocks while it runs the ASIO event loop
|
||||||
|
+ //
|
||||||
|
+ // The auxiliary thread (the notificationThread):
|
||||||
|
+ // - waits for the HTTP client having issued its long-lived HTTP GET
|
||||||
|
+ // - sends a bunch of notifications to sysrepo
|
||||||
|
+ // - waits for all the expectations getting spent, and then terminates the ASIO event loop cleanly
|
||||||
|
+
|
||||||
|
+ std::jthread notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||||
|
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||||
|
+ auto ctx = notifSession.getContext();
|
||||||
|
+
|
||||||
|
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||||
|
+
|
||||||
|
+ SEND_NOTIFICATION(notificationsJSON[0]);
|
||||||
|
+ SEND_NOTIFICATION(notificationsJSON[1]);
|
||||||
|
+ std::this_thread::sleep_for(500ms); // simulate some delays; server might be slow in creating notifications, client should still remain connected
|
||||||
|
+ SEND_NOTIFICATION(notificationsJSON[2]);
|
||||||
|
+ SEND_NOTIFICATION(notificationsJSON[3]);
|
||||||
|
+ std::this_thread::sleep_for(500ms);
|
||||||
|
+ SEND_NOTIFICATION(notificationsJSON[4]);
|
||||||
|
+
|
||||||
|
+ // once the main thread has processed all the notifications, stop the ASIO loop
|
||||||
|
+ waitForCompletionAndBitMore(seq1);
|
||||||
|
+ waitForCompletionAndBitMore(seq2);
|
||||||
|
+ }));
|
||||||
|
+
|
||||||
|
+ std::map<std::string, std::string> streamHeaders;
|
||||||
|
+ if (rpcRequestAuthHeader) {
|
||||||
|
+ streamHeaders.insert(*rpcRequestAuthHeader);
|
||||||
|
+ }
|
||||||
|
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, streamHeaders);
|
||||||
|
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+TEST_CASE("Terminating server under notification load")
|
||||||
|
+{
|
||||||
|
+ trompeloeil::sequence seq1;
|
||||||
|
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||||
|
+ spdlog::set_level(spdlog::level::trace);
|
||||||
|
+
|
||||||
|
+ auto srConn = sysrepo::Connection{};
|
||||||
|
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
+
|
||||||
|
+ auto nacmGuard = manageNacm(srSess);
|
||||||
|
+ auto server = std::make_unique<rousette::restconf::Server>(srConn, SERVER_ADDRESS, SERVER_PORT);
|
||||||
|
+ setupRealNacm(srSess);
|
||||||
|
+
|
||||||
|
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||||
|
+ constexpr auto notif = R"({"example:eventB":{}})";
|
||||||
|
+
|
||||||
|
+ std::optional<std::string> rpcSubscriptionEncoding;
|
||||||
|
+ std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
+
|
||||||
|
+ std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||||
|
+ auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||||
|
+
|
||||||
|
+ PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||||
|
+
|
||||||
|
+ std::atomic<bool> serverRunning = true;
|
||||||
|
+ std::atomic<size_t> notificationsReceived = 0;
|
||||||
|
+ constexpr size_t NOTIFICATIONS_BEFORE_TERMINATE = 50;
|
||||||
|
+
|
||||||
|
+ auto notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||||
|
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||||
|
+ auto ctx = notifSession.getContext();
|
||||||
|
+
|
||||||
|
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||||
|
+
|
||||||
|
+ while (serverRunning) {
|
||||||
|
+ SEND_NOTIFICATION(notif);
|
||||||
|
+ }
|
||||||
|
+ }));
|
||||||
|
+
|
||||||
|
+ auto serverShutdownThread = std::jthread([&]() {
|
||||||
|
+ while (notificationsReceived <= NOTIFICATIONS_BEFORE_TERMINATE) {
|
||||||
|
+ // A condition variable would be more elegant, but this is just a test...
|
||||||
|
+ std::this_thread::sleep_for(20ms);
|
||||||
|
+ }
|
||||||
|
+ server.reset();
|
||||||
|
+ serverRunning = false;
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ netconfWatcher.setDataFormat(libyang::DataFormat::JSON);
|
||||||
|
+ REQUIRE_CALL(netconfWatcher, data(notif)).IN_SEQUENCE(seq1).TIMES(AT_LEAST(NOTIFICATIONS_BEFORE_TERMINATE)).LR_SIDE_EFFECT(notificationsReceived++);
|
||||||
|
+ REQUIRE_CALL(netconfWatcher, data(R"({ietf-subscribed-notifications:subscription-terminated":{"id":1,"reason":"no-such-subscription"}})")).IN_SEQUENCE(seq1).TIMES(AT_MOST(1));
|
||||||
|
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {auth});
|
||||||
|
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
+}
|
||||||
|
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||||
|
index 38ca80f..2ad244f 100644
|
||||||
|
--- a/tests/uri-parser.cpp
|
||||||
|
+++ b/tests/uri-parser.cpp
|
||||||
|
@@ -7,6 +7,7 @@
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "trompeloeil_doctest.h"
|
||||||
|
+#include <boost/uuid/string_generator.hpp>
|
||||||
|
#include <experimental/iterator>
|
||||||
|
#include <string>
|
||||||
|
#include <sysrepo-cpp/Connection.hpp>
|
||||||
|
@@ -1042,8 +1043,11 @@ TEST_CASE("URI path parser")
|
||||||
|
|
||||||
|
SECTION("filter")
|
||||||
|
{
|
||||||
|
+ using rousette::restconf::NetconfStreamRequest;
|
||||||
|
+
|
||||||
|
auto resp = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "filter=/asd");
|
||||||
|
- REQUIRE(resp.queryParams == QueryParams({{"filter", "/asd"s}}));
|
||||||
|
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(resp));
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(resp).queryParams == QueryParams({{"filter", "/asd"s}}));
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "GET", "/restconf/data/example:ordered-lists", "filter=something"),
|
||||||
|
serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'filter' can be used only with streams").c_str(),
|
||||||
|
@@ -1052,8 +1056,11 @@ TEST_CASE("URI path parser")
|
||||||
|
|
||||||
|
SECTION("start-time")
|
||||||
|
{
|
||||||
|
+ using rousette::restconf::NetconfStreamRequest;
|
||||||
|
+
|
||||||
|
auto resp = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "start-time=2024-01-01T01:01:01Z");
|
||||||
|
- REQUIRE(resp.queryParams == QueryParams({{"start-time", "2024-01-01T01:01:01Z"s}}));
|
||||||
|
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(resp));
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(resp).queryParams == QueryParams({{"start-time", "2024-01-01T01:01:01Z"s}}));
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "GET", "/restconf/data/example:ordered-lists", "start-time=2024-01-01T01:01:01Z"),
|
||||||
|
serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'start-time' can be used only with streams").c_str(),
|
||||||
|
@@ -1062,8 +1069,11 @@ TEST_CASE("URI path parser")
|
||||||
|
|
||||||
|
SECTION("stop-time")
|
||||||
|
{
|
||||||
|
+ using rousette::restconf::NetconfStreamRequest;
|
||||||
|
+
|
||||||
|
auto resp = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "stop-time=2024-01-01T01:01:01Z");
|
||||||
|
- REQUIRE(resp.queryParams == QueryParams({{"stop-time", "2024-01-01T01:01:01Z"s}}));
|
||||||
|
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(resp));
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(resp).queryParams == QueryParams({{"stop-time", "2024-01-01T01:01:01Z"s}}));
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "GET", "/restconf/data/example:ordered-lists", "stop-time=2024-01-01T01:01:01Z"),
|
||||||
|
serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'stop-time' can be used only with streams").c_str(),
|
||||||
|
@@ -1079,18 +1089,27 @@ TEST_CASE("URI path parser")
|
||||||
|
SECTION("Streams")
|
||||||
|
{
|
||||||
|
using rousette::restconf::asRestconfStreamRequest;
|
||||||
|
- using rousette::restconf::RestconfStreamRequest;
|
||||||
|
+ using rousette::restconf::NetconfStreamRequest;
|
||||||
|
+ using rousette::restconf::SubscribedStreamRequest;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||||
|
- REQUIRE(req.encoding == libyang::DataFormat::XML);
|
||||||
|
- REQUIRE(req.queryParams.empty());
|
||||||
|
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(req));
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(req).encoding == libyang::DataFormat::XML);
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(req).queryParams.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||||
|
- REQUIRE(req.encoding == libyang::DataFormat::JSON);
|
||||||
|
- REQUIRE(req.queryParams.empty());
|
||||||
|
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(req));
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(req).encoding == libyang::DataFormat::JSON);
|
||||||
|
+ REQUIRE(std::get<NetconfStreamRequest>(req).queryParams.empty());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ {
|
||||||
|
+ auto req = asRestconfStreamRequest("GET", "/streams/subscribed/a40f0a50-061a-4832-a6ac-c4db7df81a10", "");
|
||||||
|
+ REQUIRE(std::holds_alternative<SubscribedStreamRequest>(req));
|
||||||
|
+ REQUIRE(std::get<SubscribedStreamRequest>(req).uuid == boost::uuids::string_generator()("a40f0a50-061a-4832-a6ac-c4db7df81a10"));
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF", ""),
|
||||||
|
@@ -1105,6 +1124,12 @@ TEST_CASE("URI path parser")
|
||||||
|
REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF/XM", ""),
|
||||||
|
serializeErrorResponse(404, "application", "invalid-value", "Invalid stream").c_str(),
|
||||||
|
rousette::restconf::ErrorResponse);
|
||||||
|
+ REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/subscribed", ""),
|
||||||
|
+ serializeErrorResponse(404, "application", "invalid-value", "Invalid stream").c_str(),
|
||||||
|
+ rousette::restconf::ErrorResponse);
|
||||||
|
+ REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/subscribed/123-456-789", ""),
|
||||||
|
+ serializeErrorResponse(404, "application", "invalid-value", "Invalid stream").c_str(),
|
||||||
|
+ rousette::restconf::ErrorResponse);
|
||||||
|
|
||||||
|
|
||||||
|
for (const auto& httpMethod : {"OPTIONS", "PATCH", "DELETE", "POST", "PUT"}) {
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
From 9c39cdd8a8a6bb8fb94d4f943313ec8c4a1823d7 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Mon, 10 Nov 2025 19:58:36 +0100
|
||||||
|
Subject: [PATCH 29/38] restconf: dynamic subscriptions shutdown method
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Dynamic subscriptions now have a proper shutdown method that sets a
|
||||||
|
Terminating state on all the subscriptions so that each subscription
|
||||||
|
cannot call its terminate() method.
|
||||||
|
|
||||||
|
Change-Id: I4532bbafff7c7386a18ed0636f1102842d2749fa
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 10 ++++++++++
|
||||||
|
src/restconf/DynamicSubscriptions.h | 1 +
|
||||||
|
src/restconf/Server.cpp | 1 +
|
||||||
|
3 files changed, 12 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
index 7f4b171..9c195f4 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -82,6 +82,16 @@ DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri)
|
||||||
|
|
||||||
|
DynamicSubscriptions::~DynamicSubscriptions() = default;
|
||||||
|
|
||||||
|
+void DynamicSubscriptions::stop()
|
||||||
|
+{
|
||||||
|
+ std::lock_guard lock(m_mutex);
|
||||||
|
+ for (const auto& [uuid, subscriptionData] : m_subscriptions) {
|
||||||
|
+ // We are already terminating and will destroy via destructor, calls to terminate() will do nothing
|
||||||
|
+ std::lock_guard lock(subscriptionData->mutex);
|
||||||
|
+ subscriptionData->state = SubscriptionData::State::Terminating;
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput)
|
||||||
|
{
|
||||||
|
// Generate a new UUID associated with the subscription. The UUID will be used as a part of the URI so that the URI is not predictable (RFC 8650, section 5)
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||||
|
index 1874390..0309cad 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.h
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.h
|
||||||
|
@@ -55,6 +55,7 @@ public:
|
||||||
|
~DynamicSubscriptions();
|
||||||
|
std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||||
|
void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||||
|
+ void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 2a8ad33..5486e99 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -884,6 +884,7 @@ void Server::stop()
|
||||||
|
});
|
||||||
|
|
||||||
|
shutdownRequested();
|
||||||
|
+ m_dynamicSubscriptions.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::join()
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
From 3154d9209dc876fee43d9e83907ab44e06008c65 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 14 Oct 2025 12:25:20 +0200
|
||||||
|
Subject: [PATCH 30/38] restconf: terminate subsc. notification streams after
|
||||||
|
60s with no client
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
We allow anonymous users to create notification streams. However, such
|
||||||
|
users will not have permissions to delete the subscriptions which opens
|
||||||
|
a potential for resource exhaustion. To prevent this, we now terminate
|
||||||
|
the notification streams after 60 seconds of inactivity.
|
||||||
|
|
||||||
|
The period is configurable via the Server constructor, but the default
|
||||||
|
is 60 seconds.
|
||||||
|
|
||||||
|
Change-Id: Id399d108069704fa8ab79b9ef6ab855dd62cfc8d
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 72 ++++++++++++++++++---
|
||||||
|
src/restconf/DynamicSubscriptions.h | 22 ++++++-
|
||||||
|
src/restconf/Server.cpp | 5 +-
|
||||||
|
src/restconf/Server.h | 3 +-
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 34 ++++++++++
|
||||||
|
5 files changed, 122 insertions(+), 14 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
index 9c195f4..f6defb4 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -74,9 +74,11 @@ sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, c
|
||||||
|
|
||||||
|
namespace rousette::restconf {
|
||||||
|
|
||||||
|
-DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri)
|
||||||
|
+DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri, const nghttp2::asio_http2::server::http2& server, const std::chrono::seconds inactivityTimeout)
|
||||||
|
: m_restconfStreamUri(streamRootUri)
|
||||||
|
+ , m_server(server)
|
||||||
|
, m_uuidGenerator(boost::uuids::random_generator())
|
||||||
|
+ , m_inactivityTimeout(inactivityTimeout)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -86,9 +88,7 @@ void DynamicSubscriptions::stop()
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
for (const auto& [uuid, subscriptionData] : m_subscriptions) {
|
||||||
|
- // We are already terminating and will destroy via destructor, calls to terminate() will do nothing
|
||||||
|
- std::lock_guard lock(subscriptionData->mutex);
|
||||||
|
- subscriptionData->state = SubscriptionData::State::Terminating;
|
||||||
|
+ subscriptionData->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -110,7 +110,10 @@ void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, cons
|
||||||
|
std::move(sub),
|
||||||
|
dataFormat,
|
||||||
|
uuid,
|
||||||
|
- *session.getNacmUser());
|
||||||
|
+ *session.getNacmUser(),
|
||||||
|
+ *m_server.io_services().at(0),
|
||||||
|
+ m_inactivityTimeout,
|
||||||
|
+ [this, subId = sub.subscriptionId()]() { terminateSubscription(subId); });
|
||||||
|
} catch (const sysrepo::ErrorWithCode& e) {
|
||||||
|
throw ErrorResponse(400, "application", "invalid-attribute", e.what());
|
||||||
|
}
|
||||||
|
@@ -161,18 +164,29 @@ DynamicSubscriptions::SubscriptionData::SubscriptionData(
|
||||||
|
sysrepo::DynamicSubscription sub,
|
||||||
|
libyang::DataFormat format,
|
||||||
|
boost::uuids::uuid uuid,
|
||||||
|
- const std::string& user)
|
||||||
|
+ const std::string& user,
|
||||||
|
+ boost::asio::io_context& io,
|
||||||
|
+ std::chrono::seconds inactivityTimeout,
|
||||||
|
+ std::function<void()> onClientInactiveCallback)
|
||||||
|
: subscription(std::move(sub))
|
||||||
|
, dataFormat(format)
|
||||||
|
, uuid(uuid)
|
||||||
|
, user(user)
|
||||||
|
, state(State::Start)
|
||||||
|
+ , inactivityTimeout(inactivityTimeout)
|
||||||
|
+ , clientInactiveTimer(io)
|
||||||
|
+ , onClientInactiveCallback(std::move(onClientInactiveCallback))
|
||||||
|
{
|
||||||
|
spdlog::debug("{}: created", fmt::streamed(*this));
|
||||||
|
+
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+ inactivityStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicSubscriptions::SubscriptionData::~SubscriptionData()
|
||||||
|
{
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+ inactivityCancel();
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -186,12 +200,14 @@ void DynamicSubscriptions::SubscriptionData::clientDisconnected()
|
||||||
|
}
|
||||||
|
|
||||||
|
state = State::Start;
|
||||||
|
+ inactivityStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicSubscriptions::SubscriptionData::clientConnected()
|
||||||
|
{
|
||||||
|
spdlog::debug("{}: client connected", fmt::streamed(*this));
|
||||||
|
std::lock_guard lock(mutex);
|
||||||
|
+ inactivityCancel();
|
||||||
|
state = State::ReceiverActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -201,10 +217,37 @@ bool DynamicSubscriptions::SubscriptionData::isReadyToAcceptClient() const
|
||||||
|
return state == State::Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
-void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::string>& reason)
|
||||||
|
+/** @pre The mutex must be locked by the caller. */
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::inactivityStart()
|
||||||
|
{
|
||||||
|
- std::lock_guard lock(mutex);
|
||||||
|
+ if (state == State::Terminating) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ spdlog::trace("{}: starting inactivity timer", fmt::streamed(*this));
|
||||||
|
+
|
||||||
|
+ clientInactiveTimer.expires_after(inactivityTimeout);
|
||||||
|
+ clientInactiveTimer.async_wait([weakThis = weak_from_this()](const boost::system::error_code& err) {
|
||||||
|
+ auto self = weakThis.lock();
|
||||||
|
+ if (!self || err == boost::asio::error::operation_aborted) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
|
||||||
|
+ spdlog::trace("{}: client inactive, perform inactivity callback", fmt::streamed(*self));
|
||||||
|
+ self->onClientInactiveCallback();
|
||||||
|
+ });
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/** @pre The mutex must be locked by the caller. */
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::inactivityCancel()
|
||||||
|
+{
|
||||||
|
+ spdlog::trace("{}: cancelling inactivity timer", fmt::streamed(*this));
|
||||||
|
+ clientInactiveTimer.cancel();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/** @pre The mutex must be locked by the caller. */
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::string>& reason)
|
||||||
|
+{
|
||||||
|
// already terminating, do nothing
|
||||||
|
if (state == State::Terminating) {
|
||||||
|
return;
|
||||||
|
@@ -219,6 +262,14 @@ void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+void DynamicSubscriptions::SubscriptionData::stop()
|
||||||
|
+{
|
||||||
|
+ std::lock_guard lock(mutex);
|
||||||
|
+ inactivityCancel();
|
||||||
|
+ // We are already terminating and will destroy via destructor, calls to terminate() will do nothing
|
||||||
|
+ state = SubscriptionData::State::Terminating;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
std::ostream& operator<<(std::ostream& os, const DynamicSubscriptions::SubscriptionData& sub)
|
||||||
|
{
|
||||||
|
return os << "dynamic subscription (id " << sub.subscription.subscriptionId()
|
||||||
|
@@ -241,7 +292,10 @@ DynamicSubscriptionHttpStream::DynamicSubscriptionHttpStream(
|
||||||
|
*signal,
|
||||||
|
keepAlivePingInterval,
|
||||||
|
std::nullopt /* no initial event */,
|
||||||
|
- [this]() { m_subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription"); },
|
||||||
|
+ [this]() {
|
||||||
|
+ std::lock_guard lock(m_subscriptionData->mutex);
|
||||||
|
+ m_subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||||
|
+ },
|
||||||
|
[this]() { m_subscriptionData->clientDisconnected(); })
|
||||||
|
, m_subscriptionData(subscriptionData)
|
||||||
|
, m_signal(signal)
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||||
|
index 0309cad..0422947 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.h
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.h
|
||||||
|
@@ -18,6 +18,10 @@ namespace libyang {
|
||||||
|
enum class DataFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
+namespace nghttp2::asio_http2::server {
|
||||||
|
+class http2;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
namespace rousette::restconf {
|
||||||
|
|
||||||
|
/** Dynamic subscriptions manager.
|
||||||
|
@@ -39,19 +43,31 @@ public:
|
||||||
|
Terminating, ///< Subscription is being terminated by the server shutdown
|
||||||
|
} state;
|
||||||
|
|
||||||
|
+ std::chrono::seconds inactivityTimeout; ///< Time after which the subscription is considered inactive and can be removed if no client is connected
|
||||||
|
+ boost::asio::steady_timer clientInactiveTimer; ///< Timer used for auto-destruction of subscriptions that are unused
|
||||||
|
+ std::function<void()> onClientInactiveCallback;
|
||||||
|
+
|
||||||
|
SubscriptionData(
|
||||||
|
sysrepo::DynamicSubscription sub,
|
||||||
|
libyang::DataFormat format,
|
||||||
|
boost::uuids::uuid uuid,
|
||||||
|
- const std::string& user);
|
||||||
|
+ const std::string& user,
|
||||||
|
+ boost::asio::io_context& io,
|
||||||
|
+ std::chrono::seconds inactivityTimeout,
|
||||||
|
+ std::function<void()> onClientInactiveCallback);
|
||||||
|
~SubscriptionData();
|
||||||
|
void clientDisconnected();
|
||||||
|
void clientConnected();
|
||||||
|
void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||||
|
bool isReadyToAcceptClient() const;
|
||||||
|
+ void stop();
|
||||||
|
+
|
||||||
|
+ private:
|
||||||
|
+ void inactivityStart();
|
||||||
|
+ void inactivityCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
- DynamicSubscriptions(const std::string& streamRootUri);
|
||||||
|
+ DynamicSubscriptions(const std::string& streamRootUri, const nghttp2::asio_http2::server::http2& server, const std::chrono::seconds inactivityTimeout);
|
||||||
|
~DynamicSubscriptions();
|
||||||
|
std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||||
|
void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||||
|
@@ -60,8 +76,10 @@ public:
|
||||||
|
private:
|
||||||
|
std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||||
|
std::string m_restconfStreamUri;
|
||||||
|
+ const nghttp2::asio_http2::server::http2& m_server;
|
||||||
|
std::map<boost::uuids::uuid, std::shared_ptr<SubscriptionData>> m_subscriptions;
|
||||||
|
boost::uuids::random_generator m_uuidGenerator;
|
||||||
|
+ std::chrono::seconds m_inactivityTimeout;
|
||||||
|
|
||||||
|
void terminateSubscription(const uint32_t subId);
|
||||||
|
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 5486e99..0c7e855 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -911,11 +911,12 @@ Server::Server(
|
||||||
|
const std::string& address,
|
||||||
|
const std::string& port,
|
||||||
|
const std::chrono::milliseconds timeout,
|
||||||
|
- const std::chrono::seconds keepAlivePingInterval)
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval,
|
||||||
|
+ const std::chrono::seconds subNotifInactivityTimeout)
|
||||||
|
: m_monitoringSession(conn.sessionStart(sysrepo::Datastore::Operational))
|
||||||
|
, nacm(conn)
|
||||||
|
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||||
|
- , m_dynamicSubscriptions(netconfStreamRoot)
|
||||||
|
+ , m_dynamicSubscriptions(netconfStreamRoot, *server, subNotifInactivityTimeout)
|
||||||
|
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||||
|
{
|
||||||
|
server->num_threads(1); // we only use one thread for the server, so we can call join() right away
|
||||||
|
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||||
|
index c98f736..b4db948 100644
|
||||||
|
--- a/src/restconf/Server.h
|
||||||
|
+++ b/src/restconf/Server.h
|
||||||
|
@@ -33,7 +33,8 @@ public:
|
||||||
|
const std::string& address,
|
||||||
|
const std::string& port,
|
||||||
|
const std::chrono::milliseconds timeout = std::chrono::milliseconds{0},
|
||||||
|
- const std::chrono::seconds keepAlivePingInterval = std::chrono::seconds{55});
|
||||||
|
+ const std::chrono::seconds keepAlivePingInterval = std::chrono::seconds{55},
|
||||||
|
+ const std::chrono::seconds subNotifInactivityTimeout = std::chrono::seconds{60});
|
||||||
|
~Server();
|
||||||
|
void join();
|
||||||
|
void stop();
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
index 11dd992..68e9286 100644
|
||||||
|
--- a/tests/restconf-subscribed-notifications.cpp
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -450,3 +450,37 @@ TEST_CASE("Terminating server under notification load")
|
||||||
|
SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {auth});
|
||||||
|
RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+TEST_CASE("Cleaning up inactive subscriptions")
|
||||||
|
+{
|
||||||
|
+ trompeloeil::sequence seq1, seq2;
|
||||||
|
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||||
|
+ spdlog::set_level(spdlog::level::trace);
|
||||||
|
+
|
||||||
|
+ auto srConn = sysrepo::Connection{};
|
||||||
|
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||||
|
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||||
|
+
|
||||||
|
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||||
|
+
|
||||||
|
+ auto nacmGuard = manageNacm(srSess);
|
||||||
|
+ constexpr auto inactivityTimeout = 2s;
|
||||||
|
+ auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||||
|
+
|
||||||
|
+ auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||||
|
+
|
||||||
|
+ SECTION("Client connects and disconnects")
|
||||||
|
+ {
|
||||||
|
+ PREPARE_LOOP_WITH_EXCEPTIONS
|
||||||
|
+ auto thr = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||||
|
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||||
|
+ std::this_thread::sleep_for(1s);
|
||||||
|
+ }));
|
||||||
|
+
|
||||||
|
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_ROOT});
|
||||||
|
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ std::this_thread::sleep_for(inactivityTimeout + 1500ms);
|
||||||
|
+ REQUIRE(get(uri, {AUTH_ROOT}) == Response{404, plaintextHeaders, "Subscription not found."});
|
||||||
|
+}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
From 22cfcbf52be604afe0caafc892f4b7befe03b9db Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 8 Apr 2025 17:00:59 +0200
|
||||||
|
Subject: [PATCH 31/38] restconf: add {kill,delete}-subscription RPCs
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Add implementations for deleting and killing subscriptions [1,2].
|
||||||
|
|
||||||
|
Rousette allows establishing subscriptions by anonymous users. Anonymous
|
||||||
|
users can not call delete-subscription RPC though because we do not know
|
||||||
|
if it is the same anonymous user who initiated the subscription.
|
||||||
|
The subscriptions are killed after a minute of inactivity, so the
|
||||||
|
subscription will get deleted by rousette automatically after the client
|
||||||
|
disconnects.
|
||||||
|
|
||||||
|
[1] https://datatracker.ietf.org/doc/html/rfc8639.html#section-2.4.4
|
||||||
|
[2] https://datatracker.ietf.org/doc/html/rfc8639.html#section-2.4.5
|
||||||
|
|
||||||
|
Change-Id: I443d6fd88f2797045b5ae0a7d7e1a761fe74e7cb
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 44 +++++
|
||||||
|
src/restconf/DynamicSubscriptions.h | 2 +
|
||||||
|
src/restconf/Server.cpp | 4 +
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 178 +++++++++++++++++++-
|
||||||
|
4 files changed, 222 insertions(+), 6 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
index f6defb4..fabc226 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -119,6 +119,32 @@ void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, cons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+void DynamicSubscriptions::deleteSubscription(sysrepo::Session& session, const libyang::DataFormat, const libyang::DataNode& rpcInput, libyang::DataNode&)
|
||||||
|
+{
|
||||||
|
+ const auto isKill = rpcInput.findPath("/ietf-subscribed-notifications:kill-subscription") != std::nullopt;
|
||||||
|
+ const auto subId = std::get<uint32_t>(rpcInput.findPath("id")->asTerm().value());
|
||||||
|
+
|
||||||
|
+ // The RPC is already NACM-checked. Now, retrieve the subscription, if the current user has permission for it
|
||||||
|
+ auto subscriptionData = getSubscriptionForUser(subId, session.getNacmUser());
|
||||||
|
+ if (!subscriptionData) {
|
||||||
|
+ throw ErrorResponse(404, "application", "invalid-value", "Subscription not found.", rpcInput.path());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ /* I *think* the RFC 8639 says that root can use delete-subscription only for subscriptions created by root.
|
||||||
|
+ * This checks if the current user is root and the subscription was created by a different user. If so, reject the request.
|
||||||
|
+ */
|
||||||
|
+ if (!isKill && session.getNacmUser() == session.getNacmRecoveryUser() && subscriptionData->user != session.getNacmRecoveryUser()) {
|
||||||
|
+ //FIXME: pass additional error info (rc:yang-data delete-subscription-error-info from RFC 8639)
|
||||||
|
+ throw ErrorResponse(400, "application", "invalid-attribute", "Trying to delete subscription not created by root. Use kill-subscription instead.", rpcInput.path());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ spdlog::debug("Terminating subscription id {}", subId);
|
||||||
|
+ subscriptionData->subscription.terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||||
|
+
|
||||||
|
+ std::unique_lock lock(m_mutex);
|
||||||
|
+ m_subscriptions.erase(subscriptionData->uuid);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
void DynamicSubscriptions::terminateSubscription(const uint32_t subId)
|
||||||
|
{
|
||||||
|
std::lock_guard lock(m_mutex);
|
||||||
|
@@ -153,6 +179,24 @@ std::shared_ptr<DynamicSubscriptions::SubscriptionData> DynamicSubscriptions::ge
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
+/** @brief Returns the subscription data for the given subscription id and user.
|
||||||
|
+ *
|
||||||
|
+ * @param id The ID of the subscription.
|
||||||
|
+ * @return A shared pointer to the SubscriptionData object if found and user is the one who established the subscription (or NACM recovery user), otherwise nullptr.
|
||||||
|
+ */
|
||||||
|
+std::shared_ptr<DynamicSubscriptions::SubscriptionData> DynamicSubscriptions::getSubscriptionForUser(const uint32_t subId, const std::optional<std::string>& user)
|
||||||
|
+{
|
||||||
|
+ std::unique_lock lock(m_mutex);
|
||||||
|
+
|
||||||
|
+ // FIXME: This is linear search. Maybe use something like boost::multi_index?
|
||||||
|
+ if (auto it = std::find_if(m_subscriptions.begin(), m_subscriptions.end(), [subId](const auto& entry) { return entry.second->subscription.subscriptionId() == subId; });
|
||||||
|
+ it != m_subscriptions.end() && (it->second->user == user || user == sysrepo::Session::getNacmRecoveryUser())) {
|
||||||
|
+ return it->second;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return nullptr;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
boost::uuids::uuid DynamicSubscriptions::makeUUID()
|
||||||
|
{
|
||||||
|
// uuid generator instance accesses must be synchronized (https://www.boost.org/doc/libs/1_88_0/libs/uuid/doc/html/uuid.html#design_notes)
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||||
|
index 0422947..8057b17 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.h
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.h
|
||||||
|
@@ -70,8 +70,10 @@ public:
|
||||||
|
DynamicSubscriptions(const std::string& streamRootUri, const nghttp2::asio_http2::server::http2& server, const std::chrono::seconds inactivityTimeout);
|
||||||
|
~DynamicSubscriptions();
|
||||||
|
std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||||
|
+ std::shared_ptr<SubscriptionData> getSubscriptionForUser(const uint32_t subId, const std::optional<std::string>& user);
|
||||||
|
void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||||
|
void stop();
|
||||||
|
+ void deleteSubscription(sysrepo::Session& session, const libyang::DataFormat, const libyang::DataNode& rpcInput, libyang::DataNode&);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 0c7e855..04cdf56 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -447,6 +447,10 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, liby
|
||||||
|
const std::map<std::string, InternalRPCHandler> handlers{
|
||||||
|
{"/ietf-subscribed-notifications:establish-subscription",
|
||||||
|
{"/ietf-subscribed-notifications:filters", [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.establishSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||||
|
+ {"/ietf-subscribed-notifications:kill-subscription",
|
||||||
|
+ {std::nullopt, [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.deleteSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||||
|
+ {"/ietf-subscribed-notifications:delete-subscription",
|
||||||
|
+ {std::nullopt, [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.deleteSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto rpcPath = rpcInput.path();
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
index 68e9286..7c7de4b 100644
|
||||||
|
--- a/tests/restconf-subscribed-notifications.cpp
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -24,8 +24,13 @@ constexpr auto uuidV4Regex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89a
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
|
+struct EstablishSubscriptionResult {
|
||||||
|
+ uint32_t id;
|
||||||
|
+ std::string url;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
/** @brief Calls establish-subscription rpc, returns the url of the stream associated with the created subscription */
|
||||||
|
-std::string establishSubscription(
|
||||||
|
+EstablishSubscriptionResult establishSubscription(
|
||||||
|
const libyang::Context& ctx,
|
||||||
|
const libyang::DataFormat rpcEncoding,
|
||||||
|
const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||||
|
@@ -79,10 +84,16 @@ std::string establishSubscription(
|
||||||
|
auto reply = ctx.newPath("/ietf-subscribed-notifications:establish-subscription");
|
||||||
|
REQUIRE(reply.parseOp(resp.data, rpcEncoding, libyang::OperationType::ReplyRestconf).tree);
|
||||||
|
|
||||||
|
+ auto idNode = reply.findPath("id", libyang::InputOutputNodes::Output);
|
||||||
|
+ REQUIRE(idNode);
|
||||||
|
+
|
||||||
|
auto urlNode = reply.findPath("ietf-restconf-subscribed-notifications:uri", libyang::InputOutputNodes::Output);
|
||||||
|
REQUIRE(urlNode);
|
||||||
|
|
||||||
|
- return urlNode->asTerm().valueStr();
|
||||||
|
+ return {
|
||||||
|
+ std::get<uint32_t>(idNode->asTerm().value()),
|
||||||
|
+ urlNode->asTerm().valueStr(),
|
||||||
|
+ };
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
@@ -150,7 +161,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
SECTION("User DWDM establishes subscription")
|
||||||
|
{
|
||||||
|
std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||||
|
- auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||||
|
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
|
||||||
|
@@ -350,7 +361,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto uri = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||||
|
|
||||||
|
PREPARE_LOOP_WITH_EXCEPTIONS
|
||||||
|
@@ -393,6 +404,161 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, streamHeaders);
|
||||||
|
RUN_LOOP_WITH_EXCEPTIONS;
|
||||||
|
}
|
||||||
|
+
|
||||||
|
+ SECTION("delete-subscription")
|
||||||
|
+ {
|
||||||
|
+ std::optional<Response> expectedResponse;
|
||||||
|
+ std::map<std::string, std::string> headers;
|
||||||
|
+ headers.insert(CONTENT_TYPE_JSON);
|
||||||
|
+
|
||||||
|
+ SECTION("Subscription created by dwdm")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestAuthHeader = AUTH_DWDM;
|
||||||
|
+
|
||||||
|
+ SECTION("dwdm (author) can delete")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_DWDM);
|
||||||
|
+ expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Anonymous cannot delete anything because of NACM")
|
||||||
|
+ {
|
||||||
|
+ expectedResponse = Response{403, jsonHeaders, R"({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "access-denied",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||||
|
+ "error-message": "Access denied."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)"};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("norules user")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_NORULES);
|
||||||
|
+ expectedResponse = Response{404, jsonHeaders, R"({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "invalid-value",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||||
|
+ "error-message": "Subscription not found."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)"};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("root user")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_ROOT);
|
||||||
|
+ expectedResponse = Response{400, jsonHeaders, R"({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "invalid-attribute",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||||
|
+ "error-message": "Trying to delete subscription not created by root. Use kill-subscription instead."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)"};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == expectedResponse);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Anonymous user cannot delete subscription craeted by anonymous user")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestAuthHeader = std::nullopt;
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == Response{403, jsonHeaders, R"({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "access-denied",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||||
|
+ "error-message": "Access denied."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)"});
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("kill-subscription")
|
||||||
|
+ {
|
||||||
|
+ std::optional<Response> expectedResponse;
|
||||||
|
+ std::map<std::string, std::string> headers;
|
||||||
|
+ headers.insert(CONTENT_TYPE_JSON);
|
||||||
|
+
|
||||||
|
+ SECTION("User cannot kill")
|
||||||
|
+ {
|
||||||
|
+ SECTION("dwdm (author)")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_DWDM);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("anonymous")
|
||||||
|
+ {
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ expectedResponse = Response{403, jsonHeaders, R"({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "access-denied",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:kill-subscription",
|
||||||
|
+ "error-message": "Access denied."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)"};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("root")
|
||||||
|
+ {
|
||||||
|
+ headers.insert(AUTH_ROOT);
|
||||||
|
+ expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", headers, body) == expectedResponse);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Invalid kill/delete-subscription requests")
|
||||||
|
+ {
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"ietf-subscribed-notifications:input": {}})") == Response{400, jsonHeaders, R"({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "protocol",
|
||||||
|
+ "error-tag": "invalid-value",
|
||||||
|
+ "error-path": "/ietf-subscribed-notifications:kill-subscription",
|
||||||
|
+ "error-message": "Mandatory node \"id\" instance does not exist."
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+)"});
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Terminating server under notification load")
|
||||||
|
@@ -416,7 +582,7 @@ TEST_CASE("Terminating server under notification load")
|
||||||
|
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
|
||||||
|
std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||||
|
- auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||||
|
|
||||||
|
PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||||
|
|
||||||
|
@@ -467,7 +633,7 @@ TEST_CASE("Cleaning up inactive subscriptions")
|
||||||
|
constexpr auto inactivityTimeout = 2s;
|
||||||
|
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||||
|
|
||||||
|
- auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||||
|
|
||||||
|
SECTION("Client connects and disconnects")
|
||||||
|
{
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
From c5558b12ee67280b6281b639c3d8652cec3c7fa6 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Wed, 19 Mar 2025 14:39:05 +0100
|
||||||
|
Subject: [PATCH 32/38] restconf: add subscribed notifications filtering
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Implement filtering of subscribed notifications using xpath and subtree
|
||||||
|
filters.
|
||||||
|
|
||||||
|
Change-Id: Iee973a6a2d03b4a0d90f952afe5436ce701787ae
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 2 +-
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 13 ++++-
|
||||||
|
src/restconf/Server.cpp | 2 +-
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 65 ++++++++++++++++++---
|
||||||
|
4 files changed, 70 insertions(+), 12 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 5d3b7ce..28696b3 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -217,7 +217,7 @@ if(BUILD_TESTING)
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang
|
||||||
|
- --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json --enable-feature xpath --enable-feature subtree
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
index fabc226..a13d344 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -59,13 +59,22 @@ sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, c
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rpcInput.findPath("stream-filter-name")) {
|
||||||
|
- throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream filtering is not supported");
|
||||||
|
+ /* TODO: This requires support for modifying subscriptions first; a change of entry in filters container must change all
|
||||||
|
+ * subscriptions using this stream-filter, see for instance https://datatracker.ietf.org/doc/html/rfc8639.html#section-2.7.2 */
|
||||||
|
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream filtering with predefined filters is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stopTime = optionalTime(rpcInput, "stop-time");
|
||||||
|
|
||||||
|
+ std::optional<std::variant<std::string, libyang::DataNodeAny>> filter;
|
||||||
|
+ if (auto node = rpcInput.findPath("stream-xpath-filter")) {
|
||||||
|
+ filter = node->asTerm().valueStr();
|
||||||
|
+ } else if (auto node = rpcInput.findPath("stream-subtree-filter")) {
|
||||||
|
+ filter = node->asAny();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
return session.subscribeNotifications(
|
||||||
|
- std::nullopt /* TODO xpath filter */,
|
||||||
|
+ filter,
|
||||||
|
streamNode->asTerm().valueStr(),
|
||||||
|
stopTime,
|
||||||
|
std::nullopt /* TODO replayStart */);
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 04cdf56..76603b3 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -932,7 +932,7 @@ Server::Server(
|
||||||
|
{"ietf-netconf", "", {}},
|
||||||
|
{"ietf-yang-library", "2019-01-04", {}},
|
||||||
|
{"ietf-yang-patch", "2017-02-22", {}},
|
||||||
|
- {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json"}},
|
||||||
|
+ {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json", "xpath", "subtree"}},
|
||||||
|
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||||
|
}) {
|
||||||
|
if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
index 7c7de4b..7fcf549 100644
|
||||||
|
--- a/tests/restconf-subscribed-notifications.cpp
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -34,7 +34,8 @@ EstablishSubscriptionResult establishSubscription(
|
||||||
|
const libyang::Context& ctx,
|
||||||
|
const libyang::DataFormat rpcEncoding,
|
||||||
|
const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||||
|
- const std::optional<std::string>& encodingLeafValue)
|
||||||
|
+ const std::optional<std::string>& encodingLeafValue,
|
||||||
|
+ const std::variant<std::monostate, std::string, libyang::XML>& filter)
|
||||||
|
{
|
||||||
|
constexpr auto jsonPrefix = "ietf-subscribed-notifications";
|
||||||
|
constexpr auto xmlNamespace = "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications";
|
||||||
|
@@ -56,6 +57,14 @@ EstablishSubscriptionResult establishSubscription(
|
||||||
|
rpcTree.newPath("encoding", *encodingLeafValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (std::holds_alternative<std::string>(filter)) {
|
||||||
|
+ rpcTree.newPath("stream-xpath-filter", std::get<std::string>(filter));
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (std::holds_alternative<libyang::XML>(filter)) {
|
||||||
|
+ rpcTree.newPath2("stream-subtree-filter", std::get<libyang::XML>(filter));
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
switch (rpcEncoding) {
|
||||||
|
case libyang::DataFormat::JSON:
|
||||||
|
requestHeaders.insert(CONTENT_TYPE_JSON);
|
||||||
|
@@ -128,6 +137,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
|
||||||
|
libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
std::optional<std::string> rpcSubscriptionEncoding;
|
||||||
|
+ std::variant<std::monostate, std::string, libyang::XML> rpcFilter;
|
||||||
|
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
|
||||||
|
SECTION("NACM authorization")
|
||||||
|
@@ -161,7 +171,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
SECTION("User DWDM establishes subscription")
|
||||||
|
{
|
||||||
|
std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt, rpcFilter);
|
||||||
|
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
|
||||||
|
@@ -277,6 +287,23 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+)###"});
|
||||||
|
+
|
||||||
|
+ srSess.switchDatastore(sysrepo::Datastore::Operational);
|
||||||
|
+ srSess.setItem("/ietf-subscribed-notifications:filters/stream-filter[name='xyz']/stream-xpath-filter", "/example:eventA");
|
||||||
|
+ srSess.applyChanges();
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "stream-filter-name": "xyz" } })###")
|
||||||
|
+ == Response{400, jsonHeaders, R"###({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "invalid-attribute",
|
||||||
|
+ "error-message": "Stream filtering with predefined filters is not supported"
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
)###"});
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -330,6 +357,28 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ SECTION("XPath filter set")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ rpcFilter = "/example:eventA | /example:eventB";
|
||||||
|
+ rpcSubscriptionEncoding = "encode-json";
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("Subtree filter set")
|
||||||
|
+ {
|
||||||
|
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ // Constructing the filter as XML is only an implementation detail. The tree is then constructed as JSON in establishSubscription
|
||||||
|
+ rpcFilter = libyang::XML{"<eventA xmlns='http://example.tld/example' />"};
|
||||||
|
+ rpcSubscriptionEncoding = "encode-json";
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
SECTION("Content-type with set encode leaf")
|
||||||
|
{
|
||||||
|
rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
@@ -361,7 +410,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||||
|
|
||||||
|
PREPARE_LOOP_WITH_EXCEPTIONS
|
||||||
|
@@ -474,7 +523,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
)"};
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == expectedResponse);
|
||||||
|
}
|
||||||
|
@@ -482,7 +531,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
SECTION("Anonymous user cannot delete subscription craeted by anonymous user")
|
||||||
|
{
|
||||||
|
rpcRequestAuthHeader = std::nullopt;
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == Response{403, jsonHeaders, R"({
|
||||||
|
"ietf-restconf:errors": {
|
||||||
|
@@ -538,7 +587,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", headers, body) == expectedResponse);
|
||||||
|
}
|
||||||
|
@@ -582,7 +631,7 @@ TEST_CASE("Terminating server under notification load")
|
||||||
|
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
|
||||||
|
std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt, {});
|
||||||
|
|
||||||
|
PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||||
|
|
||||||
|
@@ -633,7 +682,7 @@ TEST_CASE("Cleaning up inactive subscriptions")
|
||||||
|
constexpr auto inactivityTimeout = 2s;
|
||||||
|
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||||
|
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt, {});
|
||||||
|
|
||||||
|
SECTION("Client connects and disconnects")
|
||||||
|
{
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
From fe91af1eb38888d7eacb36cb5a299bd3163f7c60 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 14 Oct 2025 17:22:00 +0200
|
||||||
|
Subject: [PATCH 33/38] cmake: wrap long line
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: Id94e18d581dbfbe2e6d386be6a232544f9344eec
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 7 ++++++-
|
||||||
|
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 28696b3..3d79dad 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -217,7 +217,12 @@ if(BUILD_TESTING)
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang
|
||||||
|
- --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json --enable-feature xpath --enable-feature subtree
|
||||||
|
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang
|
||||||
|
+ --enable-feature replay
|
||||||
|
+ --enable-feature encode-xml
|
||||||
|
+ --enable-feature encode-json
|
||||||
|
+ --enable-feature xpath
|
||||||
|
+ --enable-feature subtree
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
From 817debab72d10f2f30e83f1630bebdcdf3ec9455 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Mon, 10 Mar 2025 15:33:08 +0100
|
||||||
|
Subject: [PATCH 34/38] restconf: support replays in subscribed notifications
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: Idfc3ceb99c8111c5575c1c8b94d539f234fa43be
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 1 +
|
||||||
|
src/restconf/DynamicSubscriptions.cpp | 24 ++++-
|
||||||
|
src/restconf/Server.cpp | 2 +-
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 102 ++++++++++++++++++--
|
||||||
|
4 files changed, 115 insertions(+), 14 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 3d79dad..f98a64b 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -223,6 +223,7 @@ if(BUILD_TESTING)
|
||||||
|
--enable-feature encode-json
|
||||||
|
--enable-feature xpath
|
||||||
|
--enable-feature subtree
|
||||||
|
+ --enable-feature replay
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||||
|
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
index a13d344..9be3e92 100644
|
||||||
|
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||||
|
@@ -50,7 +50,7 @@ libyang::DataFormat getEncoding(const libyang::DataNode& rpcInput, const libyang
|
||||||
|
return requestEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
-sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, const libyang::DataNode& rpcInput)
|
||||||
|
+sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput)
|
||||||
|
{
|
||||||
|
auto streamNode = rpcInput.findPath("stream");
|
||||||
|
|
||||||
|
@@ -73,11 +73,27 @@ sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, c
|
||||||
|
filter = node->asAny();
|
||||||
|
}
|
||||||
|
|
||||||
|
- return session.subscribeNotifications(
|
||||||
|
+ std::optional<sysrepo::NotificationTimeStamp> replayStartTime;
|
||||||
|
+ if (auto node = rpcInput.findPath("replay-start-time")) {
|
||||||
|
+ replayStartTime = libyang::fromYangTimeFormat<sysrepo::NotificationTimeStamp::clock>(node->asTerm().valueStr());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ auto sub = session.subscribeNotifications(
|
||||||
|
filter,
|
||||||
|
streamNode->asTerm().valueStr(),
|
||||||
|
stopTime,
|
||||||
|
- std::nullopt /* TODO replayStart */);
|
||||||
|
+ replayStartTime);
|
||||||
|
+
|
||||||
|
+ /* Node replay-start-time-revision should be set only if time was revised to be different than the requested start time,
|
||||||
|
+ * i.e. when the "replay-start-time" contains a value that is earlier than what a publisher's retained history.
|
||||||
|
+ * Then the actual publisher's revised start time MUST be set in the returned "replay-start-time-revision" object.
|
||||||
|
+ * (RFC 8639, 2.4.2.1)
|
||||||
|
+ * */
|
||||||
|
+ if (auto replayStartTimeRevision = sub.replayStartTime(); replayStartTimeRevision && replayStartTime) {
|
||||||
|
+ rpcOutput.newPath("replay-start-time-revision", libyang::yangTimeFormat(*replayStartTimeRevision, libyang::TimezoneInterpretation::Local), libyang::CreationOptions::Output);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -109,7 +125,7 @@ void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, cons
|
||||||
|
auto dataFormat = getEncoding(rpcInput, requestEncoding);
|
||||||
|
|
||||||
|
try {
|
||||||
|
- auto sub = makeStreamSubscription(session, rpcInput);
|
||||||
|
+ auto sub = makeStreamSubscription(session, rpcInput, rpcOutput);
|
||||||
|
|
||||||
|
rpcOutput.newPath("id", std::to_string(sub.subscriptionId()), libyang::CreationOptions::Output);
|
||||||
|
rpcOutput.newPath("ietf-restconf-subscribed-notifications:uri", m_restconfStreamUri + "subscribed/" + boost::uuids::to_string(uuid), libyang::CreationOptions::Output);
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index 76603b3..d166236 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -932,7 +932,7 @@ Server::Server(
|
||||||
|
{"ietf-netconf", "", {}},
|
||||||
|
{"ietf-yang-library", "2019-01-04", {}},
|
||||||
|
{"ietf-yang-patch", "2017-02-22", {}},
|
||||||
|
- {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json", "xpath", "subtree"}},
|
||||||
|
+ {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json", "xpath", "subtree", "replay"}},
|
||||||
|
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||||
|
}) {
|
||||||
|
if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
index 7fcf549..d42fa96 100644
|
||||||
|
--- a/tests/restconf-subscribed-notifications.cpp
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -18,7 +18,7 @@ static const auto SERVER_PORT = "10092";
|
||||||
|
#include "tests/pretty_printers.h"
|
||||||
|
|
||||||
|
#define SEND_NOTIFICATION(DATA) notifSession.sendNotification(*ctx.parseOp(DATA, libyang::DataFormat::JSON, libyang::OperationType::NotificationYang).op, sysrepo::Wait::No);
|
||||||
|
-
|
||||||
|
+#define REPLAY_COMPLETED trompeloeil::re(R"(^\{"ietf-subscribed-notifications:replay-completed":\{"id":[0-9]+\}\}$)")
|
||||||
|
constexpr auto uuidV4Regex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}";
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
@@ -27,6 +27,7 @@ using namespace std::string_literals;
|
||||||
|
struct EstablishSubscriptionResult {
|
||||||
|
uint32_t id;
|
||||||
|
std::string url;
|
||||||
|
+ std::optional<sysrepo::NotificationTimeStamp> replayStartTimeRevision;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @brief Calls establish-subscription rpc, returns the url of the stream associated with the created subscription */
|
||||||
|
@@ -35,7 +36,8 @@ EstablishSubscriptionResult establishSubscription(
|
||||||
|
const libyang::DataFormat rpcEncoding,
|
||||||
|
const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||||
|
const std::optional<std::string>& encodingLeafValue,
|
||||||
|
- const std::variant<std::monostate, std::string, libyang::XML>& filter)
|
||||||
|
+ const std::variant<std::monostate, std::string, libyang::XML>& filter,
|
||||||
|
+ const std::optional<sysrepo::NotificationTimeStamp>& replayStartTime)
|
||||||
|
{
|
||||||
|
constexpr auto jsonPrefix = "ietf-subscribed-notifications";
|
||||||
|
constexpr auto xmlNamespace = "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications";
|
||||||
|
@@ -65,6 +67,10 @@ EstablishSubscriptionResult establishSubscription(
|
||||||
|
rpcTree.newPath2("stream-subtree-filter", std::get<libyang::XML>(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
+ if (replayStartTime) {
|
||||||
|
+ rpcTree.newPath("replay-start-time", libyang::yangTimeFormat(*replayStartTime, libyang::TimezoneInterpretation::Local));
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
switch (rpcEncoding) {
|
||||||
|
case libyang::DataFormat::JSON:
|
||||||
|
requestHeaders.insert(CONTENT_TYPE_JSON);
|
||||||
|
@@ -99,9 +105,15 @@ EstablishSubscriptionResult establishSubscription(
|
||||||
|
auto urlNode = reply.findPath("ietf-restconf-subscribed-notifications:uri", libyang::InputOutputNodes::Output);
|
||||||
|
REQUIRE(urlNode);
|
||||||
|
|
||||||
|
+ std::optional<sysrepo::NotificationTimeStamp> replayStartTimeRevision;
|
||||||
|
+ if (auto node = reply.findPath("ietf-subscribed-notifications:replay-start-time-revision", libyang::InputOutputNodes::Output)) {
|
||||||
|
+ replayStartTimeRevision = libyang::fromYangTimeFormat<sysrepo::NotificationTimeStamp::clock>(node->asTerm().valueStr());
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
return {
|
||||||
|
std::get<uint32_t>(idNode->asTerm().value()),
|
||||||
|
urlNode->asTerm().valueStr(),
|
||||||
|
+ replayStartTimeRevision
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -131,6 +143,8 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
R"({"example:eventA":{"message":"almost finished","progress":99}})",
|
||||||
|
R"({"example:tlc":{"list":[{"name":"k1","notif":{"message":"nested"}}]}})",
|
||||||
|
};
|
||||||
|
+ const std::string notificationForReplayJSON = R"({"example:eventA":{"message":"this-should-be-sent-very-early","progress":0}})";
|
||||||
|
+
|
||||||
|
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||||
|
|
||||||
|
RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||||
|
@@ -138,7 +152,10 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
std::optional<std::string> rpcSubscriptionEncoding;
|
||||||
|
std::variant<std::monostate, std::string, libyang::XML> rpcFilter;
|
||||||
|
+ std::optional<sysrepo::NotificationTimeStamp> rpcReplayStart;
|
||||||
|
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
+ std::pair<sysrepo::NotificationTimeStamp, sysrepo::NotificationTimeStamp> replayedNotificationSendInterval; // bounds for replayed notification event time
|
||||||
|
+ bool shouldReviseStartTime = false;
|
||||||
|
|
||||||
|
SECTION("NACM authorization")
|
||||||
|
{
|
||||||
|
@@ -171,7 +188,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
SECTION("User DWDM establishes subscription")
|
||||||
|
{
|
||||||
|
std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt, rpcFilter);
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt, rpcFilter, rpcReplayStart);
|
||||||
|
|
||||||
|
std::map<std::string, std::string> headers;
|
||||||
|
|
||||||
|
@@ -304,6 +321,23 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+)###"});
|
||||||
|
+
|
||||||
|
+ // replay-start-time > stop-time
|
||||||
|
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription",
|
||||||
|
+ {CONTENT_TYPE_JSON},
|
||||||
|
+ R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "replay-start-time": "2000-11-11T11:22:33Z", "stop-time": "2000-01-01T00:00:00Z" } })###")
|
||||||
|
+ == Response{400, jsonHeaders, R"###({
|
||||||
|
+ "ietf-restconf:errors": {
|
||||||
|
+ "error": [
|
||||||
|
+ {
|
||||||
|
+ "error-type": "application",
|
||||||
|
+ "error-tag": "invalid-attribute",
|
||||||
|
+ "error-message": "Couldn't create notification subscription: SR_ERR_INVAL_ARG\u000A Specified \"stop-time\" is earlier than \"start-time\". (SR_ERR_INVAL_ARG)"
|
||||||
|
+ }
|
||||||
|
+ ]
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
)###"});
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -379,6 +413,48 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ SECTION("Replays")
|
||||||
|
+ {
|
||||||
|
+ // Announce replay support and send one notification before the client connects
|
||||||
|
+ srConn.setModuleReplaySupport("example", true);
|
||||||
|
+
|
||||||
|
+ {
|
||||||
|
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||||
|
+ auto ctx = notifSession.getContext();
|
||||||
|
+ replayedNotificationSendInterval.first = std::chrono::system_clock::now();
|
||||||
|
+ SEND_NOTIFICATION(notificationForReplayJSON);
|
||||||
|
+ replayedNotificationSendInterval.second = std::chrono::system_clock::now();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||||
|
+ rpcSubscriptionEncoding = "encode-json";
|
||||||
|
+
|
||||||
|
+ SECTION("replay-start-time-revision is announced to the client")
|
||||||
|
+ {
|
||||||
|
+ rpcReplayStart = std::chrono::system_clock::now() - 666s /* Reasonable time in the past, earlier than the replayed notification was sent */;
|
||||||
|
+
|
||||||
|
+ EXPECT_NOTIFICATION(notificationForReplayJSON, seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(REPLAY_COMPLETED, seq1);
|
||||||
|
+ shouldReviseStartTime = true;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ SECTION("replay-start-time-revision not announced")
|
||||||
|
+ {
|
||||||
|
+ /* Ask for replay since the time when the first notification was sent. This should not revise the start time
|
||||||
|
+ * because we are not asking for the start *before* our history. (RFC 8639, 2.4.2.1) */
|
||||||
|
+ rpcReplayStart = replayedNotificationSendInterval.second;
|
||||||
|
+
|
||||||
|
+ EXPECT_NOTIFICATION(REPLAY_COMPLETED, seq1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[2], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||||
|
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
SECTION("Content-type with set encode leaf")
|
||||||
|
{
|
||||||
|
rpcRequestAuthHeader = AUTH_ROOT;
|
||||||
|
@@ -410,9 +486,17 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||||
|
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||||
|
|
||||||
|
+ if (shouldReviseStartTime) {
|
||||||
|
+ REQUIRE(replayStartTimeRevision);
|
||||||
|
+ REQUIRE(replayedNotificationSendInterval.first <= *replayStartTimeRevision);
|
||||||
|
+ REQUIRE(*replayStartTimeRevision <= replayedNotificationSendInterval.second);
|
||||||
|
+ } else {
|
||||||
|
+ REQUIRE(!replayStartTimeRevision);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
PREPARE_LOOP_WITH_EXCEPTIONS
|
||||||
|
|
||||||
|
// Here's how these two threads work together.
|
||||||
|
@@ -523,7 +607,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
)"};
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||||
|
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == expectedResponse);
|
||||||
|
}
|
||||||
|
@@ -531,7 +615,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
SECTION("Anonymous user cannot delete subscription craeted by anonymous user")
|
||||||
|
{
|
||||||
|
rpcRequestAuthHeader = std::nullopt;
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||||
|
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == Response{403, jsonHeaders, R"({
|
||||||
|
"ietf-restconf:errors": {
|
||||||
|
@@ -587,7 +671,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||||
|
expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||||
|
}
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||||
|
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||||
|
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", headers, body) == expectedResponse);
|
||||||
|
}
|
||||||
|
@@ -631,7 +715,7 @@ TEST_CASE("Terminating server under notification load")
|
||||||
|
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||||
|
|
||||||
|
std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt, {});
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt, {}, std::nullopt);
|
||||||
|
|
||||||
|
PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||||
|
|
||||||
|
@@ -682,7 +766,7 @@ TEST_CASE("Cleaning up inactive subscriptions")
|
||||||
|
constexpr auto inactivityTimeout = 2s;
|
||||||
|
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||||
|
|
||||||
|
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt, {});
|
||||||
|
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt, {}, std::nullopt);
|
||||||
|
|
||||||
|
SECTION("Client connects and disconnects")
|
||||||
|
{
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
From 7d6b1a463fdacefaf7a7057564f6f4d4a8cb4632 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||||
|
Date: Tue, 25 Mar 2025 11:06:26 +0100
|
||||||
|
Subject: [PATCH 35/38] restconf: refactor getting datastore from string
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
This will be useful when processing YANG push requests.
|
||||||
|
|
||||||
|
Change-Id: I1e76335c8d674e81890d6cd1e973adaf521eb205
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/restconf/uri.cpp | 19 +++++--------------
|
||||||
|
src/restconf/utils/sysrepo.cpp | 16 ++++++++++++++++
|
||||||
|
src/restconf/utils/sysrepo.h | 1 +
|
||||||
|
3 files changed, 22 insertions(+), 14 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||||
|
index 99c3d1f..6143435 100644
|
||||||
|
--- a/src/restconf/uri.cpp
|
||||||
|
+++ b/src/restconf/uri.cpp
|
||||||
|
@@ -17,6 +17,7 @@
|
||||||
|
#include "restconf/Exceptions.h"
|
||||||
|
#include "restconf/uri.h"
|
||||||
|
#include "restconf/uri_impl.h"
|
||||||
|
+#include "restconf/utils/sysrepo.h"
|
||||||
|
#include "restconf/utils/yang.h"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
@@ -298,21 +299,11 @@ std::optional<sysrepo::Datastore> datastoreFromApiIdentifier(const boost::option
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
- if (*datastore->prefix == "ietf-datastores") {
|
||||||
|
- if (datastore->identifier == "running") {
|
||||||
|
- return sysrepo::Datastore::Running;
|
||||||
|
- } else if (datastore->identifier == "operational") {
|
||||||
|
- return sysrepo::Datastore::Operational;
|
||||||
|
- } else if (datastore->identifier == "candidate") {
|
||||||
|
- return sysrepo::Datastore::Candidate;
|
||||||
|
- } else if (datastore->identifier == "startup") {
|
||||||
|
- return sysrepo::Datastore::Startup;
|
||||||
|
- } else if (datastore->identifier == "factory-default") {
|
||||||
|
- return sysrepo::Datastore::FactoryDefault;
|
||||||
|
- }
|
||||||
|
+ try {
|
||||||
|
+ return datastoreFromString(*datastore->prefix + ":"s + datastore->identifier);
|
||||||
|
+ } catch (const std::runtime_error&) {
|
||||||
|
+ throw ErrorResponse(400, "application", "operation-failed", "Unsupported datastore " + *datastore->prefix + ":" + datastore->identifier);
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- throw ErrorResponse(400, "application", "operation-failed", "Unsupported datastore " + *datastore->prefix + ":" + datastore->identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> getModuleOrSubmodule(const libyang::Context& ctx, const std::string& name, const std::optional<std::string>& revision)
|
||||||
|
diff --git a/src/restconf/utils/sysrepo.cpp b/src/restconf/utils/sysrepo.cpp
|
||||||
|
index 622ab27..9f14cc7 100644
|
||||||
|
--- a/src/restconf/utils/sysrepo.cpp
|
||||||
|
+++ b/src/restconf/utils/sysrepo.cpp
|
||||||
|
@@ -22,4 +22,20 @@ ScopedDatastoreSwitch::~ScopedDatastoreSwitch()
|
||||||
|
m_session.switchDatastore(m_oldDatastore);
|
||||||
|
}
|
||||||
|
|
||||||
|
+sysrepo::Datastore datastoreFromString(const std::string& datastore)
|
||||||
|
+{
|
||||||
|
+ if (datastore == "ietf-datastores:running") {
|
||||||
|
+ return sysrepo::Datastore::Running;
|
||||||
|
+ } else if (datastore == "ietf-datastores:operational") {
|
||||||
|
+ return sysrepo::Datastore::Operational;
|
||||||
|
+ } else if (datastore == "ietf-datastores:candidate") {
|
||||||
|
+ return sysrepo::Datastore::Candidate;
|
||||||
|
+ } else if (datastore == "ietf-datastores:startup") {
|
||||||
|
+ return sysrepo::Datastore::Startup;
|
||||||
|
+ } else if (datastore == "ietf-datastores:factory-default") {
|
||||||
|
+ return sysrepo::Datastore::FactoryDefault;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ throw std::runtime_error("Unknown datastore '" + datastore + "'");
|
||||||
|
+}
|
||||||
|
}
|
||||||
|
diff --git a/src/restconf/utils/sysrepo.h b/src/restconf/utils/sysrepo.h
|
||||||
|
index da6c359..0eced9e 100644
|
||||||
|
--- a/src/restconf/utils/sysrepo.h
|
||||||
|
+++ b/src/restconf/utils/sysrepo.h
|
||||||
|
@@ -26,4 +26,5 @@ public:
|
||||||
|
ScopedDatastoreSwitch& operator=(ScopedDatastoreSwitch&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
+sysrepo::Datastore datastoreFromString(const std::string& datastore);
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
From c1d5d143020ddc51c3886b82e87d463cf3211fca Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Fri, 21 Nov 2025 10:44:47 +0100
|
||||||
|
Subject: [PATCH 36/38] build: remove duplicate line
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Fixes: 817debab restconf: support replays in subscribed notifications
|
||||||
|
Change-Id: I4d2ea8429a4cb7b77f8739aff7a26a03cf535756
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 1 -
|
||||||
|
1 file changed, 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index f98a64b..3d79dad 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -223,7 +223,6 @@ if(BUILD_TESTING)
|
||||||
|
--enable-feature encode-json
|
||||||
|
--enable-feature xpath
|
||||||
|
--enable-feature subtree
|
||||||
|
- --enable-feature replay
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||||
|
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
From ed62a496744de0e0dc31171167d5d6f69f54af93 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Fri, 21 Nov 2025 12:54:44 +0100
|
||||||
|
Subject: [PATCH 37/38] remove obsolete targets
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: Idde217a4ae1bc6f03657d9609e0ec1b0a47a9747
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 9 --------
|
||||||
|
src/clock.cpp | 62 --------------------------------------------------
|
||||||
|
src/events.cpp | 33 ---------------------------
|
||||||
|
3 files changed, 104 deletions(-)
|
||||||
|
delete mode 100644 src/clock.cpp
|
||||||
|
delete mode 100644 src/events.cpp
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 3d79dad..4a91128 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -125,14 +125,6 @@ target_link_libraries(rousette-restconf PUBLIC rousette-http rousette-sysrepo ro
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/auth/NacmIdentities.h.in ${CMAKE_CURRENT_BINARY_DIR}/NacmIdentities.h @ONLY)
|
||||||
|
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
-add_executable(clock-demo src/clock.cpp)
|
||||||
|
-target_link_libraries(clock-demo rousette-http Boost::system Threads::Threads)
|
||||||
|
-
|
||||||
|
-add_executable(watch-operational-ds
|
||||||
|
- src/events.cpp
|
||||||
|
-)
|
||||||
|
-target_link_libraries(watch-operational-ds PUBLIC rousette-sysrepo)
|
||||||
|
-
|
||||||
|
add_executable(rousette src/restconf/main.cpp)
|
||||||
|
target_link_libraries(rousette PUBLIC rousette-restconf PkgConfig::DOCOPT)
|
||||||
|
if(SYSTEMD_FOUND)
|
||||||
|
@@ -140,7 +132,6 @@ if(SYSTEMD_FOUND)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS
|
||||||
|
- # clock-demo
|
||||||
|
# watch-operational-ds
|
||||||
|
rousette
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
|
||||||
|
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||||
|
deleted file mode 100644
|
||||||
|
index aa72849..0000000
|
||||||
|
--- a/src/clock.cpp
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,62 +0,0 @@
|
||||||
|
-/*
|
||||||
|
- * Copyright (C) 2016-2021 CESNET, https://photonics.cesnet.cz/
|
||||||
|
- *
|
||||||
|
- * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||||
|
- *
|
||||||
|
-*/
|
||||||
|
-
|
||||||
|
-#include <boost/lexical_cast.hpp>
|
||||||
|
-#include <chrono>
|
||||||
|
-#include <nghttp2/asio_http2_server.h>
|
||||||
|
-#include <spdlog/spdlog.h>
|
||||||
|
-#include <thread>
|
||||||
|
-#include "http/EventStream.h"
|
||||||
|
-
|
||||||
|
-using namespace std::literals;
|
||||||
|
-
|
||||||
|
-constexpr auto keepAlivePingInterval = std::chrono::seconds{55};
|
||||||
|
-
|
||||||
|
-int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||||
|
-{
|
||||||
|
- spdlog::set_level(spdlog::level::trace);
|
||||||
|
-
|
||||||
|
- rousette::http::EventStream::Termination shutdown;
|
||||||
|
- rousette::http::EventStream::EventSignal sig;
|
||||||
|
- std::jthread timer{[&sig]() {
|
||||||
|
- for (int i = 0; /* forever */; ++i) {
|
||||||
|
- std::this_thread::sleep_for(666ms);
|
||||||
|
- spdlog::info("tick: {}", i);
|
||||||
|
- sig("ping #" + std::to_string(i));
|
||||||
|
- }
|
||||||
|
- }};
|
||||||
|
-
|
||||||
|
- nghttp2::asio_http2::server::http2 server;
|
||||||
|
- server.num_threads(4);
|
||||||
|
-
|
||||||
|
- server.handle("/events", [&shutdown, &sig](const auto& req, const auto& res) {
|
||||||
|
- rousette::http::EventStream::create(req, res, shutdown, sig, keepAlivePingInterval);
|
||||||
|
- });
|
||||||
|
-
|
||||||
|
- server.handle("/", [](const auto& req, const auto& resp) {
|
||||||
|
- spdlog::info("{}: {} {}", boost::lexical_cast<std::string>(req.remote_endpoint()), req.method(), req.uri().raw_path);
|
||||||
|
- resp.write_head(200, {{"content-type", {"text/html", false}}});
|
||||||
|
- resp.end(R"(<html><head><title>nghttp2 event stream</title></head>
|
||||||
|
-<body><h1>events</h1><ul id="x"></ul>
|
||||||
|
-<script type="text/javascript">
|
||||||
|
-const ev = new EventSource("/events");
|
||||||
|
-ev.onmessage = function(event) {
|
||||||
|
- const li = document.createElement("li");
|
||||||
|
- li.textContent = event.data;
|
||||||
|
- document.getElementById("x").appendChild(li);
|
||||||
|
-};
|
||||||
|
-</script>
|
||||||
|
-</body>
|
||||||
|
-</html>)");
|
||||||
|
- });
|
||||||
|
-
|
||||||
|
- boost::system::error_code ec;
|
||||||
|
- if (server.listen_and_serve(ec, "::", "10080")) {
|
||||||
|
- return 1;
|
||||||
|
- }
|
||||||
|
- return 0;
|
||||||
|
-}
|
||||||
|
diff --git a/src/events.cpp b/src/events.cpp
|
||||||
|
deleted file mode 100644
|
||||||
|
index dc0477a..0000000
|
||||||
|
--- a/src/events.cpp
|
||||||
|
+++ /dev/null
|
||||||
|
@@ -1,33 +0,0 @@
|
||||||
|
-/*
|
||||||
|
- * Copyright (C) 2016-2021 CESNET, https://photonics.cesnet.cz/
|
||||||
|
- *
|
||||||
|
- * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||||
|
- *
|
||||||
|
-*/
|
||||||
|
-
|
||||||
|
-#include <csignal>
|
||||||
|
-#include <spdlog/spdlog.h>
|
||||||
|
-#include <sysrepo-cpp/Session.hpp>
|
||||||
|
-#include <unistd.h>
|
||||||
|
-#include "sr/AllEvents.h"
|
||||||
|
-
|
||||||
|
-using namespace rousette::sr;
|
||||||
|
-
|
||||||
|
-int main(int argc [[maybe_unused]], char* argv [[maybe_unused]] [])
|
||||||
|
-{
|
||||||
|
- spdlog::set_level(spdlog::level::trace);
|
||||||
|
-
|
||||||
|
- auto sess = sysrepo::Connection{}.sessionStart();
|
||||||
|
- auto e = AllEvents{
|
||||||
|
- sess,
|
||||||
|
- /* AllEvents::WithAttributes::All, */
|
||||||
|
- AllEvents::WithAttributes::RemoveEmptyOperationAndOrigin,
|
||||||
|
- /* AllEvents::WithAttributes::None, */
|
||||||
|
- };
|
||||||
|
-
|
||||||
|
- signal(SIGTERM, [](int) {});
|
||||||
|
- signal(SIGINT, [](int) {});
|
||||||
|
- pause();
|
||||||
|
-
|
||||||
|
- return 0;
|
||||||
|
-}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
From 5a1d0dac28c1a78e90d3906693228584adcf3ab1 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 20 Nov 2025 16:13:43 +0100
|
||||||
|
Subject: [PATCH 38/38] port to libyang v4.2
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/9012
|
||||||
|
Change-Id: I1ac6b25a3c1034f06917594732f066fd77d4b26b
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 2 +-
|
||||||
|
src/restconf/Server.cpp | 10 +++++-----
|
||||||
|
src/restconf/utils/yang.cpp | 2 +-
|
||||||
|
src/sr/AllEvents.cpp | 4 ++--
|
||||||
|
src/sr/OpticalEvents.cpp | 2 +-
|
||||||
|
tests/event_watchers.cpp | 2 +-
|
||||||
|
tests/restconf-subscribed-notifications.cpp | 2 +-
|
||||||
|
7 files changed, 12 insertions(+), 12 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 4a91128..4bb029b 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -76,7 +76,7 @@ find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||||
|
|
||||||
|
pkg_check_modules(SYSREPO REQUIRED sysrepo IMPORTED_TARGET)
|
||||||
|
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=7)
|
||||||
|
-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=5)
|
||||||
|
+pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=6)
|
||||||
|
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||||
|
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||||
|
pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt)
|
||||||
|
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||||
|
index d166236..3c7c3c7 100644
|
||||||
|
--- a/src/restconf/Server.cpp
|
||||||
|
+++ b/src/restconf/Server.cpp
|
||||||
|
@@ -111,7 +111,7 @@ void rejectWithErrorImpl(libyang::Context ctx, const libyang::DataFormat& dataFo
|
||||||
|
}
|
||||||
|
|
||||||
|
res.write_head(code, headers);
|
||||||
|
- res.end(*parent.printStr(dataFormat, libyang::PrintFlags::WithSiblings));
|
||||||
|
+ res.end(*parent.printStr(dataFormat, libyang::PrintFlags::Siblings));
|
||||||
|
}
|
||||||
|
|
||||||
|
void rejectWithError(libyang::Context ctx, const libyang::DataFormat& dataFormat, const request& req, const response& res, const int code, const std::string errorType, const std::string& errorTag, const std::string& errorMessage, const std::optional<std::string>& errorPath)
|
||||||
|
@@ -543,7 +543,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||||
|
contentType(requestCtx->dataFormat.response),
|
||||||
|
CORS,
|
||||||
|
});
|
||||||
|
- requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||||
|
+ requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
|
||||||
|
}
|
||||||
|
|
||||||
|
void processPost(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||||
|
@@ -709,7 +709,7 @@ void processYangPatch(std::shared_ptr<RequestContext> requestCtx, const std::chr
|
||||||
|
yangPatchStatus->newExtPath(yangPatchStatusExt, "/ietf-yang-patch:yang-patch-status/ok", std::nullopt);
|
||||||
|
|
||||||
|
requestCtx->res.write_head(200, {contentType(requestCtx->dataFormat.response), CORS});
|
||||||
|
- requestCtx->res.end(*yangPatchStatus->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings));
|
||||||
|
+ requestCtx->res.end(*yangPatchStatus->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::Siblings));
|
||||||
|
}
|
||||||
|
|
||||||
|
void processPutOrPlainPatch(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||||
|
@@ -842,7 +842,7 @@ libyang::PrintFlags libyangPrintFlags(const libyang::DataNode& dataNode, const s
|
||||||
|
} catch(const libyang::Error& e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
- libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont;
|
||||||
|
+ libyang::PrintFlags ret = libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers;
|
||||||
|
|
||||||
|
if (!withDefaults && node && (node->schema().nodeType() == libyang::NodeType::Leaf || node->schema().nodeType() == libyang::NodeType::Leaflist) && node->asTerm().isImplicitDefault()) {
|
||||||
|
return ret | libyang::PrintFlags::WithDefaultsAll;
|
||||||
|
@@ -1116,7 +1116,7 @@ Server::Server(
|
||||||
|
case RestconfRequest::Type::ListRPC:
|
||||||
|
res.write_head(200, {contentType(dataFormat.response), CORS});
|
||||||
|
res.end(*apiResource(sess.getContext(), restconfRequest.type, dataFormat.response)
|
||||||
|
- .printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||||
|
+ .printStr(dataFormat.response, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RestconfRequest::Type::GetData: {
|
||||||
|
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||||
|
index 9358840..5c311c1 100644
|
||||||
|
--- a/src/restconf/utils/yang.cpp
|
||||||
|
+++ b/src/restconf/utils/yang.cpp
|
||||||
|
@@ -112,7 +112,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||||
|
envelope->insertChild(*eventTime);
|
||||||
|
envelope->insertChild(notification);
|
||||||
|
|
||||||
|
- auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||||
|
+ auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||||
|
|
||||||
|
// notification node comes from sysrepo and sysrepo will free this; if not unlinked then envelope destructor would try to free this as well
|
||||||
|
notification.unlink();
|
||||||
|
diff --git a/src/sr/AllEvents.cpp b/src/sr/AllEvents.cpp
|
||||||
|
index 7b6226b..2202b36 100644
|
||||||
|
--- a/src/sr/AllEvents.cpp
|
||||||
|
+++ b/src/sr/AllEvents.cpp
|
||||||
|
@@ -110,10 +110,10 @@ sysrepo::ErrorCode AllEvents::onChange(sysrepo::Session session, const std::stri
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
- auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||||
|
+ auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||||
|
spdlog::info("JSON: {}", json);
|
||||||
|
spdlog::warn("FULL JSON: {}",
|
||||||
|
- *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||||
|
+ *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||||
|
change(module, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/sr/OpticalEvents.cpp b/src/sr/OpticalEvents.cpp
|
||||||
|
index 25906a8..3f78ddc 100644
|
||||||
|
--- a/src/sr/OpticalEvents.cpp
|
||||||
|
+++ b/src/sr/OpticalEvents.cpp
|
||||||
|
@@ -15,7 +15,7 @@
|
||||||
|
namespace {
|
||||||
|
std::string dumpDataFrom(sysrepo::Session session, const std::string& module)
|
||||||
|
{
|
||||||
|
- return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||||
|
+ return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/tests/event_watchers.cpp b/tests/event_watchers.cpp
|
||||||
|
index 26ba951..38ee7be 100644
|
||||||
|
--- a/tests/event_watchers.cpp
|
||||||
|
+++ b/tests/event_watchers.cpp
|
||||||
|
@@ -23,7 +23,7 @@ void datastoreChanges(auto session, auto& dsChangesMock, auto path)
|
||||||
|
|
||||||
|
void datastoreNewState(auto session, auto& dsChangesMock, auto path)
|
||||||
|
{
|
||||||
|
- dsChangesMock.contentAfterChange(session.getData(path)->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||||
|
+ dsChangesMock.contentAfterChange(session.getData(path)->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
index d42fa96..73b9fab 100644
|
||||||
|
--- a/tests/restconf-subscribed-notifications.cpp
|
||||||
|
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||||
|
@@ -92,7 +92,7 @@ EstablishSubscriptionResult establishSubscription(
|
||||||
|
data->unlinkWithSiblings();
|
||||||
|
envelope->insertChild(*data);
|
||||||
|
|
||||||
|
- auto body = *envelope->printStr(rpcEncoding, libyang::PrintFlags::WithSiblings);
|
||||||
|
+ auto body = *envelope->printStr(rpcEncoding, libyang::PrintFlags::Siblings);
|
||||||
|
auto resp = post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", requestHeaders, body);
|
||||||
|
REQUIRE(resp.equalStatusCodeAndHeaders(Response{200, expectedHeaders, ""}));
|
||||||
|
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
From ee0ce2ea31f3ecba4a4753a761df08f0698c2726 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Mon, 20 Oct 2025 11:47:26 +0200
|
||||||
|
Subject: [PATCH 1/9] CI: pin to libyang v3
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8978
|
||||||
|
Change-Id: I2521ad3a62d6197e757e636b88478deeb3bb5592
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
.zuul.yaml | 8 ++++----
|
||||||
|
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||||
|
index 7ada6f7..cef174f 100644
|
||||||
|
--- a/.zuul.yaml
|
||||||
|
+++ b/.zuul.yaml
|
||||||
|
@@ -4,9 +4,9 @@
|
||||||
|
- f38-gcc-cover:
|
||||||
|
required-projects:
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: devel
|
||||||
|
+ override-checkout: cesnet/2025-07-08
|
||||||
|
- name: github/sysrepo/sysrepo
|
||||||
|
- override-checkout: devel
|
||||||
|
+ override-checkout: cesnet/2025-06-02
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.4.8
|
||||||
|
- name: github/rollbear/trompeloeil
|
||||||
|
@@ -15,9 +15,9 @@
|
||||||
|
- f38-clang-asan-ubsan:
|
||||||
|
required-projects: &projects
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: devel
|
||||||
|
+ override-checkout: cesnet/2025-07-08
|
||||||
|
- name: github/sysrepo/sysrepo
|
||||||
|
- override-checkout: devel
|
||||||
|
+ override-checkout: cesnet/2025-06-02
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.4.11
|
||||||
|
- name: github/rollbear/trompeloeil
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
From e49463aba8b44b932fdd342d1c1d117e70414aba Mon Sep 17 00:00:00 2001
|
||||||
|
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
|
||||||
|
Date: Fri, 17 Oct 2025 18:08:47 +0200
|
||||||
|
Subject: [PATCH 2/9] fix: implicit dependency on optional
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
chrono imports optional in gcc >=13.
|
||||||
|
added the explicit include to compile in gcc <=12.
|
||||||
|
|
||||||
|
Change-Id: I18cc1a45ab522ee6738d4bc7080696cba8284903
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
include/sysrepo-cpp/Connection.hpp | 1 +
|
||||||
|
1 file changed, 1 insertion(+)
|
||||||
|
|
||||||
|
diff --git a/include/sysrepo-cpp/Connection.hpp b/include/sysrepo-cpp/Connection.hpp
|
||||||
|
index bf72bcd..a469a2c 100644
|
||||||
|
--- a/include/sysrepo-cpp/Connection.hpp
|
||||||
|
+++ b/include/sysrepo-cpp/Connection.hpp
|
||||||
|
@@ -7,6 +7,7 @@
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
+#include <optional>
|
||||||
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
From b8e349aacae4c7063517a1bc652e9e93f90e28ba Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Fri, 24 Oct 2025 17:05:52 +0200
|
||||||
|
Subject: [PATCH 3/9] fix #include for for our public headers
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Change-Id: Idcbfe38348e3ca2e4331fc1fad542ae759b8d0f1
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/utils/utils.cpp | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||||
|
index ff4a32c..c13b394 100644
|
||||||
|
--- a/src/utils/utils.cpp
|
||||||
|
+++ b/src/utils/utils.cpp
|
||||||
|
@@ -7,7 +7,7 @@
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sysrepo-cpp/Connection.hpp>
|
||||||
|
-#include "sysrepo-cpp/utils/exception.hpp"
|
||||||
|
+#include <sysrepo-cpp/utils/exception.hpp>
|
||||||
|
extern "C" {
|
||||||
|
#include <sysrepo.h>
|
||||||
|
}
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
From 753a3e6eb346f2a9f6263b2d9fcd1c6dbb508247 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Fri, 24 Oct 2025 17:06:56 +0200
|
||||||
|
Subject: [PATCH 4/9] reduce number of files which are named "utils.hpp"
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
We used to have three (one in publicheaders, one in src/utils/, one
|
||||||
|
below tests/). Let's cut that by one to increase our collective sanity.
|
||||||
|
|
||||||
|
Change-Id: I9e3d93c65dffb29ba18b3ae325ddd7712a8be7a4
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/Connection.cpp | 2 +-
|
||||||
|
src/Session.cpp | 2 +-
|
||||||
|
src/Subscription.cpp | 2 +-
|
||||||
|
src/utils/{utils.hpp => misc.hpp} | 0
|
||||||
|
src/utils/utils.cpp | 2 +-
|
||||||
|
5 files changed, 4 insertions(+), 4 deletions(-)
|
||||||
|
rename src/utils/{utils.hpp => misc.hpp} (100%)
|
||||||
|
|
||||||
|
diff --git a/src/Connection.cpp b/src/Connection.cpp
|
||||||
|
index 0297ebe..1717b9a 100644
|
||||||
|
--- a/src/Connection.cpp
|
||||||
|
+++ b/src/Connection.cpp
|
||||||
|
@@ -13,7 +13,7 @@ extern "C" {
|
||||||
|
#include <sysrepo-cpp/utils/exception.hpp>
|
||||||
|
#include "utils/enum.hpp"
|
||||||
|
#include "utils/exception.hpp"
|
||||||
|
-#include "utils/utils.hpp"
|
||||||
|
+#include "utils/misc.hpp"
|
||||||
|
|
||||||
|
namespace sysrepo {
|
||||||
|
|
||||||
|
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||||
|
index dfe1044..994ce87 100644
|
||||||
|
--- a/src/Session.cpp
|
||||||
|
+++ b/src/Session.cpp
|
||||||
|
@@ -21,7 +21,7 @@ extern "C" {
|
||||||
|
#include <utility>
|
||||||
|
#include "utils/enum.hpp"
|
||||||
|
#include "utils/exception.hpp"
|
||||||
|
-#include "utils/utils.hpp"
|
||||||
|
+#include "utils/misc.hpp"
|
||||||
|
|
||||||
|
using namespace std::string_literals;
|
||||||
|
namespace sysrepo {
|
||||||
|
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
|
||||||
|
index 303b54c..5bb724e 100644
|
||||||
|
--- a/src/Subscription.cpp
|
||||||
|
+++ b/src/Subscription.cpp
|
||||||
|
@@ -16,7 +16,7 @@ extern "C" {
|
||||||
|
}
|
||||||
|
#include "utils/enum.hpp"
|
||||||
|
#include "utils/exception.hpp"
|
||||||
|
-#include "utils/utils.hpp"
|
||||||
|
+#include "utils/misc.hpp"
|
||||||
|
|
||||||
|
namespace sysrepo {
|
||||||
|
/**
|
||||||
|
diff --git a/src/utils/utils.hpp b/src/utils/misc.hpp
|
||||||
|
similarity index 100%
|
||||||
|
rename from src/utils/utils.hpp
|
||||||
|
rename to src/utils/misc.hpp
|
||||||
|
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||||
|
index c13b394..2e8d8b4 100644
|
||||||
|
--- a/src/utils/utils.cpp
|
||||||
|
+++ b/src/utils/utils.cpp
|
||||||
|
@@ -12,7 +12,7 @@ extern "C" {
|
||||||
|
#include <sysrepo.h>
|
||||||
|
}
|
||||||
|
#include "enum.hpp"
|
||||||
|
-#include "utils.hpp"
|
||||||
|
+#include "misc.hpp"
|
||||||
|
|
||||||
|
namespace sysrepo {
|
||||||
|
/**
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
From dc118efa44b13508e1af47d924ad02ce58c58cea Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Fri, 24 Oct 2025 17:18:49 +0200
|
||||||
|
Subject: [PATCH 5/9] Fix a different function return type
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
The prototype that was specified in the header file did not match what
|
||||||
|
was used in the corresponding .cpp implementation. This has not been a
|
||||||
|
problem because that implementation .cpp file was not including the
|
||||||
|
actual header, so this was not detected. Then, at runtime, this was not
|
||||||
|
a problem due to the way how derived types are laid out in memory.
|
||||||
|
|
||||||
|
Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes
|
||||||
|
Change-Id: Ie4cf8c0e39e9c8486a6289c0328510ad6f7360fb
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/utils/utils.cpp | 7 ++++---
|
||||||
|
1 file changed, 4 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||||
|
index 2e8d8b4..61d0c7f 100644
|
||||||
|
--- a/src/utils/utils.cpp
|
||||||
|
+++ b/src/utils/utils.cpp
|
||||||
|
@@ -8,6 +8,7 @@
|
||||||
|
|
||||||
|
#include <sysrepo-cpp/Connection.hpp>
|
||||||
|
#include <sysrepo-cpp/utils/exception.hpp>
|
||||||
|
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||||
|
extern "C" {
|
||||||
|
#include <sysrepo.h>
|
||||||
|
}
|
||||||
|
@@ -78,7 +79,7 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional<FDHandli
|
||||||
|
* @see Session::operationalChanges()
|
||||||
|
* @see Session::dropForeignOperationalContent()
|
||||||
|
*/
|
||||||
|
-std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
|
||||||
|
+std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
|
||||||
|
{
|
||||||
|
auto discard = root.firstOpaqueSibling();
|
||||||
|
while (discard) {
|
||||||
|
@@ -97,11 +98,11 @@ std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, con
|
||||||
|
/**
|
||||||
|
* @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath
|
||||||
|
*/
|
||||||
|
-std::vector<libyang::DataNode> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||||
|
+std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||||
|
{
|
||||||
|
auto withSlash = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '/') ? xpathPrefix : xpathPrefix + '/';
|
||||||
|
auto withBracket = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '[') ? xpathPrefix : xpathPrefix + '[';
|
||||||
|
- std::vector<libyang::DataNode> res;
|
||||||
|
+ std::vector<libyang::DataNodeOpaque> res;
|
||||||
|
auto discard = root.firstOpaqueSibling();
|
||||||
|
while (discard) {
|
||||||
|
if (discard->name().matches("sysrepo", "discard-items")) {
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
From b943faea3cb00c5e86f21710647a2ca370e41f33 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
|
||||||
|
Date: Fri, 24 Oct 2025 13:45:00 +0200
|
||||||
|
Subject: [PATCH 6/9] fix: raw strings and macros break gcc < 13
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
see bug report:
|
||||||
|
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55971
|
||||||
|
|
||||||
|
Change-Id: I04afc2b3bee86a7e4b038b277aad0e3a150a1940
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
tests/subscriptions-dynamic.cpp | 21 ++++++++++++++-------
|
||||||
|
1 file changed, 14 insertions(+), 7 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||||
|
index 5ed173b..561df0c 100644
|
||||||
|
--- a/tests/subscriptions-dynamic.cpp
|
||||||
|
+++ b/tests/subscriptions-dynamic.cpp
|
||||||
|
@@ -39,21 +39,28 @@
|
||||||
|
REQUIRE(pipeStatus((SUBSCRIPTION).fd(), -1) == PipeStatus::DataReady); \
|
||||||
|
(SUBSCRIPTION).processEvent(cbNotif);
|
||||||
|
|
||||||
|
-#define SUBSCRIPTION_TERMINATED(SUBSCRIPTION) R"({
|
||||||
|
+auto SUBSCRIPTION_TERMINATED(const auto& SUBSCRIPTION)
|
||||||
|
+{
|
||||||
|
+ return R"({
|
||||||
|
"ietf-subscribed-notifications:subscription-terminated": {
|
||||||
|
- "id": )" + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(,
|
||||||
|
+ "id": )"
|
||||||
|
+ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(,
|
||||||
|
"reason": "no-such-subscription"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-)"
|
||||||
|
+)";
|
||||||
|
+};
|
||||||
|
|
||||||
|
-#define REPLAY_COMPLETED(SUBSCRIPTION) R"({
|
||||||
|
+auto REPLAY_COMPLETED(const auto& SUBSCRIPTION)
|
||||||
|
+{
|
||||||
|
+ return R"({
|
||||||
|
"ietf-subscribed-notifications:replay-completed": {
|
||||||
|
- "id": )" \
|
||||||
|
- + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(
|
||||||
|
+ "id": )"
|
||||||
|
+ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-)"
|
||||||
|
+)";
|
||||||
|
+};
|
||||||
|
|
||||||
|
#define REQUIRE_YANG_PUSH_UPDATE(SUBSCRIPTION, NOTIFICATION) \
|
||||||
|
TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate((SUBSCRIPTION).subscriptionId(), NOTIFICATION)).IN_SEQUENCE(seq);
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
From 5fd8cf4fb8db1d65b6a20d2777bad22647e7f5e0 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 23 Oct 2025 16:04:10 +0200
|
||||||
|
Subject: [PATCH 7/9] Port to libyang v4
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
I'm rather disappointed with the upstream changes that implement that
|
||||||
|
shared libyang "printed context" mapped to a fixed address, I think it's
|
||||||
|
effectively a step backwards. Upstream is happy with their solution, so
|
||||||
|
here we are.
|
||||||
|
|
||||||
|
Change-Id: I6b89422ec66bc694dc2c6ecd0d40aa1e7f76a741
|
||||||
|
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8984
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
.zuul.yaml | 8 +++---
|
||||||
|
CMakeLists.txt | 6 ++---
|
||||||
|
ci/build.sh | 4 +++
|
||||||
|
include/sysrepo-cpp/Enum.hpp | 18 ++++++++++---
|
||||||
|
include/sysrepo-cpp/utils/utils.hpp | 5 ++++
|
||||||
|
src/Connection.cpp | 3 +--
|
||||||
|
src/Session.cpp | 2 +-
|
||||||
|
src/utils/enum.hpp | 18 +++++++------
|
||||||
|
src/utils/utils.cpp | 18 +++++++++++++
|
||||||
|
tests/session.cpp | 39 +++++++++++++++++++++--------
|
||||||
|
10 files changed, 90 insertions(+), 31 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||||
|
index cef174f..7ada6f7 100644
|
||||||
|
--- a/.zuul.yaml
|
||||||
|
+++ b/.zuul.yaml
|
||||||
|
@@ -4,9 +4,9 @@
|
||||||
|
- f38-gcc-cover:
|
||||||
|
required-projects:
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: cesnet/2025-07-08
|
||||||
|
+ override-checkout: devel
|
||||||
|
- name: github/sysrepo/sysrepo
|
||||||
|
- override-checkout: cesnet/2025-06-02
|
||||||
|
+ override-checkout: devel
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.4.8
|
||||||
|
- name: github/rollbear/trompeloeil
|
||||||
|
@@ -15,9 +15,9 @@
|
||||||
|
- f38-clang-asan-ubsan:
|
||||||
|
required-projects: &projects
|
||||||
|
- name: github/CESNET/libyang
|
||||||
|
- override-checkout: cesnet/2025-07-08
|
||||||
|
+ override-checkout: devel
|
||||||
|
- name: github/sysrepo/sysrepo
|
||||||
|
- override-checkout: cesnet/2025-06-02
|
||||||
|
+ override-checkout: devel
|
||||||
|
- name: github/doctest/doctest
|
||||||
|
override-checkout: v2.4.11
|
||||||
|
- name: github/rollbear/trompeloeil
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index f2a9b09..49338dd 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -19,7 +19,7 @@ add_custom_target(sysrepo-cpp-version-cmake
|
||||||
|
cmake/ProjectGitVersionRunner.cmake
|
||||||
|
)
|
||||||
|
include(cmake/ProjectGitVersion.cmake)
|
||||||
|
-set(SYSREPO_CPP_PKG_VERSION "6")
|
||||||
|
+set(SYSREPO_CPP_PKG_VERSION "7")
|
||||||
|
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
|
||||||
|
|
||||||
|
find_package(Doxygen)
|
||||||
|
@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||||
|
option(WITH_EXAMPLES "Build examples" ON)
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||||
|
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.7.4 IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET)
|
||||||
|
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
diff --git a/ci/build.sh b/ci/build.sh
|
||||||
|
index 785a811..67bc7d3 100755
|
||||||
|
--- a/ci/build.sh
|
||||||
|
+++ b/ci/build.sh
|
||||||
|
@@ -47,6 +47,10 @@ if [[ $ZUUL_JOB_NAME =~ .*-tsan ]]; then
|
||||||
|
# Our TSAN does not have interceptors for a variety of "less common" functions such as pthread_mutex_clocklock.
|
||||||
|
# Disable all functions which are optional in sysrepo/libnetconf2/Netopeer2.
|
||||||
|
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DHAVE_PTHREAD_MUTEX_TIMEDLOCK=OFF -DHAVE_PTHREAD_MUTEX_CLOCKLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKRDLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKWRLOCK=OFF -DHAVE_PTHREAD_COND_CLOCKWAIT=OFF"
|
||||||
|
+
|
||||||
|
+ # This is currently broken on TSAN ("Failed to map the printed context (Operation not permitted).").
|
||||||
|
+ # Even if it wasn't broken, I want at least one build in the matrix with this (mis)feature disabled.
|
||||||
|
+ CMAKE_OPTIONS="${CMAKE_OPTIONS} -DPRINTED_CONTEXT_ADDRESS=0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $ZUUL_JOB_NAME =~ .*-cover.* ]]; then
|
||||||
|
diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp
|
||||||
|
index 48467c6..505797a 100644
|
||||||
|
--- a/include/sysrepo-cpp/Enum.hpp
|
||||||
|
+++ b/include/sysrepo-cpp/Enum.hpp
|
||||||
|
@@ -176,12 +176,24 @@ enum class NotificationType : uint32_t {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
- * Wraps `sr_conn_flag_e`.
|
||||||
|
+ * Wraps `sr_context_flag_t`.
|
||||||
|
+ */
|
||||||
|
+enum class ContextFlags : uint32_t {
|
||||||
|
+ Default = 0x00, /**< SR_CTX_DEFAULT */
|
||||||
|
+ NoPrinted = 0x01, /**< SR_CTX_NO_PRINTED */
|
||||||
|
+ LibYangPrivParsed = 0x02, /**< SR_CTX_SET_PRIV_PARSED */
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
+constexpr ContextFlags operator|(const ContextFlags a, const ContextFlags b)
|
||||||
|
+{
|
||||||
|
+ return implEnumBitOr(a, b);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/**
|
||||||
|
+ * Wraps `sr_conn_flag_t`.
|
||||||
|
*/
|
||||||
|
enum class ConnectionFlags : uint32_t {
|
||||||
|
Default = 0x00, /**< SR_CONN_DEFAULT */
|
||||||
|
- CacheRunning = 0x01, /**< SR_CONN_CACHE_RUNNING */
|
||||||
|
- LibYangPrivParsed = 0x02, /**< SR_CONN_CTX_SET_PRIV_PARSED */
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr ConnectionFlags operator|(const ConnectionFlags a, const ConnectionFlags b)
|
||||||
|
diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp
|
||||||
|
index 64b1c2e..6f2446f 100644
|
||||||
|
--- a/include/sysrepo-cpp/utils/utils.hpp
|
||||||
|
+++ b/include/sysrepo-cpp/utils/utils.hpp
|
||||||
|
@@ -12,6 +12,11 @@
|
||||||
|
namespace sysrepo {
|
||||||
|
Session wrapUnmanagedSession(sr_session_ctx_s* session);
|
||||||
|
void setLogLevelStderr(const LogLevel);
|
||||||
|
+enum class GlobalContextEffect {
|
||||||
|
+ Lazy, /**< Set these flags the next time the shared context is applied */
|
||||||
|
+ Immediate, /**< Apply immediately */
|
||||||
|
+};
|
||||||
|
+ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when);
|
||||||
|
std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath);
|
||||||
|
std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix);
|
||||||
|
void unlinkFromForest(std::optional<libyang::DataNode>& root, libyang::DataNode node);
|
||||||
|
diff --git a/src/Connection.cpp b/src/Connection.cpp
|
||||||
|
index 1717b9a..96e340c 100644
|
||||||
|
--- a/src/Connection.cpp
|
||||||
|
+++ b/src/Connection.cpp
|
||||||
|
@@ -26,7 +26,7 @@ Connection::Connection(const ConnectionFlags options)
|
||||||
|
: ctx(nullptr)
|
||||||
|
{
|
||||||
|
sr_conn_ctx_t* ctx;
|
||||||
|
- auto res = sr_connect(static_cast<sr_conn_options_t>(options), &ctx);
|
||||||
|
+ auto res = sr_connect(static_cast<sr_conn_flag_t>(options), &ctx);
|
||||||
|
|
||||||
|
throwIfError(res, "Couldn't connect to sysrepo");
|
||||||
|
this->ctx = std::shared_ptr<sr_conn_ctx_t>(ctx, sr_disconnect);
|
||||||
|
@@ -96,5 +96,4 @@ uint32_t Connection::getId() const
|
||||||
|
{
|
||||||
|
return sr_get_cid(ctx.get());
|
||||||
|
}
|
||||||
|
-
|
||||||
|
}
|
||||||
|
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||||
|
index 994ce87..95b8f24 100644
|
||||||
|
--- a/src/Session.cpp
|
||||||
|
+++ b/src/Session.cpp
|
||||||
|
@@ -241,7 +241,7 @@ std::optional<libyang::DataNode> Session::operationalChanges(const std::optional
|
||||||
|
void Session::discardOperationalChanges(const std::optional<std::string>& moduleName, std::chrono::milliseconds timeout)
|
||||||
|
{
|
||||||
|
SYSREPO_CPP_SESSION_MTX;
|
||||||
|
- auto res = sr_discard_oper_changes(nullptr, m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count());
|
||||||
|
+ auto res = sr_discard_oper_changes(m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count());
|
||||||
|
throwIfError(res, "Session::discardOoperationalChanges: Couldn't discard "s + (moduleName ? "for module \"" + *moduleName + "\"" : "globally"s), m_sess.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
|
||||||
|
index 2d3a298..e917b21 100644
|
||||||
|
--- a/src/utils/enum.hpp
|
||||||
|
+++ b/src/utils/enum.hpp
|
||||||
|
@@ -151,13 +151,16 @@ static_assert(toNotificationType(SR_EV_NOTIF_MODIFIED) == NotificationType::Modi
|
||||||
|
static_assert(toNotificationType(SR_EV_NOTIF_SUSPENDED) == NotificationType::Suspended);
|
||||||
|
static_assert(toNotificationType(SR_EV_NOTIF_RESUMED) == NotificationType::Resumed);
|
||||||
|
|
||||||
|
-static_assert(std::is_same_v<sr_conn_options_t, std::underlying_type_t<ConnectionFlags>>);
|
||||||
|
+static_assert(std::is_same_v<std::underlying_type_t<sr_conn_flag_t>, std::underlying_type_t<ConnectionFlags>>);
|
||||||
|
static_assert(static_cast<ConnectionFlags>(SR_CONN_DEFAULT) == ConnectionFlags::Default);
|
||||||
|
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CACHE_RUNNING) == ConnectionFlags::CacheRunning);
|
||||||
|
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CTX_SET_PRIV_PARSED) == ConnectionFlags::LibYangPrivParsed);
|
||||||
|
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CACHE_RUNNING | SR_CONN_CTX_SET_PRIV_PARSED) == (ConnectionFlags::CacheRunning | ConnectionFlags::LibYangPrivParsed));
|
||||||
|
|
||||||
|
-static_assert(std::is_same_v<sr_get_options_t, std::underlying_type_t<GetOptions>>);
|
||||||
|
+static_assert(std::is_same_v<std::underlying_type_t<sr_context_flag_t>, std::underlying_type_t<ContextFlags>>);
|
||||||
|
+static_assert(static_cast<ContextFlags>(SR_CTX_DEFAULT) == ContextFlags::Default);
|
||||||
|
+static_assert(static_cast<ContextFlags>(SR_CTX_SET_PRIV_PARSED) == ContextFlags::LibYangPrivParsed);
|
||||||
|
+static_assert(static_cast<ContextFlags>(SR_CTX_NO_PRINTED) == ContextFlags::NoPrinted);
|
||||||
|
+static_assert(static_cast<ContextFlags>(SR_CTX_SET_PRIV_PARSED | SR_CTX_NO_PRINTED) == (ContextFlags::LibYangPrivParsed | ContextFlags::NoPrinted));
|
||||||
|
+
|
||||||
|
+// upstream project provides no dedicated type for these
|
||||||
|
static_assert(static_cast<GetOptions>(SR_OPER_DEFAULT) == GetOptions::Default);
|
||||||
|
static_assert(static_cast<GetOptions>(SR_OPER_NO_STATE) == GetOptions::OperNoState);
|
||||||
|
static_assert(static_cast<GetOptions>(SR_OPER_NO_CONFIG) == GetOptions::OperNoConfig);
|
||||||
|
@@ -168,9 +171,10 @@ static_assert(static_cast<GetOptions>(SR_OPER_NO_POLL_CACHED) == GetOptions::Ope
|
||||||
|
static_assert(static_cast<GetOptions>(SR_OPER_NO_RUN_CACHED) == GetOptions::OperNoRunningCached);
|
||||||
|
static_assert(static_cast<GetOptions>(SR_GET_NO_FILTER) == GetOptions::NoFilter);
|
||||||
|
|
||||||
|
-constexpr sr_get_options_t toGetOptions(const GetOptions opts)
|
||||||
|
+// upstream project provides no dedicated type for these
|
||||||
|
+constexpr uint32_t toGetOptions(const GetOptions opts)
|
||||||
|
{
|
||||||
|
- return static_cast<sr_get_options_t>(opts);
|
||||||
|
+ return static_cast<uint32_t>(opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create);
|
||||||
|
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||||
|
index 61d0c7f..dbbc5ce 100644
|
||||||
|
--- a/src/utils/utils.cpp
|
||||||
|
+++ b/src/utils/utils.cpp
|
||||||
|
@@ -13,6 +13,7 @@ extern "C" {
|
||||||
|
#include <sysrepo.h>
|
||||||
|
}
|
||||||
|
#include "enum.hpp"
|
||||||
|
+#include "exception.hpp"
|
||||||
|
#include "misc.hpp"
|
||||||
|
|
||||||
|
namespace sysrepo {
|
||||||
|
@@ -37,6 +38,23 @@ void setLogLevelStderr(const LogLevel level)
|
||||||
|
sr_log_stderr(toLogLevel(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * @brief Set global sysrepo-level context options
|
||||||
|
+ *
|
||||||
|
+ * Be advised of consequences of manipulating a shared global state, especially when using multiple connections.
|
||||||
|
+ *
|
||||||
|
+ * Wraps `sr_context_options`.
|
||||||
|
+ */
|
||||||
|
+ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when)
|
||||||
|
+{
|
||||||
|
+ uint32_t old;
|
||||||
|
+ throwIfError(
|
||||||
|
+ sr_context_options(static_cast<uint32_t>(flags), when == GlobalContextEffect::Immediate, &old),
|
||||||
|
+ "sr_context_options failed",
|
||||||
|
+ nullptr);
|
||||||
|
+ return static_cast<ContextFlags>(old);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
std::timespec toTimespec(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> tp)
|
||||||
|
{
|
||||||
|
// https://embeddedartistry.com/blog/2019/01/31/converting-between-timespec-stdchrono#-std-chrono-time_point-to-timespec-
|
||||||
|
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||||
|
index 47b5631..a709a09 100644
|
||||||
|
--- a/tests/session.cpp
|
||||||
|
+++ b/tests/session.cpp
|
||||||
|
@@ -627,17 +627,34 @@ TEST_CASE("session")
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("libyang context flags")
|
||||||
|
{
|
||||||
|
- sess.setItem("/test_module:popelnice/s", "666");
|
||||||
|
- REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666");
|
||||||
|
- // Parsed type info is not preserved by libyang unless its context is constructed with a flag,
|
||||||
|
- // and that flag is not used by sysrepo by default...
|
||||||
|
- REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable);
|
||||||
|
-
|
||||||
|
- // ...unless we pass that flag explicitly as a parameter to the connection.
|
||||||
|
- auto sess2 = sysrepo::Connection{sysrepo::ConnectionFlags::LibYangPrivParsed}.sessionStart();
|
||||||
|
- sess2.setItem("/test_module:popelnice/s", "333");
|
||||||
|
- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333");
|
||||||
|
- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10);
|
||||||
|
+ DOCTEST_SUBCASE("default")
|
||||||
|
+ {
|
||||||
|
+ sess.setItem("/test_module:popelnice/s", "666");
|
||||||
|
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666");
|
||||||
|
+ // Parsed type info is not preserved by libyang unless its context is constructed with a flag,
|
||||||
|
+ // and that flag is not used by sysrepo by default.
|
||||||
|
+ REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable);
|
||||||
|
+
|
||||||
|
+ // pending changes hold the context lock, so we cannot change the global context options
|
||||||
|
+ REQUIRE_THROWS_WITH_AS(
|
||||||
|
+ sysrepo::setGlobalContextOptions(
|
||||||
|
+ sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted,
|
||||||
|
+ sysrepo::GlobalContextEffect::Immediate),
|
||||||
|
+ "sr_context_options failed: SR_ERR_TIME_OUT",
|
||||||
|
+ sysrepo::ErrorWithCode);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Since this is actually a shared global state, changing it is "tricky". The best bet is to actually
|
||||||
|
+ // ensure that there are no other connections (which we cannot do here, we already have a connection opened
|
||||||
|
+ // from the common code at the very top of this test case), and especially no internal sysrepo-level locks
|
||||||
|
+ // on the shared libyang context. For example, no uncommited changes -- hence a new subcase.
|
||||||
|
+ DOCTEST_SUBCASE("Connection::setGlobalContextOptions LibYangPrivParsed")
|
||||||
|
+ {
|
||||||
|
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||||
|
+ sess.setItem("/test_module:popelnice/s", "333");
|
||||||
|
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333");
|
||||||
|
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("replay support")
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
From 6e9dbd8513ce75e51f2f714df69c08af5b556d1d Mon Sep 17 00:00:00 2001
|
||||||
|
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
|
||||||
|
Date: Tue, 28 Oct 2025 13:24:13 +0100
|
||||||
|
Subject: [PATCH 8/9] fix: onOperGet unconsistent with C library.
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Fixes: https://github.com/sysrepo/sysrepo-cpp/issues/29
|
||||||
|
Change-Id: I5a21069889c59466df8e9457c3a316b58e2571bb
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
include/sysrepo-cpp/Session.hpp | 2 +-
|
||||||
|
include/sysrepo-cpp/Subscription.hpp | 2 +-
|
||||||
|
src/Session.cpp | 4 ++--
|
||||||
|
src/Subscription.cpp | 4 ++--
|
||||||
|
4 files changed, 6 insertions(+), 6 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||||
|
index aae7068..3af4b10 100644
|
||||||
|
--- a/include/sysrepo-cpp/Session.hpp
|
||||||
|
+++ b/include/sysrepo-cpp/Session.hpp
|
||||||
|
@@ -127,7 +127,7 @@ public:
|
||||||
|
[[nodiscard]] Subscription onOperGet(
|
||||||
|
const std::string& moduleName,
|
||||||
|
OperGetCb cb,
|
||||||
|
- const std::optional<std::string>& xpath = std::nullopt,
|
||||||
|
+ const std::string& path,
|
||||||
|
const SubscribeOptions opts = SubscribeOptions::Default,
|
||||||
|
ExceptionHandler handler = nullptr,
|
||||||
|
const std::optional<FDHandling>& callbacks = std::nullopt);
|
||||||
|
diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp
|
||||||
|
index c8f3ff8..14ecce4 100644
|
||||||
|
--- a/include/sysrepo-cpp/Subscription.hpp
|
||||||
|
+++ b/include/sysrepo-cpp/Subscription.hpp
|
||||||
|
@@ -32,7 +32,7 @@ public:
|
||||||
|
Subscription& operator=(Subscription&&) noexcept;
|
||||||
|
|
||||||
|
void onModuleChange(const std::string& moduleName, ModuleChangeCb cb, const std::optional<std::string>& xpath = std::nullopt, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||||
|
- void onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional<std::string>& xpath, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||||
|
+ void onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||||
|
void onRPCAction(const std::string& xpath, RpcActionCb cb, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||||
|
void onNotification(
|
||||||
|
const std::string& moduleName,
|
||||||
|
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||||
|
index 95b8f24..6f0327d 100644
|
||||||
|
--- a/src/Session.cpp
|
||||||
|
+++ b/src/Session.cpp
|
||||||
|
@@ -505,14 +505,14 @@ Subscription Session::onModuleChange(
|
||||||
|
Subscription Session::onOperGet(
|
||||||
|
const std::string& moduleName,
|
||||||
|
OperGetCb cb,
|
||||||
|
- const std::optional<std::string>& xpath,
|
||||||
|
+ const std::string& path,
|
||||||
|
const SubscribeOptions opts,
|
||||||
|
ExceptionHandler handler,
|
||||||
|
const std::optional<FDHandling>& callbacks)
|
||||||
|
{
|
||||||
|
checkNoThreadFlag(opts, callbacks);
|
||||||
|
auto sub = Subscription{*this, handler, callbacks};
|
||||||
|
- sub.onOperGet(moduleName, cb, xpath, opts);
|
||||||
|
+ sub.onOperGet(moduleName, cb, path, opts);
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
|
||||||
|
index 5bb724e..dca1176 100644
|
||||||
|
--- a/src/Subscription.cpp
|
||||||
|
+++ b/src/Subscription.cpp
|
||||||
|
@@ -215,14 +215,14 @@ void Subscription::onModuleChange(const std::string& moduleName, ModuleChangeCb
|
||||||
|
* @param xpath XPath that identifies which data this subscription is able to provide.
|
||||||
|
* @param opts Options further changing the behavior of this method.
|
||||||
|
*/
|
||||||
|
-void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional<std::string>& xpath, const SubscribeOptions opts)
|
||||||
|
+void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts)
|
||||||
|
{
|
||||||
|
SYSREPO_CPP_SESSION_MTX_OF(m_sess);
|
||||||
|
checkNoThreadFlag(opts, m_customEventLoopCbs);
|
||||||
|
|
||||||
|
auto& privRef = m_operGetCbs.emplace_back(PrivData{cb, m_exceptionHandler.get()});
|
||||||
|
sr_subscription_ctx_s* ctx = m_sub.get();
|
||||||
|
- auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), xpath ? xpath->c_str() : nullptr, operGetItemsCb, reinterpret_cast<void*>(&privRef), toSubscribeOptions(opts), &ctx);
|
||||||
|
+ auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), path.c_str(), operGetItemsCb, reinterpret_cast<void*>(&privRef), toSubscribeOptions(opts), &ctx);
|
||||||
|
throwIfError(res, "Couldn't create operational get items subscription", m_sess.m_sess.get());
|
||||||
|
|
||||||
|
saveContext(ctx);
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
From 60d729e4791e5447cf0d969be6da9f8f1878af64 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||||
|
Date: Thu, 20 Nov 2025 15:38:04 +0100
|
||||||
|
Subject: [PATCH 9/9] Update to libyang v4.2
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/9009
|
||||||
|
Change-Id: Ia4c53dba0befa8dade38e728489edc6473aa5f4f
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 4 ++--
|
||||||
|
tests/session.cpp | 14 +++++++-------
|
||||||
|
tests/subscriptions-dynamic.cpp | 4 ++--
|
||||||
|
3 files changed, 11 insertions(+), 11 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 49338dd..8234464 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||||
|
option(WITH_EXAMPLES "Build examples" ON)
|
||||||
|
|
||||||
|
find_package(PkgConfig)
|
||||||
|
-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET)
|
||||||
|
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=6 IMPORTED_TARGET)
|
||||||
|
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.8 IMPORTED_TARGET)
|
||||||
|
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||||
|
index a709a09..0e92e65 100644
|
||||||
|
--- a/tests/session.cpp
|
||||||
|
+++ b/tests/session.cpp
|
||||||
|
@@ -105,7 +105,7 @@ TEST_CASE("session")
|
||||||
|
|
||||||
|
auto data = sess.getData("/test_module:popelnice", 0);
|
||||||
|
REQUIRE(data);
|
||||||
|
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"test_module:popelnice": {
|
||||||
|
"content": {
|
||||||
|
"trash": [
|
||||||
|
@@ -126,14 +126,14 @@ TEST_CASE("session")
|
||||||
|
|
||||||
|
data = sess.getData("/test_module:popelnice", 1);
|
||||||
|
REQUIRE(data);
|
||||||
|
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"test_module:popelnice": {}
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
|
||||||
|
data = sess.getData("/test_module:popelnice", 2);
|
||||||
|
REQUIRE(data);
|
||||||
|
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"test_module:popelnice": {
|
||||||
|
"content": {}
|
||||||
|
}
|
||||||
|
@@ -143,7 +143,7 @@ TEST_CASE("session")
|
||||||
|
// If a list should be returned, its keys are always returned as well.
|
||||||
|
data = sess.getData("/test_module:popelnice", 3);
|
||||||
|
REQUIRE(data);
|
||||||
|
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"test_module:popelnice": {
|
||||||
|
"content": {
|
||||||
|
"trash": [
|
||||||
|
@@ -161,7 +161,7 @@ TEST_CASE("session")
|
||||||
|
|
||||||
|
data = sess.getData("/test_module:popelnice", 4);
|
||||||
|
REQUIRE(data);
|
||||||
|
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||||
|
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||||
|
"test_module:popelnice": {
|
||||||
|
"content": {
|
||||||
|
"trash": [
|
||||||
|
@@ -497,7 +497,7 @@ TEST_CASE("session")
|
||||||
|
{
|
||||||
|
sess.switchDatastore(sysrepo::Datastore::FactoryDefault);
|
||||||
|
auto data = sess.getData("/*");
|
||||||
|
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == "{\n\n}\n");
|
||||||
|
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == "{\n\n}\n");
|
||||||
|
REQUIRE_THROWS_AS(sess.setItem(leaf, "123"), sysrepo::ErrorWithCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -622,7 +622,7 @@ TEST_CASE("session")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the original tree is not corrupted
|
||||||
|
- REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) != "");
|
||||||
|
+ REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) != "");
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_SUBCASE("libyang context flags")
|
||||||
|
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||||
|
index 561df0c..0d4ce7c 100644
|
||||||
|
--- a/tests/subscriptions-dynamic.cpp
|
||||||
|
+++ b/tests/subscriptions-dynamic.cpp
|
||||||
|
@@ -140,12 +140,12 @@ TEST_CASE("Dynamic subscriptions")
|
||||||
|
const auto subId = std::get<uint32_t>(idNode->asTerm().value());
|
||||||
|
idNode->unlink();
|
||||||
|
|
||||||
|
- rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||||
|
+ rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||||
|
};
|
||||||
|
|
||||||
|
auto cbNotif = [&](const std::optional<libyang::DataNode>& tree, auto) {
|
||||||
|
REQUIRE(tree);
|
||||||
|
- rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||||
|
+ rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||||
|
};
|
||||||
|
|
||||||
|
// write some initial data
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -1,6 +1,77 @@
|
|||||||
diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
diff --git a/lib/northbound.c b/lib/northbound.c
|
||||||
--- frr-9.1.3.orig/lib/yang.c 2024-12-27 22:06:42.000000000 +0100
|
index 6ff5c24bd1..c6cca523b2 100644
|
||||||
+++ frr-9.1.3/lib/yang.c 2025-03-04 12:23:06.723640114 +0100
|
--- a/lib/northbound.c
|
||||||
|
+++ b/lib/northbound.c
|
||||||
|
@@ -581,7 +581,7 @@ void nb_config_diff(const struct nb_config *config1,
|
||||||
|
char *s;
|
||||||
|
|
||||||
|
if (!lyd_print_mem(&s, diff, LYD_JSON,
|
||||||
|
- LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)) {
|
||||||
|
+ LYD_PRINT_SIBLINGS | LYD_PRINT_WD_ALL)) {
|
||||||
|
zlog_debug("%s: %s", __func__, s);
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
@@ -2283,7 +2283,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
|
||||||
|
if (sleaf->when)
|
||||||
|
return true;
|
||||||
|
if (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE)
|
||||||
|
- || sleaf->dflt)
|
||||||
|
+ || sleaf->dflt.str)
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case LYS_CONTAINER:
|
||||||
|
diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
|
||||||
|
index 8003679ed5..0997b5f8c5 100644
|
||||||
|
--- a/lib/northbound_cli.c
|
||||||
|
+++ b/lib/northbound_cli.c
|
||||||
|
@@ -634,7 +634,7 @@ static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
|
||||||
|
return CMD_WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
- SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
|
||||||
|
+ SET_FLAG(options, LYD_PRINT_SIBLINGS);
|
||||||
|
if (with_defaults)
|
||||||
|
SET_FLAG(options, LYD_PRINT_WD_ALL);
|
||||||
|
else
|
||||||
|
@@ -1443,7 +1443,7 @@ DEFPY (show_yang_operational_data,
|
||||||
|
struct ly_ctx *ly_ctx;
|
||||||
|
struct lyd_node *dnode;
|
||||||
|
char *strp;
|
||||||
|
- uint32_t print_options = LYD_PRINT_WITHSIBLINGS;
|
||||||
|
+ uint32_t print_options = LYD_PRINT_SIBLINGS;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (xml)
|
||||||
|
diff --git a/lib/northbound_db.c b/lib/northbound_db.c
|
||||||
|
index 74abcde955..7d51f39291 100644
|
||||||
|
--- a/lib/northbound_db.c
|
||||||
|
+++ b/lib/northbound_db.c
|
||||||
|
@@ -79,7 +79,7 @@ int nb_db_transaction_save(const struct nb_transaction *transaction,
|
||||||
|
* values too, as this covers the case where defaults may change.
|
||||||
|
*/
|
||||||
|
if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
|
||||||
|
- LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
|
||||||
|
+ LYD_PRINT_SIBLINGS | LYD_PRINT_WD_ALL)
|
||||||
|
!= 0)
|
||||||
|
goto exit;
|
||||||
|
|
||||||
|
diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp
|
||||||
|
index 6c33351cef..b3e14828f3 100644
|
||||||
|
--- a/lib/northbound_grpc.cpp
|
||||||
|
+++ b/lib/northbound_grpc.cpp
|
||||||
|
@@ -370,7 +370,7 @@ static LY_ERR data_tree_from_dnode(frr::DataTree *dt,
|
||||||
|
char *strp;
|
||||||
|
int options = 0;
|
||||||
|
|
||||||
|
- SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
|
||||||
|
+ SET_FLAG(options, LYD_PRINT_SIBLINGS);
|
||||||
|
if (with_defaults)
|
||||||
|
SET_FLAG(options, LYD_PRINT_WD_ALL);
|
||||||
|
else
|
||||||
|
diff --git a/lib/yang.c b/lib/yang.c
|
||||||
|
index 4dd8654217..44563d4922 100644
|
||||||
|
--- a/lib/yang.c
|
||||||
|
+++ b/lib/yang.c
|
||||||
@@ -10,11 +10,23 @@
|
@@ -10,11 +10,23 @@
|
||||||
#include "lib_errors.h"
|
#include "lib_errors.h"
|
||||||
#include "yang.h"
|
#include "yang.h"
|
||||||
@@ -25,7 +96,18 @@ diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
|||||||
/* libyang container. */
|
/* libyang container. */
|
||||||
struct ly_ctx *ly_native_ctx;
|
struct ly_ctx *ly_native_ctx;
|
||||||
|
|
||||||
@@ -657,7 +669,12 @@
|
@@ -329,9 +341,7 @@ const char *yang_snode_get_default(const struct lysc_node *snode)
|
||||||
|
switch (snode->nodetype) {
|
||||||
|
case LYS_LEAF:
|
||||||
|
sleaf = (const struct lysc_node_leaf *)snode;
|
||||||
|
- return sleaf->dflt ? lyd_value_get_canonical(sleaf->module->ctx,
|
||||||
|
- sleaf->dflt)
|
||||||
|
- : NULL;
|
||||||
|
+ return sleaf->dflt.str;
|
||||||
|
case LYS_LEAFLIST:
|
||||||
|
/* TODO: check leaf-list default values */
|
||||||
|
return NULL;
|
||||||
|
@@ -657,7 +667,12 @@ struct yang_data *yang_data_list_find(const struct list *list,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make libyang log its errors using FRR logging infrastructure. */
|
/* Make libyang log its errors using FRR logging infrastructure. */
|
||||||
@@ -39,7 +121,7 @@ diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
|||||||
{
|
{
|
||||||
int priority = LOG_ERR;
|
int priority = LOG_ERR;
|
||||||
|
|
||||||
@@ -674,8 +691,14 @@
|
@@ -674,8 +689,14 @@ static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +138,7 @@ diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
|||||||
else
|
else
|
||||||
zlog(priority, "libyang: %s", msg);
|
zlog(priority, "libyang: %s", msg);
|
||||||
}
|
}
|
||||||
@@ -752,7 +775,7 @@
|
@@ -752,7 +773,7 @@ struct ly_ctx *yang_ctx_new_setup(bool embedded_modules, bool explicit_compile)
|
||||||
void yang_init(bool embedded_modules, bool defer_compile)
|
void yang_init(bool embedded_modules, bool defer_compile)
|
||||||
{
|
{
|
||||||
/* Initialize libyang global parameters that affect all containers. */
|
/* Initialize libyang global parameters that affect all containers. */
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
diff --git a/src/server_config.c b/src/server_config.c
|
||||||
|
index 48d362e..dc9f59d 100644
|
||||||
|
--- a/src/server_config.c
|
||||||
|
+++ b/src/server_config.c
|
||||||
|
@@ -4151,6 +4151,8 @@ config_asymmetric_key_certs(const struct lyd_node *node, enum nc_operation paren
|
||||||
|
struct lyd_node *n;
|
||||||
|
enum nc_operation op;
|
||||||
|
|
||||||
|
+ if (!node)
|
||||||
|
+ return 0;
|
||||||
|
NC_NODE_GET_OP(node, parent_op, &op);
|
||||||
|
|
||||||
|
/* configure all certificates */
|
||||||
|
@@ -4225,7 +4227,7 @@ config_asymmetric_key(const struct lyd_node *node, enum nc_operation parent_op,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* config asymmetric key certificates */
|
||||||
|
- NC_CHECK_RET(nc_lyd_find_child(node, "certificates", 1, &n));
|
||||||
|
+ NC_CHECK_RET(nc_lyd_find_child(node, "certificates", 0, &n));
|
||||||
|
NC_CHECK_RET(config_asymmetric_key_certs(n, op, entry));
|
||||||
|
|
||||||
|
/* config generate csr */
|
||||||
+741
@@ -0,0 +1,741 @@
|
|||||||
|
From 545473073552cd4cf5e0fc839e7170d4678ef646 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Wed, 10 Dec 2025 09:52:13 +0100
|
||||||
|
Subject: [PATCH 1/6] plugins ext BUGFIX printed context align all sizes and
|
||||||
|
mem
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Refs #2455
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/plugins_exts.h | 17 +++-
|
||||||
|
src/plugins_exts/metadata.c | 4 +-
|
||||||
|
src/plugins_exts/schema_mount.c | 24 +++---
|
||||||
|
src/plugins_exts/structure.c | 8 +-
|
||||||
|
src/printer_context.c | 148 +++++++++++++++-----------------
|
||||||
|
5 files changed, 100 insertions(+), 101 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/plugins_exts.h b/src/plugins_exts.h
|
||||||
|
index f260ff787..e912fe908 100644
|
||||||
|
--- a/src/plugins_exts.h
|
||||||
|
+++ b/src/plugins_exts.h
|
||||||
|
@@ -953,13 +953,26 @@ LIBYANG_API_DECL void lyplg_ext_cfree_instance_substatements(const struct ly_ctx
|
||||||
|
* compiled size
|
||||||
|
*/
|
||||||
|
|
||||||
|
+/**
|
||||||
|
+ * @brief Alignment of all the printed data, ensures all memory access is aligned to this number (B)
|
||||||
|
+ */
|
||||||
|
+#define LY_CTXP_MEM_ALIGN 8
|
||||||
|
+
|
||||||
|
+/**
|
||||||
|
+ * @brief Adjust data size to an aligned size to make sure the following data is aligned.
|
||||||
|
+ *
|
||||||
|
+ * @param[in,out] SIZE Data size that is adjusted.
|
||||||
|
+ */
|
||||||
|
+#define LY_CTXP_MEM_SIZE(SIZE) ((SIZE) + ((~(SIZE) + 1) & (LY_CTXP_MEM_ALIGN - 1)))
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* @brief Callback to return the size of the custom compiled structure and substmts array. If there are none, do not
|
||||||
|
* define this callback.
|
||||||
|
*
|
||||||
|
* @param[in] ext Compiled extension structure.
|
||||||
|
* @param[in,out] addr_ht Hash table with addresses of shared structures that were already accounted for, can be added to.
|
||||||
|
- * @return Total size of the custom compiled structures and the substmts array;
|
||||||
|
+ * @return Total size of the custom compiled structures and the substmts array. Always use ::LY_CTXP_MEM_SIZE when
|
||||||
|
+ * adding to the size for correct alignment.
|
||||||
|
* @return -1 on error.
|
||||||
|
*/
|
||||||
|
typedef int (*lyplg_ext_compiled_size_clb)(const struct lysc_ext_instance *ext, struct ly_ht *addr_ht);
|
||||||
|
@@ -984,7 +997,7 @@ LIBYANG_API_DECL int lyplg_ext_compiled_stmts_storage_size(const struct lysc_ext
|
||||||
|
* @param[in] orig_ext Compiled extension structure to print.
|
||||||
|
* @param[in,out] ext Printed extension structure to modify.
|
||||||
|
* @param[in,out] mem Memory chunk of the size returned by ::lyplg_ext_compiled_size_clb() to print into, is moved
|
||||||
|
- * after all the printed data.
|
||||||
|
+ * after all the printed data. Always use ::LY_CTXP_MEM_SIZE when moving @p mem for correct alignment.
|
||||||
|
* @param[in,out] ptr_set Set with pointers to set to printed addresses.
|
||||||
|
* @param[in,out] addr_ht Hash table with pairs of addresses of shared structures to be printed and their printed
|
||||||
|
* addresses, can be added to.
|
||||||
|
diff --git a/src/plugins_exts/metadata.c b/src/plugins_exts/metadata.c
|
||||||
|
index d5492839e..c73c8f9a8 100644
|
||||||
|
--- a/src/plugins_exts/metadata.c
|
||||||
|
+++ b/src/plugins_exts/metadata.c
|
||||||
|
@@ -220,7 +220,7 @@ annotation_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *addr
|
||||||
|
{
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
- size += sizeof(struct lysc_ext_metadata);
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(sizeof(struct lysc_ext_metadata));
|
||||||
|
size += lyplg_ext_compiled_stmts_storage_size(ext->substmts, addr_ht);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
@@ -233,7 +233,7 @@ annotation_compiled_print(const struct lysc_ext_instance *orig_ext, struct lysc_
|
||||||
|
struct lysc_ext_metadata *ann_cdata;
|
||||||
|
|
||||||
|
ann_cdata = ext->compiled = *mem;
|
||||||
|
- *mem = (char *)*mem + sizeof *ann_cdata;
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *ann_cdata);
|
||||||
|
memset(ann_cdata, 0, sizeof *ann_cdata);
|
||||||
|
|
||||||
|
ext->substmts[1].storage_p = (void **)&ann_cdata->units;
|
||||||
|
diff --git a/src/plugins_exts/schema_mount.c b/src/plugins_exts/schema_mount.c
|
||||||
|
index df736e5ad..3dd23f992 100644
|
||||||
|
--- a/src/plugins_exts/schema_mount.c
|
||||||
|
+++ b/src/plugins_exts/schema_mount.c
|
||||||
|
@@ -79,8 +79,6 @@ struct sprinter_tree_priv {
|
||||||
|
lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EINT, "Internal error (%s:%d).", __FILE__, __LINE__); \
|
||||||
|
return LY_EINT
|
||||||
|
|
||||||
|
-#define CTXP_MEM_SIZE(SIZE) ((SIZE) + ((~(SIZE) + 1) & (8 - 1)))
|
||||||
|
-
|
||||||
|
/**
|
||||||
|
* @brief Check if given mount point is unique among its siblings
|
||||||
|
*
|
||||||
|
@@ -1468,7 +1466,7 @@ schema_mount_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *ad
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
- size += CTXP_MEM_SIZE(sizeof *sm_data);
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(sizeof *sm_data);
|
||||||
|
|
||||||
|
/* ht addr check, make sure the shared context is stored only once */
|
||||||
|
hash = lyht_hash((const char *)&sm_data->shared, sizeof sm_data->shared);
|
||||||
|
@@ -1476,12 +1474,12 @@ schema_mount_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *ad
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
- size += CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||||
|
- size += CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||||
|
for (i = 0; i < sm_data->shared->schema_count; ++i) {
|
||||||
|
- size += CTXP_MEM_SIZE(ly_ctx_compiled_size(sm_data->shared->schemas[i].ctx));
|
||||||
|
- size += CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||||
|
- size += CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(ly_ctx_compiled_size(sm_data->shared->schemas[i].ctx));
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inlined contexts cannot be reused and will not be printed */
|
||||||
|
@@ -1501,7 +1499,7 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||||
|
|
||||||
|
/* sm_data */
|
||||||
|
ext->compiled = sm_data = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *sm_data);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *sm_data);
|
||||||
|
|
||||||
|
pthread_mutexattr_init(&attr);
|
||||||
|
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
|
||||||
|
@@ -1517,7 +1515,7 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||||
|
|
||||||
|
/* sm_data->shared */
|
||||||
|
sm_data->shared = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||||
|
|
||||||
|
sm_data->shared->schema_count = orig_sm_data->shared->schema_count;
|
||||||
|
sm_data->shared->ref_count = orig_sm_data->shared->ref_count;
|
||||||
|
@@ -1525,7 +1523,7 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||||
|
/* sm_data->shared->schemas */
|
||||||
|
if (orig_sm_data->shared->schemas) {
|
||||||
|
sm_data->shared->schemas = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||||
|
|
||||||
|
for (i = 0; i < sm_data->shared->schema_count; ++i) {
|
||||||
|
/* ctx */
|
||||||
|
@@ -1538,12 +1536,12 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||||
|
/* mount_point */
|
||||||
|
strcpy(*mem, orig_sm_data->shared->schemas[i].mount_point);
|
||||||
|
sm_data->shared->schemas[i].mount_point = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||||
|
|
||||||
|
/* content_id */
|
||||||
|
strcpy(*mem, orig_sm_data->shared->schemas[i].content_id);
|
||||||
|
sm_data->shared->schemas[i].content_id = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff --git a/src/plugins_exts/structure.c b/src/plugins_exts/structure.c
|
||||||
|
index b88c9a9a5..6309a2c30 100644
|
||||||
|
--- a/src/plugins_exts/structure.c
|
||||||
|
+++ b/src/plugins_exts/structure.c
|
||||||
|
@@ -329,9 +329,9 @@ structure_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *addr_
|
||||||
|
|
||||||
|
struct_cdata = ext->compiled;
|
||||||
|
|
||||||
|
- size += sizeof *struct_cdata;
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(sizeof *struct_cdata);
|
||||||
|
size += lyplg_ext_compiled_stmts_storage_size(ext->substmts, addr_ht);
|
||||||
|
- size += sizeof *struct_cdata->top_cont;
|
||||||
|
+ size += LY_CTXP_MEM_SIZE(sizeof *struct_cdata->top_cont);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
@@ -347,12 +347,12 @@ structure_compiled_print(const struct lysc_ext_instance *orig_ext, struct lysc_e
|
||||||
|
|
||||||
|
/* ext structure */
|
||||||
|
struct_cdata = ext->compiled = *mem;
|
||||||
|
- *mem = (char *)*mem + sizeof *struct_cdata;
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *struct_cdata);
|
||||||
|
memset(struct_cdata, 0, sizeof *struct_cdata);
|
||||||
|
|
||||||
|
/* top_cont */
|
||||||
|
struct_cdata->top_cont = *mem;
|
||||||
|
- *mem = (char *)*mem + sizeof *struct_cdata->top_cont;
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *struct_cdata->top_cont);
|
||||||
|
lyplg_ext_compiled_print_add_addr(addr_ht, orig_cdata->top_cont, struct_cdata->top_cont);
|
||||||
|
memset(struct_cdata->top_cont, 0, sizeof *struct_cdata->top_cont);
|
||||||
|
|
||||||
|
diff --git a/src/printer_context.c b/src/printer_context.c
|
||||||
|
index 57d5f90e1..3fb50ac0a 100644
|
||||||
|
--- a/src/printer_context.c
|
||||||
|
+++ b/src/printer_context.c
|
||||||
|
@@ -46,10 +46,10 @@
|
||||||
|
if (ORIG_ARRAY) { \
|
||||||
|
LY_ARRAY_COUNT_TYPE count = LY_ARRAY_COUNT(ORIG_ARRAY); \
|
||||||
|
memcpy(*MEM, &count, sizeof count); \
|
||||||
|
- *MEM = (char *)*MEM + CTXP_MEM_SIZE(sizeof count); \
|
||||||
|
+ *MEM = (char *)*MEM + LY_CTXP_MEM_SIZE(sizeof count); \
|
||||||
|
\
|
||||||
|
ARRAY = *MEM; \
|
||||||
|
- *MEM = (char *)*MEM + CTXP_MEM_SIZE(count * sizeof *ORIG_ARRAY); \
|
||||||
|
+ *MEM = (char *)*MEM + LY_CTXP_MEM_SIZE(count * sizeof *ORIG_ARRAY); \
|
||||||
|
} else { \
|
||||||
|
ARRAY = NULL; \
|
||||||
|
}
|
||||||
|
@@ -67,24 +67,12 @@
|
||||||
|
#define CTXP_OPTIONAL_STRUCT(FUNC, ORIG_PTR, PTR, ADDR_HT, PTR_SET, MEM) \
|
||||||
|
if (ORIG_PTR) { \
|
||||||
|
PTR = *MEM; \
|
||||||
|
- *MEM = (char *)*MEM + CTXP_MEM_SIZE(sizeof *PTR); \
|
||||||
|
+ *MEM = (char *)*MEM + LY_CTXP_MEM_SIZE(sizeof *PTR); \
|
||||||
|
FUNC(ORIG_PTR, PTR, ADDR_HT, PTR_SET, MEM); \
|
||||||
|
} else { \
|
||||||
|
PTR = NULL; \
|
||||||
|
}
|
||||||
|
|
||||||
|
-/**
|
||||||
|
- * @brief Alignment of all the printed data, ensures all memory access is aligned to this number (B)
|
||||||
|
- */
|
||||||
|
-#define CTXP_MEM_ALIGN 8
|
||||||
|
-
|
||||||
|
-/**
|
||||||
|
- * @brief Adjust data size to an aligned size to make sure the following data is aligned.
|
||||||
|
- *
|
||||||
|
- * @param[in,out] SIZE Data size that is adjusted.
|
||||||
|
- */
|
||||||
|
-#define CTXP_MEM_SIZE(SIZE) ((SIZE) + ((~(SIZE) + 1) & (CTXP_MEM_ALIGN - 1)))
|
||||||
|
-
|
||||||
|
static void
|
||||||
|
ctxs_dict_strings(const struct ly_ht *ht, int *size)
|
||||||
|
{
|
||||||
|
@@ -96,7 +84,7 @@ ctxs_dict_strings(const struct ly_ht *ht, int *size)
|
||||||
|
dict_rec = (struct ly_dict_rec *)&rec->val;
|
||||||
|
|
||||||
|
/* strings */
|
||||||
|
- *size += CTXP_MEM_SIZE(strlen(dict_rec->value) + 1);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(strlen(dict_rec->value) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -117,7 +105,7 @@ ctxs_exts(const struct lysc_ext_instance *exts, struct ly_ht *ht, int *size)
|
||||||
|
|
||||||
|
/* compiled, substmts storage */
|
||||||
|
if (exts[u].def->plugin_ref && (ext_plg = LYSC_GET_EXT_PLG(exts[u].def->plugin_ref)) && ext_plg->compiled_size) {
|
||||||
|
- *size += CTXP_MEM_SIZE(ext_plg->compiled_size(&exts[u], ht));
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(ext_plg->compiled_size(&exts[u], ht));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -131,7 +119,7 @@ ctxs_prefixes(const struct lysc_prefix *prefixes, int *size)
|
||||||
|
LY_ARRAY_FOR(prefixes, u) {
|
||||||
|
/* string not in the dictionary */
|
||||||
|
if (prefixes[u].prefix) {
|
||||||
|
- *size += CTXP_MEM_SIZE(strlen(prefixes[u].prefix) + 1);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(strlen(prefixes[u].prefix) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -141,20 +129,20 @@ ctxs_expr(const struct lyxp_expr *exp, int *size)
|
||||||
|
{
|
||||||
|
uint32_t i, j;
|
||||||
|
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *exp);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *exp);
|
||||||
|
|
||||||
|
- *size += CTXP_MEM_SIZE(strlen(exp->expr) + 1);
|
||||||
|
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->tokens);
|
||||||
|
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_pos);
|
||||||
|
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_len);
|
||||||
|
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->repeat);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(strlen(exp->expr) + 1);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->tokens);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_pos);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_len);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->repeat);
|
||||||
|
for (i = 0; i < exp->used; ++i) {
|
||||||
|
if (!exp->repeat[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (j = 0; exp->repeat[i][j]; ++j) {}
|
||||||
|
- *size += CTXP_MEM_SIZE((j + 1) * sizeof **exp->repeat);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE((j + 1) * sizeof **exp->repeat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -182,7 +170,7 @@ ctxs_when(const struct lysc_when *when, struct ly_ht *ht, int *size)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *when);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *when);
|
||||||
|
|
||||||
|
ctxs_expr(when->cond, size);
|
||||||
|
ctxs_prefixes(when->prefixes, size);
|
||||||
|
@@ -207,7 +195,7 @@ ctxs_range(const struct lysc_range *range, struct ly_ht *ht, int *size)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *range);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *range);
|
||||||
|
|
||||||
|
*size += CTXS_SIZED_ARRAY(range->parts);
|
||||||
|
ctxs_exts(range->exts, ht, size);
|
||||||
|
@@ -227,7 +215,7 @@ ctxs_patterns(const struct lysc_pattern **patterns, struct ly_ht *ht, int *size)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *patterns[u]);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *patterns[u]);
|
||||||
|
ctxs_exts(patterns[u]->exts, ht, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -270,7 +258,7 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||||
|
switch (type->basetype) {
|
||||||
|
case LY_TYPE_BINARY:
|
||||||
|
type_bin = (const struct lysc_type_bin *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_bin);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_bin);
|
||||||
|
|
||||||
|
ctxs_range(type_bin->length, ht, size);
|
||||||
|
break;
|
||||||
|
@@ -283,13 +271,13 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||||
|
case LY_TYPE_INT32:
|
||||||
|
case LY_TYPE_INT64:
|
||||||
|
type_num = (const struct lysc_type_num *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_num);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_num);
|
||||||
|
|
||||||
|
ctxs_range(type_num->range, ht, size);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_STRING:
|
||||||
|
type_str = (const struct lysc_type_str *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_str);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_str);
|
||||||
|
|
||||||
|
ctxs_range(type_str->length, ht, size);
|
||||||
|
ctxs_patterns((const struct lysc_pattern **)type_str->patterns, ht, size);
|
||||||
|
@@ -297,33 +285,33 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||||
|
case LY_TYPE_BITS:
|
||||||
|
case LY_TYPE_ENUM:
|
||||||
|
type_enum_bits = (const struct lysc_type_enum *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_enum_bits);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_enum_bits);
|
||||||
|
|
||||||
|
ctxs_enums(type_enum_bits->enums, ht, size);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_BOOL:
|
||||||
|
case LY_TYPE_EMPTY:
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_DEC64:
|
||||||
|
type_dec = (const struct lysc_type_dec *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_dec);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_dec);
|
||||||
|
|
||||||
|
ctxs_range(type_dec->range, ht, size);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_IDENT:
|
||||||
|
type_identref = (const struct lysc_type_identityref *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_identref);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_identref);
|
||||||
|
|
||||||
|
*size += CTXS_SIZED_ARRAY(type_identref->bases);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_INST:
|
||||||
|
type_instid = (const struct lysc_type_instanceid *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_instid);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_instid);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_LEAFREF:
|
||||||
|
type_lref = (const struct lysc_type_leafref *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_lref);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_lref);
|
||||||
|
|
||||||
|
ctxs_expr(type_lref->path, size);
|
||||||
|
ctxs_prefixes(type_lref->prefixes, size);
|
||||||
|
@@ -331,7 +319,7 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||||
|
break;
|
||||||
|
case LY_TYPE_UNION:
|
||||||
|
type_union = (const struct lysc_type_union *)type;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *type_union);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_union);
|
||||||
|
|
||||||
|
*size += CTXS_SIZED_ARRAY(type_union->types);
|
||||||
|
LY_ARRAY_FOR(type_union->types, u) {
|
||||||
|
@@ -366,7 +354,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
switch (node->nodetype) {
|
||||||
|
case LYS_CONTAINER:
|
||||||
|
cont = (const struct lysc_node_container *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *cont);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *cont);
|
||||||
|
|
||||||
|
LY_LIST_FOR(cont->child, child) {
|
||||||
|
ctxs_node(child, ht, size);
|
||||||
|
@@ -382,7 +370,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
break;
|
||||||
|
case LYS_CHOICE:
|
||||||
|
choic = (const struct lysc_node_choice *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *choic);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *choic);
|
||||||
|
|
||||||
|
LY_LIST_FOR((const struct lysc_node *)choic->cases, child) {
|
||||||
|
ctxs_node(child, ht, size);
|
||||||
|
@@ -391,7 +379,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
break;
|
||||||
|
case LYS_LEAF:
|
||||||
|
leaf = (const struct lysc_node_leaf *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *leaf);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *leaf);
|
||||||
|
|
||||||
|
ctxs_musts(leaf->musts, ht, size);
|
||||||
|
ctxs_whens((const struct lysc_when **)leaf->when, ht, size);
|
||||||
|
@@ -400,7 +388,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
break;
|
||||||
|
case LYS_LEAFLIST:
|
||||||
|
llist = (const struct lysc_node_leaflist *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *llist);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *llist);
|
||||||
|
|
||||||
|
ctxs_musts(llist->musts, ht, size);
|
||||||
|
ctxs_whens((const struct lysc_when **)llist->when, ht, size);
|
||||||
|
@@ -412,7 +400,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
break;
|
||||||
|
case LYS_LIST:
|
||||||
|
list = (const struct lysc_node_list *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *list);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *list);
|
||||||
|
|
||||||
|
LY_LIST_FOR(list->child, child) {
|
||||||
|
ctxs_node(child, ht, size);
|
||||||
|
@@ -427,21 +415,21 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
}
|
||||||
|
*size += CTXS_SIZED_ARRAY(list->uniques);
|
||||||
|
LY_ARRAY_FOR(list->uniques, u) {
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof(LY_ARRAY_COUNT_TYPE)) +
|
||||||
|
- CTXP_MEM_SIZE(LY_ARRAY_COUNT(list->uniques[u]) * sizeof **list->uniques);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof(LY_ARRAY_COUNT_TYPE)) +
|
||||||
|
+ LY_CTXP_MEM_SIZE(LY_ARRAY_COUNT(list->uniques[u]) * sizeof **list->uniques);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LYS_ANYXML:
|
||||||
|
case LYS_ANYDATA:
|
||||||
|
any = (const struct lysc_node_anydata *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *any);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *any);
|
||||||
|
|
||||||
|
ctxs_musts(any->musts, ht, size);
|
||||||
|
ctxs_whens((const struct lysc_when **)any->when, ht, size);
|
||||||
|
break;
|
||||||
|
case LYS_CASE:
|
||||||
|
cas = (const struct lysc_node_case *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *cas);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *cas);
|
||||||
|
|
||||||
|
LY_LIST_FOR(cas->child, child) {
|
||||||
|
ctxs_node(child, ht, size);
|
||||||
|
@@ -451,7 +439,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
case LYS_RPC:
|
||||||
|
case LYS_ACTION:
|
||||||
|
act = (const struct lysc_node_action *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *act);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *act);
|
||||||
|
|
||||||
|
ctxs_whens((const struct lysc_when **)act->when, ht, size);
|
||||||
|
ctxs_node((struct lysc_node *)&act->input, ht, size);
|
||||||
|
@@ -469,7 +457,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||||
|
break;
|
||||||
|
case LYS_NOTIF:
|
||||||
|
notif = (const struct lysc_node_notif *)node;
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *notif);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *notif);
|
||||||
|
|
||||||
|
LY_LIST_FOR(notif->child, child) {
|
||||||
|
ctxs_node(child, ht, size);
|
||||||
|
@@ -493,7 +481,7 @@ ctxs_compiled(const struct lysc_module *compiled, struct ly_ht *ht, int *size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compiled module */
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *compiled);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *compiled);
|
||||||
|
|
||||||
|
*size += CTXS_SIZED_ARRAY(compiled->features);
|
||||||
|
LY_LIST_FOR(compiled->data, node) {
|
||||||
|
@@ -539,7 +527,7 @@ static void
|
||||||
|
ctxs_module(const struct lys_module *mod, struct ly_ht *ht, int *size)
|
||||||
|
{
|
||||||
|
/* module */
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *mod);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *mod);
|
||||||
|
|
||||||
|
/* compiled module */
|
||||||
|
ctxs_compiled(mod->compiled, ht, size);
|
||||||
|
@@ -561,13 +549,13 @@ ly_ctx_compiled_size_context(const struct ly_ctx *ctx, struct ly_ht *addr_ht, in
|
||||||
|
const struct lys_module *mod;
|
||||||
|
|
||||||
|
/* context */
|
||||||
|
- *size += CTXP_MEM_SIZE(sizeof *ctx);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(sizeof *ctx);
|
||||||
|
|
||||||
|
/* all the strings in the dictionary */
|
||||||
|
ctxs_dict_strings(ctx->dict.hash_tab, size);
|
||||||
|
|
||||||
|
/* module set */
|
||||||
|
- *size += CTXP_MEM_SIZE(ctx->modules.count * sizeof ctx->modules.objs);
|
||||||
|
+ *size += LY_CTXP_MEM_SIZE(ctx->modules.count * sizeof ctx->modules.objs);
|
||||||
|
for (i = 0; i < ctx->modules.count; ++i) {
|
||||||
|
mod = ctx->modules.objs[i];
|
||||||
|
|
||||||
|
@@ -720,7 +708,7 @@ ctxp_set(const struct ly_set *orig_set, struct ly_set *set, void **mem)
|
||||||
|
set->size = orig_set->count;
|
||||||
|
set->count = orig_set->count;
|
||||||
|
set->objs = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(set->count * sizeof set->objs);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(set->count * sizeof set->objs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
@@ -741,7 +729,7 @@ ctxp_dict_strings(const struct ly_ht *orig_dict, struct ly_ht *addr_ht, void **m
|
||||||
|
orig_str = dict_rec->value;
|
||||||
|
|
||||||
|
str = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(len);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(len);
|
||||||
|
|
||||||
|
memcpy(str, orig_str, len);
|
||||||
|
|
||||||
|
@@ -801,30 +789,30 @@ ctxp_expr(const struct lyxp_expr *orig_exp, struct lyxp_expr *exp, void **mem)
|
||||||
|
uint32_t i, len;
|
||||||
|
|
||||||
|
exp->expr = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(strlen(orig_exp->expr) + 1);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(strlen(orig_exp->expr) + 1);
|
||||||
|
memcpy(exp->expr, orig_exp->expr, strlen(orig_exp->expr) + 1);
|
||||||
|
|
||||||
|
exp->tokens = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tokens);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tokens);
|
||||||
|
memcpy(exp->tokens, orig_exp->tokens, orig_exp->used * sizeof *exp->tokens);
|
||||||
|
|
||||||
|
exp->tok_pos = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_pos);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_pos);
|
||||||
|
memcpy(exp->tok_pos, orig_exp->tok_pos, orig_exp->used * sizeof *exp->tok_pos);
|
||||||
|
|
||||||
|
exp->tok_len = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_len);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_len);
|
||||||
|
memcpy(exp->tok_len, orig_exp->tok_len, orig_exp->used * sizeof *exp->tok_len);
|
||||||
|
|
||||||
|
exp->repeat = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->repeat);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->repeat);
|
||||||
|
for (i = 0; i < orig_exp->used; ++i) {
|
||||||
|
if (orig_exp->repeat[i]) {
|
||||||
|
for (len = 0; orig_exp->repeat[i][len]; ++len) {}
|
||||||
|
++len;
|
||||||
|
|
||||||
|
exp->repeat[i] = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(len * sizeof **exp->repeat);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(len * sizeof **exp->repeat);
|
||||||
|
memcpy(exp->repeat[i], orig_exp->repeat[i], len * sizeof **exp->repeat);
|
||||||
|
} else {
|
||||||
|
exp->repeat[i] = NULL;
|
||||||
|
@@ -845,7 +833,7 @@ ctxp_prefix(const struct lysc_prefix *orig_prefix, struct lysc_prefix *prefix, s
|
||||||
|
size = strlen(orig_prefix->prefix) + 1;
|
||||||
|
|
||||||
|
prefix->prefix = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(size);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(size);
|
||||||
|
memcpy(prefix->prefix, orig_prefix->prefix, size);
|
||||||
|
} else {
|
||||||
|
prefix->prefix = NULL;
|
||||||
|
@@ -867,7 +855,7 @@ ctxp_must(const struct lysc_must *orig_must, struct lysc_must *must, struct ly_h
|
||||||
|
}
|
||||||
|
|
||||||
|
must->cond = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *must->cond);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *must->cond);
|
||||||
|
ctxp_expr(orig_must->cond, must->cond, mem);
|
||||||
|
|
||||||
|
CTXP_SIZED_ARRAY(orig_must->prefixes, must->prefixes, mem);
|
||||||
|
@@ -901,7 +889,7 @@ ctxp_when(const struct lysc_when *orig_when, struct lysc_when **when, struct ly_
|
||||||
|
}
|
||||||
|
|
||||||
|
w = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *w);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *w);
|
||||||
|
|
||||||
|
if (orig_when->exts) {
|
||||||
|
/* may be referenced in the parent */
|
||||||
|
@@ -909,7 +897,7 @@ ctxp_when(const struct lysc_when *orig_when, struct lysc_when **when, struct ly_
|
||||||
|
}
|
||||||
|
|
||||||
|
w->cond = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *w->cond);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *w->cond);
|
||||||
|
ctxp_expr(orig_when->cond, w->cond, mem);
|
||||||
|
|
||||||
|
w->context = ly_ctx_compiled_addr_ht_get(addr_ht, orig_when->context, 0);
|
||||||
|
@@ -980,7 +968,7 @@ ctxp_children(const struct lysc_node *orig_child, struct lysc_node **child, stru
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(node_size);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(node_size);
|
||||||
|
|
||||||
|
/* referenced by pointers */
|
||||||
|
ly_ctx_compiled_addr_ht_add(addr_ht, orig_child, ch);
|
||||||
|
@@ -1035,7 +1023,7 @@ ctxp_pattern(const struct lysc_pattern *orig_pattern, struct lysc_pattern **patt
|
||||||
|
}
|
||||||
|
|
||||||
|
p = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *p);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *p);
|
||||||
|
|
||||||
|
if (orig_pattern->exts) {
|
||||||
|
/* may be referenced in the parent */
|
||||||
|
@@ -1122,7 +1110,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_BINARY:
|
||||||
|
orig_type_bin = (const struct lysc_type_bin *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_bin);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_bin);
|
||||||
|
t_bin = (struct lysc_type_bin *)t;
|
||||||
|
|
||||||
|
CTXP_OPTIONAL_STRUCT(ctxp_range, orig_type_bin->length, t_bin->length, addr_ht, ptr_set, mem);
|
||||||
|
@@ -1137,7 +1125,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_INT64:
|
||||||
|
orig_type_num = (const struct lysc_type_num *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_num);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_num);
|
||||||
|
t_num = (struct lysc_type_num *)t;
|
||||||
|
|
||||||
|
CTXP_OPTIONAL_STRUCT(ctxp_range, orig_type_num->range, t_num->range, addr_ht, ptr_set, mem);
|
||||||
|
@@ -1145,7 +1133,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_STRING:
|
||||||
|
orig_type_str = (const struct lysc_type_str *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_str);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_str);
|
||||||
|
t_str = (struct lysc_type_str *)t;
|
||||||
|
|
||||||
|
CTXP_OPTIONAL_STRUCT(ctxp_range, orig_type_str->length, t_str->length, addr_ht, ptr_set, mem);
|
||||||
|
@@ -1158,7 +1146,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_ENUM:
|
||||||
|
orig_type_enum_bits = (const struct lysc_type_enum *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_enum_bits);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_enum_bits);
|
||||||
|
t_enum_bits = (struct lysc_type_enum *)t;
|
||||||
|
|
||||||
|
CTXP_SIZED_ARRAY(orig_type_enum_bits->enums, t_enum_bits->enums, mem);
|
||||||
|
@@ -1170,12 +1158,12 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_EMPTY:
|
||||||
|
/* no additional members */
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type);
|
||||||
|
break;
|
||||||
|
case LY_TYPE_DEC64:
|
||||||
|
orig_type_dec = (const struct lysc_type_dec *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_dec);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_dec);
|
||||||
|
t_dec = (struct lysc_type_dec *)t;
|
||||||
|
|
||||||
|
t_dec->fraction_digits = orig_type_dec->fraction_digits;
|
||||||
|
@@ -1184,7 +1172,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_IDENT:
|
||||||
|
orig_type_identref = (const struct lysc_type_identityref *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_identref);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_identref);
|
||||||
|
t_identref = (struct lysc_type_identityref *)t;
|
||||||
|
|
||||||
|
CTXP_SIZED_ARRAY(orig_type_identref->bases, t_identref->bases, mem);
|
||||||
|
@@ -1197,7 +1185,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_INST:
|
||||||
|
orig_type_instid = (const struct lysc_type_instanceid *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_instid);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_instid);
|
||||||
|
t_instid = (struct lysc_type_instanceid *)t;
|
||||||
|
|
||||||
|
t_instid->require_instance = orig_type_instid->require_instance;
|
||||||
|
@@ -1205,11 +1193,11 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_LEAFREF:
|
||||||
|
orig_type_lref = (const struct lysc_type_leafref *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_lref);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_lref);
|
||||||
|
t_lref = (struct lysc_type_leafref *)t;
|
||||||
|
|
||||||
|
t_lref->path = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *t_lref->path);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *t_lref->path);
|
||||||
|
ctxp_expr(orig_type_lref->path, t_lref->path, mem);
|
||||||
|
|
||||||
|
CTXP_SIZED_ARRAY(orig_type_lref->prefixes, t_lref->prefixes, mem);
|
||||||
|
@@ -1223,7 +1211,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||||
|
case LY_TYPE_UNION:
|
||||||
|
orig_type_union = (const struct lysc_type_union *)orig_type;
|
||||||
|
t = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_union);
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_union);
|
||||||
|
t_union = (struct lysc_type_union *)t;
|
||||||
|
|
||||||
|
CTXP_SIZED_ARRAY(orig_type_union->types, t_union->types, mem);
|
||||||
|
@@ -1736,7 +1724,7 @@ ly_ctx_compiled_print_context(const struct ly_ctx *orig_ctx, struct ly_ctx *ctx,
|
||||||
|
for (i = 0; i < ctx->modules.count; ++i) {
|
||||||
|
/* allocate the shared module and store its new address in HT so it can be referenced */
|
||||||
|
ctx->modules.objs[i] = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof(struct lys_module));
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof(struct lys_module));
|
||||||
|
ly_ctx_compiled_addr_ht_add(addr_ht, orig_ctx->modules.objs[i], ctx->modules.objs[i]);
|
||||||
|
}
|
||||||
|
for (i = 0; i < ctx->modules.count; ++i) {
|
||||||
|
@@ -1864,7 +1852,7 @@ ly_ctx_compiled_ext_stmts_storage_print(const struct lysc_ext_substmt *orig_subs
|
||||||
|
case LY_STMT_LENGTH:
|
||||||
|
case LY_STMT_RANGE:
|
||||||
|
*substmts[u].storage_p = *mem;
|
||||||
|
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof(struct lysc_range));
|
||||||
|
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof(struct lysc_range));
|
||||||
|
ctxp_range(*orig_substmts[u].storage_p, *substmts[u].storage_p, addr_ht, ptr_set, mem);
|
||||||
|
break;
|
||||||
|
case LY_STMT_MAX_ELEMENTS:
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
From 4d32b003a7aaad5ecca9a013db2dbaa36ef79692 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Wed, 10 Dec 2025 10:13:54 +0100
|
||||||
|
Subject: [PATCH 2/6] printer context BUGFIX missing mem alignment
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Refs #2455
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/context.c | 2 +-
|
||||||
|
src/printer_context.c | 2 +-
|
||||||
|
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/context.c b/src/context.c
|
||||||
|
index 88f4678b3..52a4ae46d 100644
|
||||||
|
--- a/src/context.c
|
||||||
|
+++ b/src/context.c
|
||||||
|
@@ -1459,7 +1459,7 @@ ly_ctx_compiled_print(const struct ly_ctx *ctx, void *mem, void **mem_end)
|
||||||
|
|
||||||
|
/* context, referenced */
|
||||||
|
pctx = mem;
|
||||||
|
- mem = (char *)mem + sizeof *pctx;
|
||||||
|
+ mem = (char *)mem + LY_CTXP_MEM_SIZE(sizeof *pctx);
|
||||||
|
ly_ctx_compiled_addr_ht_add(addr_ht, ctx, pctx);
|
||||||
|
|
||||||
|
/* members */
|
||||||
|
diff --git a/src/printer_context.c b/src/printer_context.c
|
||||||
|
index 3fb50ac0a..ade0a210d 100644
|
||||||
|
--- a/src/printer_context.c
|
||||||
|
+++ b/src/printer_context.c
|
||||||
|
@@ -33,7 +33,7 @@
|
||||||
|
* @return Size of @p ARRAY.
|
||||||
|
*/
|
||||||
|
#define CTXS_SIZED_ARRAY(ARRAY) \
|
||||||
|
- (ARRAY ? sizeof(LY_ARRAY_COUNT_TYPE) : 0) + LY_ARRAY_COUNT(ARRAY) * sizeof *ARRAY
|
||||||
|
+ (ARRAY ? LY_CTXP_MEM_SIZE(sizeof(LY_ARRAY_COUNT_TYPE)) : 0) + LY_CTXP_MEM_SIZE(LY_ARRAY_COUNT(ARRAY) * sizeof *ARRAY)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print (serialize) a sized array.
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
From 51f257fe100adde66efc4d93a5e40b2e5a9a9fab Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Thu, 11 Dec 2025 12:05:02 +0100
|
||||||
|
Subject: [PATCH 3/6] lyb BUGFIX big-endian fixes
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Refs #2455
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/parser_lyb.c | 8 +++++++-
|
||||||
|
src/printer_lyb.c | 18 ++++++++++++------
|
||||||
|
2 files changed, 19 insertions(+), 7 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
|
||||||
|
index 43ab02f95..82c759c3d 100644
|
||||||
|
--- a/src/parser_lyb.c
|
||||||
|
+++ b/src/parser_lyb.c
|
||||||
|
@@ -1425,7 +1425,10 @@ lyb_parse_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_n
|
||||||
|
char *mod_name = NULL, mod_rev[LY_REV_SIZE];
|
||||||
|
|
||||||
|
/* read node type */
|
||||||
|
- lyb_read((uint64_t *)&lyb_type, LYB_NODE_TYPE_BITS, lybctx->parse_ctx);
|
||||||
|
+ lyb_read(&lyb_type, LYB_NODE_TYPE_BITS, lybctx->parse_ctx);
|
||||||
|
+
|
||||||
|
+ /* correct byte order */
|
||||||
|
+ lyb_type = le32toh(lyb_type);
|
||||||
|
|
||||||
|
switch (lyb_type) {
|
||||||
|
case LYB_NODE_END:
|
||||||
|
@@ -1543,6 +1546,9 @@ lyb_parse_header(struct lyd_lyb_ctx *lybctx)
|
||||||
|
data_hash = 0;
|
||||||
|
lyb_read((uint8_t *)&data_hash, LYB_HEADER_CTX_HASH_BITS, pctx);
|
||||||
|
|
||||||
|
+ /* correct byte order */
|
||||||
|
+ data_hash = le32toh(data_hash);
|
||||||
|
+
|
||||||
|
if (!data_hash) {
|
||||||
|
/* fine for no data */
|
||||||
|
pctx->empty_hash = 1;
|
||||||
|
diff --git a/src/printer_lyb.c b/src/printer_lyb.c
|
||||||
|
index b35d3c0fe..7094467a7 100644
|
||||||
|
--- a/src/printer_lyb.c
|
||||||
|
+++ b/src/printer_lyb.c
|
||||||
|
@@ -447,12 +447,12 @@ lyb_write_count(uint32_t count, struct lylyb_print_ctx *lybctx)
|
||||||
|
return LY_EINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
- /* correct byte order */
|
||||||
|
- count = htole32(count);
|
||||||
|
-
|
||||||
|
/* copy count to buf */
|
||||||
|
buf |= count << prefix_b;
|
||||||
|
|
||||||
|
+ /* correct byte order */
|
||||||
|
+ buf = htole32(buf);
|
||||||
|
+
|
||||||
|
return lyb_write(&buf, prefix_b + num_b, lybctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -492,12 +492,12 @@ lyb_write_size(uint32_t size, struct lylyb_print_ctx *lybctx)
|
||||||
|
num_b = 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
- /* correct byte order */
|
||||||
|
- size = htole32(size);
|
||||||
|
-
|
||||||
|
/* copy size to buf */
|
||||||
|
buf |= size << prefix_b;
|
||||||
|
|
||||||
|
+ /* correct byte order */
|
||||||
|
+ buf = htole32(buf);
|
||||||
|
+
|
||||||
|
return lyb_write(&buf, prefix_b + num_b, lybctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -635,6 +635,9 @@ lyb_print_header(struct lylyb_print_ctx *lybctx)
|
||||||
|
/* context hash (is truncated), if not printing empty data */
|
||||||
|
if (lybctx->ctx) {
|
||||||
|
hash = lyb_truncate_hash_nonzero(ly_ctx_get_modules_hash(lybctx->ctx), LYB_HEADER_CTX_HASH_BITS);
|
||||||
|
+
|
||||||
|
+ /* correct byte order */
|
||||||
|
+ hash = htole32(hash);
|
||||||
|
} else {
|
||||||
|
hash = 0;
|
||||||
|
}
|
||||||
|
@@ -1019,6 +1022,9 @@ lyb_print_lyb_type(const struct lyd_node *node, struct lylyb_print_ctx *lybctx)
|
||||||
|
lyb_type = LYB_NODE_CHILD;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ /* correct byte order */
|
||||||
|
+ lyb_type = htole32(lyb_type);
|
||||||
|
+
|
||||||
|
LY_CHECK_RET(lyb_write(&lyb_type, LYB_NODE_TYPE_BITS, lybctx));
|
||||||
|
|
||||||
|
return LY_SUCCESS;
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
From a44103219029ba70b67a87d5a33cfb8aec1e03f3 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Thu, 11 Dec 2025 12:05:51 +0100
|
||||||
|
Subject: [PATCH 4/6] bits BUGFIX operator priority
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Refs #2455
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/plugins_types/bits.c | 4 ++--
|
||||||
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/plugins_types/bits.c b/src/plugins_types/bits.c
|
||||||
|
index ab7acb527..f5fa6dccb 100644
|
||||||
|
--- a/src/plugins_types/bits.c
|
||||||
|
+++ b/src/plugins_types/bits.c
|
||||||
|
@@ -61,7 +61,7 @@ lyplg_type_bits_is_bit_set(const char *bitmap, uint32_t size_bits, uint32_t bit_
|
||||||
|
uint32_t size;
|
||||||
|
|
||||||
|
/* get size in bytes */
|
||||||
|
- size = size_bits / 8 + (size_bits % 8) ? 1 : 0;
|
||||||
|
+ size = size_bits / 8 + ((size_bits % 8) ? 1 : 0);
|
||||||
|
|
||||||
|
/* find the byte with our bit */
|
||||||
|
(void)size;
|
||||||
|
@@ -93,7 +93,7 @@ bits_bit_set(char *bitmap, uint32_t size_bits, uint32_t bit_position)
|
||||||
|
uint32_t size;
|
||||||
|
|
||||||
|
/* get size in bytes */
|
||||||
|
- size = size_bits / 8 + (size_bits % 8) ? 1 : 0;
|
||||||
|
+ size = size_bits / 8 + ((size_bits % 8) ? 1 : 0);
|
||||||
|
|
||||||
|
/* find the byte with our bit */
|
||||||
|
(void)size;
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
From e2c06476b886642411181e0a236b95de14145940 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Thu, 11 Dec 2025 12:06:15 +0100
|
||||||
|
Subject: [PATCH 5/6] integer BUGFIX big-endian print fixes
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Refs #2455
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/plugins_types/integer.c | 96 ++++++++++---------------------------
|
||||||
|
1 file changed, 24 insertions(+), 72 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/plugins_types/integer.c b/src/plugins_types/integer.c
|
||||||
|
index 945d93f86..f05096970 100644
|
||||||
|
--- a/src/plugins_types/integer.c
|
||||||
|
+++ b/src/plugins_types/integer.c
|
||||||
|
@@ -291,64 +291,6 @@ lyplg_type_sort_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *va
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
-static const void *
|
||||||
|
-lyplg_type_print_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format,
|
||||||
|
- void *UNUSED(prefix_data), ly_bool *dynamic, uint32_t *value_size_bits)
|
||||||
|
-{
|
||||||
|
- int64_t prev_num = 0, num = 0;
|
||||||
|
- void *buf;
|
||||||
|
-
|
||||||
|
- if (format == LY_VALUE_LYB) {
|
||||||
|
- switch (value->realtype->basetype) {
|
||||||
|
- case LY_TYPE_INT8:
|
||||||
|
- prev_num = num = value->int8;
|
||||||
|
- break;
|
||||||
|
- case LY_TYPE_INT16:
|
||||||
|
- prev_num = num = value->int16;
|
||||||
|
- break;
|
||||||
|
- case LY_TYPE_INT32:
|
||||||
|
- prev_num = num = value->int32;
|
||||||
|
- break;
|
||||||
|
- case LY_TYPE_INT64:
|
||||||
|
- prev_num = num = value->int64;
|
||||||
|
- break;
|
||||||
|
- default:
|
||||||
|
- break;
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- num = htole64(num);
|
||||||
|
- if (num == prev_num) {
|
||||||
|
- /* values are equal, little-endian or int8 */
|
||||||
|
- *dynamic = 0;
|
||||||
|
- if (value_size_bits) {
|
||||||
|
- /* the least amount of bits that can hold the number */
|
||||||
|
- *value_size_bits = lyplg_type_get_highest_set_bit_pos(num);
|
||||||
|
- }
|
||||||
|
- return &value->int64;
|
||||||
|
- } else {
|
||||||
|
- /* values differ, big-endian */
|
||||||
|
- buf = calloc(1, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||||
|
- LY_CHECK_RET(!buf, NULL);
|
||||||
|
-
|
||||||
|
- *dynamic = 1;
|
||||||
|
- if (value_size_bits) {
|
||||||
|
- *value_size_bits = lyplg_type_get_highest_set_bit_pos(num);
|
||||||
|
- }
|
||||||
|
- memcpy(buf, &num, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||||
|
- return buf;
|
||||||
|
- }
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- /* use the cached canonical value */
|
||||||
|
- if (dynamic) {
|
||||||
|
- *dynamic = 0;
|
||||||
|
- }
|
||||||
|
- if (value_size_bits) {
|
||||||
|
- *value_size_bits = strlen(value->_canonical) * 8;
|
||||||
|
- }
|
||||||
|
- return value->_canonical;
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
static LY_ERR
|
||||||
|
lyplg_type_store_uint(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, uint32_t value_size_bits,
|
||||||
|
uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints,
|
||||||
|
@@ -573,31 +515,37 @@ lyplg_type_sort_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *v
|
||||||
|
}
|
||||||
|
|
||||||
|
static const void *
|
||||||
|
-lyplg_type_print_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format,
|
||||||
|
+lyplg_type_print_u_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format,
|
||||||
|
void *UNUSED(prefix_data), ly_bool *dynamic, uint32_t *value_size_bits)
|
||||||
|
{
|
||||||
|
uint64_t num = 0;
|
||||||
|
+ uint8_t bytes_used;
|
||||||
|
+ uint16_t bits_used;
|
||||||
|
void *buf;
|
||||||
|
|
||||||
|
if (format == LY_VALUE_LYB) {
|
||||||
|
switch (value->realtype->basetype) {
|
||||||
|
case LY_TYPE_UINT8:
|
||||||
|
+ case LY_TYPE_INT8:
|
||||||
|
num = value->uint8;
|
||||||
|
break;
|
||||||
|
case LY_TYPE_UINT16:
|
||||||
|
+ case LY_TYPE_INT16:
|
||||||
|
num = value->uint16;
|
||||||
|
break;
|
||||||
|
case LY_TYPE_UINT32:
|
||||||
|
+ case LY_TYPE_INT32:
|
||||||
|
num = value->uint32;
|
||||||
|
break;
|
||||||
|
case LY_TYPE_UINT64:
|
||||||
|
+ case LY_TYPE_INT64:
|
||||||
|
num = value->uint64;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
- num = htole64(num);
|
||||||
|
- if (num == value->uint64) {
|
||||||
|
+
|
||||||
|
+ if (htole64(num) == value->uint64) {
|
||||||
|
/* values are equal, little-endian or uint8 */
|
||||||
|
*dynamic = 0;
|
||||||
|
if (value_size_bits) {
|
||||||
|
@@ -607,14 +555,18 @@ lyplg_type_print_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *
|
||||||
|
return &value->uint64;
|
||||||
|
} else {
|
||||||
|
/* values differ, big-endian */
|
||||||
|
- buf = calloc(1, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||||
|
+ bits_used = lyplg_type_get_highest_set_bit_pos(num);
|
||||||
|
+ bytes_used = LYPLG_BITS2BYTES(bits_used);
|
||||||
|
+
|
||||||
|
+ buf = calloc(1, bytes_used);
|
||||||
|
LY_CHECK_RET(!buf, NULL);
|
||||||
|
+ num = htole64(num);
|
||||||
|
+ memcpy(buf, &num, bytes_used);
|
||||||
|
|
||||||
|
*dynamic = 1;
|
||||||
|
if (value_size_bits) {
|
||||||
|
- *value_size_bits = lyplg_type_get_highest_set_bit_pos(num);
|
||||||
|
+ *value_size_bits = bits_used;
|
||||||
|
}
|
||||||
|
- memcpy(buf, &num, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -649,7 +601,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_uint,
|
||||||
|
.plugin.sort = lyplg_type_sort_uint,
|
||||||
|
- .plugin.print = lyplg_type_print_uint,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -664,7 +616,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_uint,
|
||||||
|
.plugin.sort = lyplg_type_sort_uint,
|
||||||
|
- .plugin.print = lyplg_type_print_uint,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -679,7 +631,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_uint,
|
||||||
|
.plugin.sort = lyplg_type_sort_uint,
|
||||||
|
- .plugin.print = lyplg_type_print_uint,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -694,7 +646,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_uint,
|
||||||
|
.plugin.sort = lyplg_type_sort_uint,
|
||||||
|
- .plugin.print = lyplg_type_print_uint,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -709,7 +661,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_int,
|
||||||
|
.plugin.sort = lyplg_type_sort_int,
|
||||||
|
- .plugin.print = lyplg_type_print_int,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -724,7 +676,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_int,
|
||||||
|
.plugin.sort = lyplg_type_sort_int,
|
||||||
|
- .plugin.print = lyplg_type_print_int,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -739,7 +691,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_int,
|
||||||
|
.plugin.sort = lyplg_type_sort_int,
|
||||||
|
- .plugin.print = lyplg_type_print_int,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
}, {
|
||||||
|
@@ -754,7 +706,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||||
|
.plugin.validate_tree = NULL,
|
||||||
|
.plugin.compare = lyplg_type_compare_int,
|
||||||
|
.plugin.sort = lyplg_type_sort_int,
|
||||||
|
- .plugin.print = lyplg_type_print_int,
|
||||||
|
+ .plugin.print = lyplg_type_print_u_int,
|
||||||
|
.plugin.duplicate = lyplg_type_dup_simple,
|
||||||
|
.plugin.free = lyplg_type_free_simple,
|
||||||
|
},
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
From 0e911b67177252160f7f1361e2481a88200debc3 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Thu, 11 Dec 2025 15:20:28 +0100
|
||||||
|
Subject: [PATCH 6/6] context BUGFIX unexpected context node
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Fixes #2457
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/context.c | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/src/context.c b/src/context.c
|
||||||
|
index 52a4ae46d..1c287d9f2 100644
|
||||||
|
--- a/src/context.c
|
||||||
|
+++ b/src/context.c
|
||||||
|
@@ -370,7 +370,7 @@ ly_ctx_new_yl_legacy(struct ly_ctx *ctx, const struct lyd_node *yltree)
|
||||||
|
LY_ERR ret = LY_SUCCESS;
|
||||||
|
uint32_t i, j;
|
||||||
|
|
||||||
|
- LY_CHECK_RET(ret = lyd_find_xpath(yltree, "modules-state/module", &set));
|
||||||
|
+ LY_CHECK_RET(ret = lyd_find_xpath(yltree, "/ietf-yang-library:modules-state/module", &set));
|
||||||
|
|
||||||
|
if (!set->count) {
|
||||||
|
ret = LY_ENOTFOUND;
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
From 8406098242751deb3de1172ceda382251fd2c8f5 Mon Sep 17 00:00:00 2001
|
From 84a8003b97caaf602be1406e8291cea10d5cb7c1 Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Thu, 22 Jun 2023 10:24:57 +0200
|
Date: Thu, 22 Jun 2023 10:24:57 +0200
|
||||||
Subject: [PATCH 1/2] Allow 'factory' as copy-from (only) in rpc copy-config
|
Subject: [PATCH 1/4] Allow 'factory' as copy-from (only) in rpc copy-config
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -14,7 +14,7 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
1 file changed, 2 insertions(+)
|
1 file changed, 2 insertions(+)
|
||||||
|
|
||||||
diff --git a/src/netconf.c b/src/netconf.c
|
diff --git a/src/netconf.c b/src/netconf.c
|
||||||
index 1d9ba79..2a6f1a4 100644
|
index 764f1f6..084e1d4 100644
|
||||||
--- a/src/netconf.c
|
--- a/src/netconf.c
|
||||||
+++ b/src/netconf.c
|
+++ b/src/netconf.c
|
||||||
@@ -334,6 +334,8 @@ np2srv_rpc_copyconfig_cb(const struct lyd_node *rpc, struct np_user_sess *user_s
|
@@ -334,6 +334,8 @@ np2srv_rpc_copyconfig_cb(const struct lyd_node *rpc, struct np_user_sess *user_s
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
From 6ad71e6359453b8dcecbf9f33f59bd736111ecf3 Mon Sep 17 00:00:00 2001
|
From 010621423617d87b87f3484188ce8d6b960b50b6 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||||
Date: Tue, 4 Feb 2025 20:14:50 +0100
|
Date: Tue, 4 Feb 2025 20:14:50 +0100
|
||||||
Subject: [PATCH 2/2] Do not generate data in sysrepo
|
Subject: [PATCH 2/4] Do not generate data in sysrepo
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -16,7 +16,7 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
1 file changed, 40 deletions(-)
|
1 file changed, 40 deletions(-)
|
||||||
|
|
||||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
index cf97275..95b82ae 100644
|
index 71c8bdd..594363f 100644
|
||||||
--- a/CMakeLists.txt
|
--- a/CMakeLists.txt
|
||||||
+++ b/CMakeLists.txt
|
+++ b/CMakeLists.txt
|
||||||
@@ -395,47 +395,7 @@ if(SYSREPO_SETUP)
|
@@ -395,47 +395,7 @@ if(SYSREPO_SETUP)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
From d3a5114761d077ad72171998045d37cbf46bb7d0 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Michal Vasko <mvasko@cesnet.cz>
|
||||||
|
Date: Thu, 4 Dec 2025 14:13:10 +0100
|
||||||
|
Subject: [PATCH 3/4] main UPDATE support having local users disabled
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
src/main.c | 21 +++++++++++++++------
|
||||||
|
1 file changed, 15 insertions(+), 6 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/main.c b/src/main.c
|
||||||
|
index 64ffaaa..7492b43 100644
|
||||||
|
--- a/src/main.c
|
||||||
|
+++ b/src/main.c
|
||||||
|
@@ -1158,6 +1158,8 @@ server_data_subscribe(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
const char *mod_name, *xpath;
|
||||||
|
+ const struct lys_module *mod;
|
||||||
|
+ const struct ly_ctx *ly_ctx;
|
||||||
|
|
||||||
|
#define SR_OPER_SUBSCR(mod_name, xpath, cb) \
|
||||||
|
rc = sr_oper_get_subscribe(np2srv.sr_sess, mod_name, xpath, cb, NULL, 0, &np2srv.sr_data_sub); \
|
||||||
|
@@ -1203,12 +1205,19 @@ server_data_subscribe(void)
|
||||||
|
mod_name = "ietf-tls-common";
|
||||||
|
SR_OPER_SUBSCR(mod_name, "/ietf-tls-common:supported-algorithms", np2srv_tls_algs_oper_cb);
|
||||||
|
|
||||||
|
- /* password last modified oper data for both listen + call-home SSH users */
|
||||||
|
- mod_name = "ietf-netconf-server";
|
||||||
|
- SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint/ssh/"
|
||||||
|
- "ssh-server-parameters/client-authentication/users/user/password/last-modified", np2srv_password_last_modified_oper_cb);
|
||||||
|
- SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/call-home/netconf-client/endpoints/endpoint/ssh/"
|
||||||
|
- "ssh-server-parameters/client-authentication/users/user/password/last-modified", np2srv_password_last_modified_oper_cb);
|
||||||
|
+ ly_ctx = sr_acquire_context(np2srv.sr_conn);
|
||||||
|
+ mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-ssh-server");
|
||||||
|
+ sr_release_context(np2srv.sr_conn);
|
||||||
|
+ if (!lys_feature_value(mod, "local-users-supported")) {
|
||||||
|
+ /* password last modified oper data for both listen + call-home SSH users */
|
||||||
|
+ mod_name = "ietf-netconf-server";
|
||||||
|
+ SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint/ssh/"
|
||||||
|
+ "ssh-server-parameters/client-authentication/users/user/password/last-modified",
|
||||||
|
+ np2srv_password_last_modified_oper_cb);
|
||||||
|
+ SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/call-home/netconf-client/endpoints/endpoint/ssh/"
|
||||||
|
+ "ssh-server-parameters/client-authentication/users/user/password/last-modified",
|
||||||
|
+ np2srv_password_last_modified_oper_cb);
|
||||||
|
+ }
|
||||||
|
#endif /* NC_ENABLED_SSH_TLS */
|
||||||
|
|
||||||
|
/* subscriptions to running DS */
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
From d23806c941a16a8a12e006de240fd16afd963f47 Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||||
|
Date: Tue, 16 Dec 2025 08:15:54 +0100
|
||||||
|
Subject: [PATCH 4/4] Do not install YANG models, we do it in confd.
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeLists.txt | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||||
|
index 594363f..d449230 100644
|
||||||
|
--- a/CMakeLists.txt
|
||||||
|
+++ b/CMakeLists.txt
|
||||||
|
@@ -86,7 +86,7 @@ set(YANG_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/yang/modul
|
||||||
|
set(DATA_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/netopeer2" CACHE STRING "Directory with shared netopeer2 data")
|
||||||
|
|
||||||
|
# script options
|
||||||
|
-option(SYSREPO_SETUP "Install required modules with their default configuration into sysrepo using a script" ON)
|
||||||
|
+option(SYSREPO_SETUP "Install required modules with their default configuration into sysrepo using a script" OFF)
|
||||||
|
set(MODULES_PERMS 600 CACHE STRING "File access permissions set for all the server modules")
|
||||||
|
if(NOT MODULES_OWNER)
|
||||||
|
execute_process(COMMAND id -un RESULT_VARIABLE RET
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
@@ -0,0 +1,876 @@
|
|||||||
|
From 8c246567c03471e20cd9574f9c95a179712c3e3f Mon Sep 17 00:00:00 2001
|
||||||
|
From: Stefan Gula <steweg@gmail.com>
|
||||||
|
Date: Sun, 2 Nov 2025 21:26:55 +0100
|
||||||
|
Subject: [PATCH] cffi: allows to usage of libyang v4.2.2
|
||||||
|
|
||||||
|
This patch adjust cffi definitions and also all associated functions
|
||||||
|
based on libyang v4.2.2.
|
||||||
|
|
||||||
|
Signed-off-by: Stefan Gula <steweg@gmail.com>
|
||||||
|
---
|
||||||
|
cffi/cdefs.h | 61 ++++++++++++++++++++++-----------------
|
||||||
|
cffi/source.c | 4 +--
|
||||||
|
libyang/context.py | 25 ++++++++++------
|
||||||
|
libyang/data.py | 7 ++---
|
||||||
|
libyang/extension.py | 24 +++++++++++-----
|
||||||
|
libyang/schema.py | 66 +++++++++++++++++++++++++++++++++---------
|
||||||
|
tests/test_context.py | 6 +++-
|
||||||
|
tests/test_data.py | 67 ++++++++++++++++++++++++-------------------
|
||||||
|
tests/test_diff.py | 4 ++-
|
||||||
|
tests/test_schema.py | 4 +--
|
||||||
|
10 files changed, 172 insertions(+), 96 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/cffi/cdefs.h b/cffi/cdefs.h
|
||||||
|
index 8b6d426..82ec974 100644
|
||||||
|
--- a/cffi/cdefs.h
|
||||||
|
+++ b/cffi/cdefs.h
|
||||||
|
@@ -17,6 +17,7 @@ struct ly_ctx;
|
||||||
|
#define LY_CTX_LEAFREF_EXTENDED ...
|
||||||
|
#define LY_CTX_LEAFREF_LINKING ...
|
||||||
|
#define LY_CTX_BUILTIN_PLUGINS_ONLY ...
|
||||||
|
+#define LY_CTX_COMPILE_OBSOLETE ...
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
@@ -304,20 +305,19 @@ enum lyd_type {
|
||||||
|
LYD_TYPE_REPLY_RESTCONF
|
||||||
|
};
|
||||||
|
|
||||||
|
-#define LYD_PRINT_KEEPEMPTYCONT ...
|
||||||
|
#define LYD_PRINT_SHRINK ...
|
||||||
|
+#define LYD_PRINT_EMPTY_CONT ...
|
||||||
|
#define LYD_PRINT_WD_ALL ...
|
||||||
|
#define LYD_PRINT_WD_ALL_TAG ...
|
||||||
|
#define LYD_PRINT_WD_EXPLICIT ...
|
||||||
|
#define LYD_PRINT_WD_IMPL_TAG ...
|
||||||
|
#define LYD_PRINT_WD_MASK ...
|
||||||
|
-#define LYD_PRINT_WITHSIBLINGS ...
|
||||||
|
+#define LYD_PRINT_SIBLINGS ...
|
||||||
|
#define LYD_PRINT_WD_TRIM ...
|
||||||
|
LY_ERR lyd_print_mem(char **, const struct lyd_node *, LYD_FORMAT, uint32_t);
|
||||||
|
LY_ERR lyd_print_tree(struct ly_out *, const struct lyd_node *, LYD_FORMAT, uint32_t);
|
||||||
|
LY_ERR lyd_print_all(struct ly_out *, const struct lyd_node *, LYD_FORMAT, uint32_t);
|
||||||
|
|
||||||
|
-#define LYD_PARSE_LYB_MOD_UPDATE ...
|
||||||
|
#define LYD_PARSE_NO_STATE ...
|
||||||
|
#define LYD_PARSE_STORE_ONLY ...
|
||||||
|
#define LYD_PARSE_JSON_NULL ...
|
||||||
|
@@ -351,7 +351,7 @@ LY_ERR ly_out_new_file(FILE *, struct ly_out **);
|
||||||
|
LY_ERR ly_out_new_fd(int, struct ly_out **);
|
||||||
|
|
||||||
|
LY_ERR lyd_parse_data(const struct ly_ctx *, struct lyd_node *, struct ly_in *, LYD_FORMAT, uint32_t, uint32_t, struct lyd_node **);
|
||||||
|
-LY_ERR lyd_parse_op(const struct ly_ctx *, struct lyd_node *, struct ly_in *, LYD_FORMAT, enum lyd_type, struct lyd_node **, struct lyd_node **);
|
||||||
|
+LY_ERR lyd_parse_op(const struct ly_ctx *, struct lyd_node *, struct ly_in *, LYD_FORMAT, enum lyd_type, uint32_t, struct lyd_node **, struct lyd_node **);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LYS_OUT_UNKNOWN,
|
||||||
|
@@ -369,6 +369,7 @@ LY_ERR lys_print_module(struct ly_out *, const struct lys_module *, LYS_OUTFORMA
|
||||||
|
|
||||||
|
struct lysc_module {
|
||||||
|
struct lys_module *mod;
|
||||||
|
+ const char **features;
|
||||||
|
struct lysc_node *data;
|
||||||
|
struct lysc_node_action *rpcs;
|
||||||
|
struct lysc_node_notif *notifs;
|
||||||
|
@@ -388,13 +389,15 @@ struct lys_module {
|
||||||
|
const char *ref;
|
||||||
|
struct lysp_module *parsed;
|
||||||
|
struct lysc_module *compiled;
|
||||||
|
+ struct lysc_ext *extensions;
|
||||||
|
struct lysc_ident *identities;
|
||||||
|
+ struct lysc_submodule *submodules;
|
||||||
|
struct lys_module **augmented_by;
|
||||||
|
struct lys_module **deviated_by;
|
||||||
|
ly_bool implemented;
|
||||||
|
ly_bool to_compile;
|
||||||
|
- uint8_t latest_revision;
|
||||||
|
- ...;
|
||||||
|
+ uint8_t version : 2;
|
||||||
|
+ uint8_t latest_revision : 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lysp_module {
|
||||||
|
@@ -467,12 +470,11 @@ struct lysp_ext_instance {
|
||||||
|
const char *argument;
|
||||||
|
LY_VALUE_FORMAT format;
|
||||||
|
void *prefix_data;
|
||||||
|
- struct lysp_ext *def;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
void *parent;
|
||||||
|
enum ly_stmt parent_stmt;
|
||||||
|
uint64_t parent_stmt_index;
|
||||||
|
uint16_t flags;
|
||||||
|
- const struct lyplg_ext_record *record;
|
||||||
|
struct lysp_ext_substmt *substmts;
|
||||||
|
void *parsed;
|
||||||
|
struct lysp_stmt *child;
|
||||||
|
@@ -610,6 +612,11 @@ struct lysp_node_container {
|
||||||
|
...;
|
||||||
|
};
|
||||||
|
|
||||||
|
+struct lysc_value {
|
||||||
|
+ const char *str;
|
||||||
|
+ struct lysc_prefix *prefixes;
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
struct lysc_node_leaf {
|
||||||
|
union {
|
||||||
|
struct lysc_node node;
|
||||||
|
@@ -623,7 +630,7 @@ struct lysc_node_leaf {
|
||||||
|
struct lysc_when **when;
|
||||||
|
struct lysc_type *type;
|
||||||
|
const char *units;
|
||||||
|
- struct lyd_value *dflt;
|
||||||
|
+ struct lysc_value dflt;
|
||||||
|
...;
|
||||||
|
};
|
||||||
|
|
||||||
|
@@ -653,7 +660,7 @@ struct lysc_node_leaflist {
|
||||||
|
struct lysc_when **when;
|
||||||
|
struct lysc_type *type;
|
||||||
|
const char *units;
|
||||||
|
- struct lyd_value **dflts;
|
||||||
|
+ struct lysc_value *dflts;
|
||||||
|
uint32_t min;
|
||||||
|
uint32_t max;
|
||||||
|
...;
|
||||||
|
@@ -783,7 +790,7 @@ struct lysp_node_augment {
|
||||||
|
struct lysc_type {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
};
|
||||||
|
@@ -859,7 +866,7 @@ struct lysc_ext {
|
||||||
|
const char *name;
|
||||||
|
const char *argname;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_ext *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
struct lys_module *module;
|
||||||
|
uint16_t flags;
|
||||||
|
};
|
||||||
|
@@ -982,7 +989,6 @@ typedef struct pcre2_real_code pcre2_code;
|
||||||
|
|
||||||
|
struct lysc_pattern {
|
||||||
|
const char *expr;
|
||||||
|
- pcre2_code *code;
|
||||||
|
const char *dsc;
|
||||||
|
const char *ref;
|
||||||
|
const char *emsg;
|
||||||
|
@@ -1017,7 +1023,7 @@ struct lysc_ident {
|
||||||
|
struct lysc_type_num {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_range *range;
|
||||||
|
@@ -1026,7 +1032,7 @@ struct lysc_type_num {
|
||||||
|
struct lysc_type_dec {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
uint8_t fraction_digits;
|
||||||
|
@@ -1036,7 +1042,7 @@ struct lysc_type_dec {
|
||||||
|
struct lysc_type_str {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_range *length;
|
||||||
|
@@ -1058,7 +1064,7 @@ struct lysc_type_bitenum_item {
|
||||||
|
struct lysc_type_enum {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_type_bitenum_item *enums;
|
||||||
|
@@ -1067,7 +1073,7 @@ struct lysc_type_enum {
|
||||||
|
struct lysc_type_bits {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_type_bitenum_item *bits;
|
||||||
|
@@ -1076,7 +1082,7 @@ struct lysc_type_bits {
|
||||||
|
struct lysc_type_leafref {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lyxp_expr *path;
|
||||||
|
@@ -1088,7 +1094,7 @@ struct lysc_type_leafref {
|
||||||
|
struct lysc_type_identityref {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_ident **bases;
|
||||||
|
@@ -1097,7 +1103,7 @@ struct lysc_type_identityref {
|
||||||
|
struct lysc_type_instanceid {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
uint8_t require_instance;
|
||||||
|
@@ -1106,7 +1112,7 @@ struct lysc_type_instanceid {
|
||||||
|
struct lysc_type_union {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_type **types;
|
||||||
|
@@ -1115,7 +1121,7 @@ struct lysc_type_union {
|
||||||
|
struct lysc_type_bin {
|
||||||
|
const char *name;
|
||||||
|
struct lysc_ext_instance *exts;
|
||||||
|
- struct lyplg_type *plugin;
|
||||||
|
+ uintptr_t plugin_ref;
|
||||||
|
LY_DATA_TYPE basetype;
|
||||||
|
uint32_t refcount;
|
||||||
|
struct lysc_range *length;
|
||||||
|
@@ -1159,8 +1165,7 @@ typedef enum {
|
||||||
|
LYD_ANYDATA_DATATREE,
|
||||||
|
LYD_ANYDATA_STRING,
|
||||||
|
LYD_ANYDATA_XML,
|
||||||
|
- LYD_ANYDATA_JSON,
|
||||||
|
- LYD_ANYDATA_LYB
|
||||||
|
+ LYD_ANYDATA_JSON
|
||||||
|
} LYD_ANYDATA_VALUETYPE;
|
||||||
|
|
||||||
|
union lyd_any_value {
|
||||||
|
@@ -1168,7 +1173,6 @@ union lyd_any_value {
|
||||||
|
const char *str;
|
||||||
|
const char *xml;
|
||||||
|
const char *json;
|
||||||
|
- char *mem;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct lyd_node_any {
|
||||||
|
@@ -1320,6 +1324,8 @@ struct lyd_attr {
|
||||||
|
LY_ERR lyd_new_attr(struct lyd_node *, const char *, const char *, const char *, struct lyd_attr **);
|
||||||
|
void lyd_free_attr_single(const struct ly_ctx *ctx, struct lyd_attr *attr);
|
||||||
|
|
||||||
|
+LY_ERR lyd_value_validate_dflt(const struct lysc_node *, const char *, struct lysc_prefix *, const struct lyd_node *, const struct lysc_type **, const char **);
|
||||||
|
+
|
||||||
|
struct lyd_leafref_links_rec {
|
||||||
|
const struct lyd_node_term *node;
|
||||||
|
const struct lyd_node_term **leafref_nodes;
|
||||||
|
@@ -1328,13 +1334,14 @@ struct lyd_leafref_links_rec {
|
||||||
|
|
||||||
|
LY_ERR lyd_leafref_get_links(const struct lyd_node_term *, const struct lyd_leafref_links_rec **);
|
||||||
|
LY_ERR lyd_leafref_link_node_tree(struct lyd_node *);
|
||||||
|
+struct lyplg_ext *lysc_get_ext_plugin(uintptr_t);
|
||||||
|
const char *lyplg_ext_stmt2str(enum ly_stmt stmt);
|
||||||
|
const struct lysp_module *lyplg_ext_parse_get_cur_pmod(const struct lysp_ctx *);
|
||||||
|
struct ly_ctx *lyplg_ext_compile_get_ctx(const struct lysc_ctx *);
|
||||||
|
void lyplg_ext_parse_log(const struct lysp_ctx *, const struct lysp_ext_instance *, LY_LOG_LEVEL, LY_ERR, const char *, ...);
|
||||||
|
void lyplg_ext_compile_log(const struct lysc_ctx *, const struct lysc_ext_instance *, LY_LOG_LEVEL, LY_ERR, const char *, ...);
|
||||||
|
LY_ERR lyplg_ext_parse_extension_instance(struct lysp_ctx *, struct lysp_ext_instance *);
|
||||||
|
-LY_ERR lyplg_ext_compile_extension_instance(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *);
|
||||||
|
+LY_ERR lyplg_ext_compile_extension_instance(struct lysc_ctx *, const struct lysp_ext_instance *, struct lysc_ext_instance *, struct lysc_node *);
|
||||||
|
void lyplg_ext_pfree_instance_substatements(const struct ly_ctx *ctx, struct lysp_ext_substmt *substmts);
|
||||||
|
void lyplg_ext_cfree_instance_substatements(const struct ly_ctx *ctx, struct lysc_ext_substmt *substmts);
|
||||||
|
typedef LY_ERR (*lyplg_ext_parse_clb)(struct lysp_ctx *, struct lysp_ext_instance *);
|
||||||
|
diff --git a/cffi/source.c b/cffi/source.c
|
||||||
|
index 34e18b0..3c76e98 100644
|
||||||
|
--- a/cffi/source.c
|
||||||
|
+++ b/cffi/source.c
|
||||||
|
@@ -6,6 +6,6 @@
|
||||||
|
#include <libyang/libyang.h>
|
||||||
|
#include <libyang/version.h>
|
||||||
|
|
||||||
|
-#if LY_VERSION_MAJOR * 10000 + LY_VERSION_MINOR * 100 + LY_VERSION_MICRO < 30801
|
||||||
|
-#error "This version of libyang bindings only works with libyang soversion 3.8.1+"
|
||||||
|
+#if LY_VERSION_MAJOR * 10000 + LY_VERSION_MINOR * 100 + LY_VERSION_MICRO < 40202
|
||||||
|
+#error "This version of libyang bindings only works with libyang soversion 4.2.2+"
|
||||||
|
#endif
|
||||||
|
diff --git a/libyang/context.py b/libyang/context.py
|
||||||
|
index 4489680..21db162 100644
|
||||||
|
--- a/libyang/context.py
|
||||||
|
+++ b/libyang/context.py
|
||||||
|
@@ -205,6 +205,7 @@ def __init__(
|
||||||
|
builtin_plugins_only: bool = False,
|
||||||
|
all_implemented: bool = False,
|
||||||
|
enable_imp_features: bool = False,
|
||||||
|
+ compile_obsolete: bool = False,
|
||||||
|
yanglib_path: Optional[str] = None,
|
||||||
|
yanglib_fmt: str = "json",
|
||||||
|
cdata=None, # C type: "struct ly_ctx *"
|
||||||
|
@@ -231,6 +232,8 @@ def __init__(
|
||||||
|
options |= lib.LY_CTX_ALL_IMPLEMENTED
|
||||||
|
if enable_imp_features:
|
||||||
|
options |= lib.LY_CTX_ENABLE_IMP_FEATURES
|
||||||
|
+ if compile_obsolete:
|
||||||
|
+ options |= lib.LY_CTX_COMPILE_OBSOLETE
|
||||||
|
# force priv parsed
|
||||||
|
options |= lib.LY_CTX_SET_PRIV_PARSED
|
||||||
|
|
||||||
|
@@ -259,6 +262,7 @@ def __init__(
|
||||||
|
fmt = lib.LYD_JSON
|
||||||
|
else:
|
||||||
|
fmt = lib.LYD_XML
|
||||||
|
+ print("steweg", search_path, yanglib_path, yanglib_fmt, options)
|
||||||
|
ret = lib.ly_ctx_new_ylpath(
|
||||||
|
str2c(search_path), str2c(yanglib_path), fmt, options, ctx
|
||||||
|
)
|
||||||
|
@@ -542,6 +546,8 @@ def parse_op(
|
||||||
|
in_data: Union[IO, str],
|
||||||
|
dtype: DataType,
|
||||||
|
parent: DNode = None,
|
||||||
|
+ opaq: bool = False,
|
||||||
|
+ strict: bool = False,
|
||||||
|
) -> DNode:
|
||||||
|
fmt = data_format(fmt)
|
||||||
|
data = ffi.new("struct ly_in **")
|
||||||
|
@@ -551,13 +557,14 @@ def parse_op(
|
||||||
|
if ret != lib.LY_SUCCESS:
|
||||||
|
raise self.error("failed to read input data")
|
||||||
|
|
||||||
|
+ flags = parser_flags(opaq=opaq, strict=strict)
|
||||||
|
tree = ffi.new("struct lyd_node **", ffi.NULL)
|
||||||
|
op = ffi.new("struct lyd_node **", ffi.NULL)
|
||||||
|
par = ffi.new("struct lyd_node **", ffi.NULL)
|
||||||
|
if parent is not None:
|
||||||
|
par[0] = parent.cdata
|
||||||
|
|
||||||
|
- ret = lib.lyd_parse_op(self.cdata, par[0], data[0], fmt, dtype, tree, op)
|
||||||
|
+ ret = lib.lyd_parse_op(self.cdata, par[0], data[0], fmt, dtype, flags, tree, op)
|
||||||
|
lib.ly_in_free(data[0], 0)
|
||||||
|
if ret != lib.LY_SUCCESS:
|
||||||
|
raise self.error("failed to parse input data")
|
||||||
|
@@ -570,9 +577,17 @@ def parse_op_mem(
|
||||||
|
data: str,
|
||||||
|
dtype: DataType = DataType.DATA_YANG,
|
||||||
|
parent: DNode = None,
|
||||||
|
+ opaq: bool = False,
|
||||||
|
+ strict: bool = False,
|
||||||
|
):
|
||||||
|
return self.parse_op(
|
||||||
|
- fmt, in_type=IOType.MEMORY, in_data=data, dtype=dtype, parent=parent
|
||||||
|
+ fmt,
|
||||||
|
+ in_type=IOType.MEMORY,
|
||||||
|
+ in_data=data,
|
||||||
|
+ dtype=dtype,
|
||||||
|
+ parent=parent,
|
||||||
|
+ opaq=opaq,
|
||||||
|
+ strict=strict,
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse_data(
|
||||||
|
@@ -581,7 +596,6 @@ def parse_data(
|
||||||
|
in_type: IOType,
|
||||||
|
in_data: Union[str, bytes, IO],
|
||||||
|
parent: DNode = None,
|
||||||
|
- lyb_mod_update: bool = False,
|
||||||
|
no_state: bool = False,
|
||||||
|
parse_only: bool = False,
|
||||||
|
opaq: bool = False,
|
||||||
|
@@ -596,7 +610,6 @@ def parse_data(
|
||||||
|
if self.cdata is None:
|
||||||
|
raise RuntimeError("context already destroyed")
|
||||||
|
parser_flgs = parser_flags(
|
||||||
|
- lyb_mod_update=lyb_mod_update,
|
||||||
|
no_state=no_state,
|
||||||
|
parse_only=parse_only,
|
||||||
|
opaq=opaq,
|
||||||
|
@@ -640,7 +653,6 @@ def parse_data_mem(
|
||||||
|
data: Union[str, bytes],
|
||||||
|
fmt: str,
|
||||||
|
parent: DNode = None,
|
||||||
|
- lyb_mod_update: bool = False,
|
||||||
|
no_state: bool = False,
|
||||||
|
parse_only: bool = False,
|
||||||
|
opaq: bool = False,
|
||||||
|
@@ -657,7 +669,6 @@ def parse_data_mem(
|
||||||
|
in_type=IOType.MEMORY,
|
||||||
|
in_data=data,
|
||||||
|
parent=parent,
|
||||||
|
- lyb_mod_update=lyb_mod_update,
|
||||||
|
no_state=no_state,
|
||||||
|
parse_only=parse_only,
|
||||||
|
opaq=opaq,
|
||||||
|
@@ -675,7 +686,6 @@ def parse_data_file(
|
||||||
|
fileobj: IO,
|
||||||
|
fmt: str,
|
||||||
|
parent: DNode = None,
|
||||||
|
- lyb_mod_update: bool = False,
|
||||||
|
no_state: bool = False,
|
||||||
|
parse_only: bool = False,
|
||||||
|
opaq: bool = False,
|
||||||
|
@@ -692,7 +702,6 @@ def parse_data_file(
|
||||||
|
in_type=IOType.FD,
|
||||||
|
in_data=fileobj,
|
||||||
|
parent=parent,
|
||||||
|
- lyb_mod_update=lyb_mod_update,
|
||||||
|
no_state=no_state,
|
||||||
|
parse_only=parse_only,
|
||||||
|
opaq=opaq,
|
||||||
|
diff --git a/libyang/data.py b/libyang/data.py
|
||||||
|
index 7a5887b..230c51a 100644
|
||||||
|
--- a/libyang/data.py
|
||||||
|
+++ b/libyang/data.py
|
||||||
|
@@ -53,11 +53,11 @@ def printer_flags(
|
||||||
|
) -> int:
|
||||||
|
flags = 0
|
||||||
|
if with_siblings:
|
||||||
|
- flags |= lib.LYD_PRINT_WITHSIBLINGS
|
||||||
|
+ flags |= lib.LYD_PRINT_SIBLINGS
|
||||||
|
if not pretty:
|
||||||
|
flags |= lib.LYD_PRINT_SHRINK
|
||||||
|
if keep_empty_containers:
|
||||||
|
- flags |= lib.LYD_PRINT_KEEPEMPTYCONT
|
||||||
|
+ flags |= lib.LYD_PRINT_EMPTY_CONT
|
||||||
|
if trim_default_values:
|
||||||
|
flags |= lib.LYD_PRINT_WD_TRIM
|
||||||
|
if include_implicit_defaults:
|
||||||
|
@@ -109,7 +109,6 @@ def newval_flags(
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------------
|
||||||
|
def parser_flags(
|
||||||
|
- lyb_mod_update: bool = False,
|
||||||
|
no_state: bool = False,
|
||||||
|
parse_only: bool = False,
|
||||||
|
opaq: bool = False,
|
||||||
|
@@ -120,8 +119,6 @@ def parser_flags(
|
||||||
|
json_string_datatypes: bool = False,
|
||||||
|
) -> int:
|
||||||
|
flags = 0
|
||||||
|
- if lyb_mod_update:
|
||||||
|
- flags |= lib.LYD_PARSE_LYB_MOD_UPDATE
|
||||||
|
if no_state:
|
||||||
|
flags |= lib.LYD_PARSE_NO_STATE
|
||||||
|
if parse_only:
|
||||||
|
diff --git a/libyang/extension.py b/libyang/extension.py
|
||||||
|
index 57f7cb2..f7e4ba9 100644
|
||||||
|
--- a/libyang/extension.py
|
||||||
|
+++ b/libyang/extension.py
|
||||||
|
@@ -8,7 +8,7 @@
|
||||||
|
from _libyang import ffi, lib
|
||||||
|
from .context import Context
|
||||||
|
from .log import get_libyang_level
|
||||||
|
-from .schema import ExtensionCompiled, ExtensionParsed, Module
|
||||||
|
+from .schema import ExtensionCompiled, ExtensionParsed, Module, SNode
|
||||||
|
from .util import LibyangError, c2str, str2c
|
||||||
|
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@ def __init__(self, message: str, ret: int, log_level: int) -> None:
|
||||||
|
|
||||||
|
@ffi.def_extern(name="lypy_lyplg_ext_parse_clb")
|
||||||
|
def libyang_c_lyplg_ext_parse_clb(pctx, pext):
|
||||||
|
- plugin = extensions_plugins[pext.record.plugin]
|
||||||
|
+ plugin = extensions_plugins[lib.lysc_get_ext_plugin(pext.plugin_ref)]
|
||||||
|
module_cdata = lib.lyplg_ext_parse_get_cur_pmod(pctx).mod
|
||||||
|
context = Context(cdata=module_cdata.ctx)
|
||||||
|
module = Module(context, module_cdata)
|
||||||
|
@@ -46,7 +46,7 @@ def libyang_c_lyplg_ext_parse_clb(pctx, pext):
|
||||||
|
|
||||||
|
@ffi.def_extern(name="lypy_lyplg_ext_compile_clb")
|
||||||
|
def libyang_c_lyplg_ext_compile_clb(cctx, pext, cext):
|
||||||
|
- plugin = extensions_plugins[pext.record.plugin]
|
||||||
|
+ plugin = extensions_plugins[lib.lysc_get_ext_plugin(pext.plugin_ref)]
|
||||||
|
context = Context(cdata=lib.lyplg_ext_compile_get_ctx(cctx))
|
||||||
|
module = Module(context, cext.module)
|
||||||
|
parsed_ext = ExtensionParsed(context, pext, module)
|
||||||
|
@@ -67,7 +67,7 @@ def libyang_c_lyplg_ext_compile_clb(cctx, pext, cext):
|
||||||
|
|
||||||
|
@ffi.def_extern(name="lypy_lyplg_ext_parse_free_clb")
|
||||||
|
def libyang_c_lyplg_ext_parse_free_clb(ctx, pext):
|
||||||
|
- plugin = extensions_plugins[pext.record.plugin]
|
||||||
|
+ plugin = extensions_plugins[lib.lysc_get_ext_plugin(pext.plugin_ref)]
|
||||||
|
context = Context(cdata=ctx)
|
||||||
|
parsed_ext = ExtensionParsed(context, pext, None)
|
||||||
|
plugin.parse_free_clb(parsed_ext)
|
||||||
|
@@ -75,7 +75,9 @@ def libyang_c_lyplg_ext_parse_free_clb(ctx, pext):
|
||||||
|
|
||||||
|
@ffi.def_extern(name="lypy_lyplg_ext_compile_free_clb")
|
||||||
|
def libyang_c_lyplg_ext_compile_free_clb(ctx, cext):
|
||||||
|
- plugin = extensions_plugins[getattr(cext, "def").plugin]
|
||||||
|
+ plugin = extensions_plugins[
|
||||||
|
+ lib.lysc_get_ext_plugin(getattr(cext, "def").plugin_ref)
|
||||||
|
+ ]
|
||||||
|
context = Context(cdata=ctx)
|
||||||
|
compiled_ext = ExtensionCompiled(context, cext)
|
||||||
|
plugin.compile_free_clb(compiled_ext)
|
||||||
|
@@ -200,9 +202,17 @@ def set_compile_ctx(self, cctx) -> None:
|
||||||
|
def parse_substmts(self, ext: ExtensionParsed) -> int:
|
||||||
|
return lib.lyplg_ext_parse_extension_instance(self._pctx, ext.cdata)
|
||||||
|
|
||||||
|
- def compile_substmts(self, pext: ExtensionParsed, cext: ExtensionCompiled) -> int:
|
||||||
|
+ def compile_substmts(
|
||||||
|
+ self,
|
||||||
|
+ pext: ExtensionParsed,
|
||||||
|
+ cext: ExtensionCompiled,
|
||||||
|
+ parent: Optional[SNode] = None,
|
||||||
|
+ ) -> int:
|
||||||
|
return lib.lyplg_ext_compile_extension_instance(
|
||||||
|
- self._cctx, pext.cdata, cext.cdata
|
||||||
|
+ self._cctx,
|
||||||
|
+ pext.cdata,
|
||||||
|
+ cext.cdata,
|
||||||
|
+ ffi.NULL if parent is None else parent.cdata,
|
||||||
|
)
|
||||||
|
|
||||||
|
def free_parse_substmts(self, ext: ExtensionParsed) -> None:
|
||||||
|
diff --git a/libyang/schema.py b/libyang/schema.py
|
||||||
|
index 23c1b1b..f104a87 100644
|
||||||
|
--- a/libyang/schema.py
|
||||||
|
+++ b/libyang/schema.py
|
||||||
|
@@ -1522,21 +1522,35 @@ def new(context: "libyang.Context", cdata) -> "SNode":
|
||||||
|
# -------------------------------------------------------------------------------------
|
||||||
|
@SNode.register(SNode.LEAF)
|
||||||
|
class SLeaf(SNode):
|
||||||
|
- __slots__ = ("cdata_leaf", "cdata_leaf_parsed")
|
||||||
|
+ __slots__ = ("cdata_leaf", "cdata_leaf_parsed", "cdata_default_realtype")
|
||||||
|
|
||||||
|
def __init__(self, context: "libyang.Context", cdata):
|
||||||
|
super().__init__(context, cdata)
|
||||||
|
self.cdata_leaf = ffi.cast("struct lysc_node_leaf *", cdata)
|
||||||
|
self.cdata_leaf_parsed = ffi.cast("struct lysp_node_leaf *", self.cdata_parsed)
|
||||||
|
+ self.cdata_default_realtype = None
|
||||||
|
|
||||||
|
def default(self) -> Union[None, bool, int, str, float]:
|
||||||
|
- if not self.cdata_leaf.dflt:
|
||||||
|
+ if not self.cdata_leaf.dflt.str:
|
||||||
|
return None
|
||||||
|
- val = lib.lyd_value_get_canonical(self.context.cdata, self.cdata_leaf.dflt)
|
||||||
|
- if not val:
|
||||||
|
- return None
|
||||||
|
- val = c2str(val)
|
||||||
|
- val_type = Type(self.context, self.cdata_leaf.dflt.realtype, None)
|
||||||
|
+
|
||||||
|
+ if self.cdata_default_realtype is None:
|
||||||
|
+ # calculate real type of default value just once
|
||||||
|
+ val_type_cdata = ffi.new("struct lysc_type **", ffi.NULL)
|
||||||
|
+ ret = lib.lyd_value_validate_dflt(
|
||||||
|
+ self.cdata,
|
||||||
|
+ self.cdata_leaf.dflt.str,
|
||||||
|
+ self.cdata_leaf.dflt.prefixes,
|
||||||
|
+ ffi.NULL,
|
||||||
|
+ val_type_cdata,
|
||||||
|
+ ffi.NULL,
|
||||||
|
+ )
|
||||||
|
+ if ret != lib.LY_SUCCESS:
|
||||||
|
+ raise self.context.error("Unable to get real type of default value")
|
||||||
|
+ self.cdata_default_realtype = Type(self.context, val_type_cdata[0], None)
|
||||||
|
+
|
||||||
|
+ val = c2str(self.cdata_leaf.dflt.str)
|
||||||
|
+ val_type = self.cdata_default_realtype
|
||||||
|
if val_type.base() == Type.BOOL:
|
||||||
|
return val == "true"
|
||||||
|
if val_type.base() in Type.NUM_TYPES:
|
||||||
|
@@ -1563,7 +1577,7 @@ def __str__(self):
|
||||||
|
# -------------------------------------------------------------------------------------
|
||||||
|
@SNode.register(SNode.LEAFLIST)
|
||||||
|
class SLeafList(SNode):
|
||||||
|
- __slots__ = ("cdata_leaflist", "cdata_leaflist_parsed")
|
||||||
|
+ __slots__ = ("cdata_leaflist", "cdata_leaflist_parsed", "cdata_default_realtypes")
|
||||||
|
|
||||||
|
def __init__(self, context: "libyang.Context", cdata):
|
||||||
|
super().__init__(context, cdata)
|
||||||
|
@@ -1571,6 +1585,7 @@ def __init__(self, context: "libyang.Context", cdata):
|
||||||
|
self.cdata_leaflist_parsed = ffi.cast(
|
||||||
|
"struct lysp_node_leaflist *", self.cdata_parsed
|
||||||
|
)
|
||||||
|
+ self.cdata_default_realtypes = None
|
||||||
|
|
||||||
|
def ordered(self) -> bool:
|
||||||
|
return bool(self.cdata_parsed.flags & lib.LYS_ORDBY_USER)
|
||||||
|
@@ -1586,12 +1601,37 @@ def type(self) -> Type:
|
||||||
|
def defaults(self) -> Iterator[Union[None, bool, int, str, float]]:
|
||||||
|
if self.cdata_leaflist.dflts == ffi.NULL:
|
||||||
|
return
|
||||||
|
- for dflt in ly_array_iter(self.cdata_leaflist.dflts):
|
||||||
|
- val = lib.lyd_value_get_canonical(self.context.cdata, dflt)
|
||||||
|
- if not val:
|
||||||
|
+
|
||||||
|
+ if self.cdata_default_realtypes is None:
|
||||||
|
+ # calculate real types of default values just once
|
||||||
|
+ val_type_cdata = ffi.new("struct lysc_type **", ffi.NULL)
|
||||||
|
+ self.cdata_default_realtypes = []
|
||||||
|
+ for dflt in ly_array_iter(self.cdata_leaflist.dflts):
|
||||||
|
+ if not dflt.str:
|
||||||
|
+ self.cdata_default_realtypes.append(None)
|
||||||
|
+ continue
|
||||||
|
+ val_type_cdata[0] = ffi.NULL
|
||||||
|
+ ret = lib.lyd_value_validate_dflt(
|
||||||
|
+ self.cdata,
|
||||||
|
+ dflt.str,
|
||||||
|
+ dflt.prefixes,
|
||||||
|
+ ffi.NULL,
|
||||||
|
+ val_type_cdata,
|
||||||
|
+ ffi.NULL,
|
||||||
|
+ )
|
||||||
|
+ if ret != lib.LY_SUCCESS:
|
||||||
|
+ raise self.context.error("Unable to get real type of default value")
|
||||||
|
+ self.cdata_default_realtypes.append(
|
||||||
|
+ Type(self.context, val_type_cdata[0], None)
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ for dflt, val_type in zip(
|
||||||
|
+ ly_array_iter(self.cdata_leaflist.dflts), self.cdata_default_realtypes
|
||||||
|
+ ):
|
||||||
|
+ if not dflt.str:
|
||||||
|
yield None
|
||||||
|
- val = c2str(val)
|
||||||
|
- val_type = Type(self.context, dflt.realtype, None)
|
||||||
|
+ continue
|
||||||
|
+ val = c2str(dflt.str)
|
||||||
|
if val_type.base() == Type.BOOL:
|
||||||
|
yield val == "true"
|
||||||
|
elif val_type.base() in Type.NUM_TYPES:
|
||||||
|
diff --git a/tests/test_context.py b/tests/test_context.py
|
||||||
|
index 61c0ae8..8ffd045 100644
|
||||||
|
--- a/tests/test_context.py
|
||||||
|
+++ b/tests/test_context.py
|
||||||
|
@@ -20,7 +20,11 @@ def test_ctx_no_dir(self):
|
||||||
|
self.assertIsNot(ctx, None)
|
||||||
|
|
||||||
|
def test_ctx_yanglib(self):
|
||||||
|
- ctx = Context(YANG_DIR, yanglib_path=YANG_DIR + "/yang-library.json")
|
||||||
|
+ ctx = Context(
|
||||||
|
+ YANG_DIR,
|
||||||
|
+ yanglib_path=YANG_DIR + "/yang-library.json",
|
||||||
|
+ compile_obsolete=True,
|
||||||
|
+ )
|
||||||
|
ctx.load_module("yolo-system")
|
||||||
|
dnode = ctx.get_yanglib_data()
|
||||||
|
j = dnode.print_mem("json", with_siblings=True)
|
||||||
|
diff --git a/tests/test_data.py b/tests/test_data.py
|
||||||
|
index 430873a..8f56dc1 100644
|
||||||
|
--- a/tests/test_data.py
|
||||||
|
+++ b/tests/test_data.py
|
||||||
|
@@ -33,7 +33,7 @@
|
||||||
|
# -------------------------------------------------------------------------------------
|
||||||
|
class DataTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
- self.ctx = Context(YANG_DIR)
|
||||||
|
+ self.ctx = Context(YANG_DIR, compile_obsolete=True)
|
||||||
|
modules = [
|
||||||
|
self.ctx.load_module("ietf-netconf"),
|
||||||
|
self.ctx.load_module("yolo-system"),
|
||||||
|
@@ -51,18 +51,18 @@ def tearDown(self):
|
||||||
|
"yolo-system:conf": {
|
||||||
|
"hostname": "foo",
|
||||||
|
"url": [
|
||||||
|
- {
|
||||||
|
- "proto": "https",
|
||||||
|
- "host": "github.com",
|
||||||
|
- "path": "/CESNET/libyang-python",
|
||||||
|
- "enabled": false
|
||||||
|
- },
|
||||||
|
{
|
||||||
|
"proto": "http",
|
||||||
|
"host": "foobar.com",
|
||||||
|
"port": 8080,
|
||||||
|
"path": "/index.html",
|
||||||
|
"enabled": true
|
||||||
|
+ },
|
||||||
|
+ {
|
||||||
|
+ "proto": "https",
|
||||||
|
+ "host": "github.com",
|
||||||
|
+ "path": "/CESNET/libyang-python",
|
||||||
|
+ "enabled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"number": [
|
||||||
|
@@ -76,7 +76,9 @@ def tearDown(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_data_parse_config_json(self):
|
||||||
|
- dnode = self.ctx.parse_data_mem(self.JSON_CONFIG, "json", no_state=True)
|
||||||
|
+ dnode = self.ctx.parse_data_mem(
|
||||||
|
+ self.JSON_CONFIG, "json", no_state=True, ordered=True
|
||||||
|
+ )
|
||||||
|
self.assertIsInstance(dnode, DContainer)
|
||||||
|
try:
|
||||||
|
j = dnode.print_mem("json", with_siblings=True)
|
||||||
|
@@ -91,18 +93,18 @@ def test_data_parse_config_json(self):
|
||||||
|
"yolo-system:conf": {
|
||||||
|
"hostname": "foo",
|
||||||
|
"url": [
|
||||||
|
- {
|
||||||
|
- "proto": "https",
|
||||||
|
- "host": "github.com",
|
||||||
|
- "path": "/CESNET/libyang-python",
|
||||||
|
- "enabled": false
|
||||||
|
- },
|
||||||
|
{
|
||||||
|
"proto": "http",
|
||||||
|
"host": "foobar.com",
|
||||||
|
"port": 8080,
|
||||||
|
"path": "/index.html",
|
||||||
|
"enabled": true
|
||||||
|
+ },
|
||||||
|
+ {
|
||||||
|
+ "proto": "https",
|
||||||
|
+ "host": "github.com",
|
||||||
|
+ "path": "/CESNET/libyang-python",
|
||||||
|
+ "enabled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"number": [
|
||||||
|
@@ -128,12 +130,6 @@ def test_data_parse_config_json_without_yang_lib(self):
|
||||||
|
"yolo-system:conf": {
|
||||||
|
"hostname": "foo",
|
||||||
|
"url": [
|
||||||
|
- {
|
||||||
|
- "proto": "https",
|
||||||
|
- "host": "github.com",
|
||||||
|
- "path": "/CESNET/libyang-python",
|
||||||
|
- "enabled": false
|
||||||
|
- },
|
||||||
|
{
|
||||||
|
"proto": "http",
|
||||||
|
"host": "barfoo.com",
|
||||||
|
@@ -145,6 +141,12 @@ def test_data_parse_config_json_without_yang_lib(self):
|
||||||
|
"port": 8080,
|
||||||
|
"path": "/index.html",
|
||||||
|
"enabled": true
|
||||||
|
+ },
|
||||||
|
+ {
|
||||||
|
+ "proto": "https",
|
||||||
|
+ "host": "github.com",
|
||||||
|
+ "path": "/CESNET/libyang-python",
|
||||||
|
+ "enabled": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"number": [
|
||||||
|
@@ -254,7 +256,9 @@ def test_data_parse_state_json(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_data_parse_config_xml(self):
|
||||||
|
- dnode = self.ctx.parse_data_mem(self.XML_CONFIG, "xml", validate_present=True)
|
||||||
|
+ dnode = self.ctx.parse_data_mem(
|
||||||
|
+ self.XML_CONFIG, "xml", validate_present=True, ordered=True
|
||||||
|
+ )
|
||||||
|
self.assertIsInstance(dnode, DContainer)
|
||||||
|
try:
|
||||||
|
xml = dnode.print_mem("xml", with_siblings=True, trim_default_values=True)
|
||||||
|
@@ -509,6 +513,7 @@ def test_data_from_dict_module_free_func(self):
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_data_from_dict_module_with_prefix(self):
|
||||||
|
+ self.maxDiff = None
|
||||||
|
module = self.ctx.get_module("yolo-system")
|
||||||
|
dnode = module.parse_data_dict(
|
||||||
|
self.DICT_CONFIG_WITH_PREFIX, strict=True, validate_present=True
|
||||||
|
@@ -853,17 +858,17 @@ def test_data_diff(self):
|
||||||
|
TREE = [
|
||||||
|
"/yolo-system:conf",
|
||||||
|
"/yolo-system:conf/hostname",
|
||||||
|
- "/yolo-system:conf/url[proto='https'][host='github.com']",
|
||||||
|
- "/yolo-system:conf/url[proto='https'][host='github.com']/proto",
|
||||||
|
- "/yolo-system:conf/url[proto='https'][host='github.com']/host",
|
||||||
|
- "/yolo-system:conf/url[proto='https'][host='github.com']/path",
|
||||||
|
- "/yolo-system:conf/url[proto='https'][host='github.com']/enabled",
|
||||||
|
"/yolo-system:conf/url[proto='http'][host='foobar.com']",
|
||||||
|
"/yolo-system:conf/url[proto='http'][host='foobar.com']/proto",
|
||||||
|
"/yolo-system:conf/url[proto='http'][host='foobar.com']/host",
|
||||||
|
"/yolo-system:conf/url[proto='http'][host='foobar.com']/port",
|
||||||
|
"/yolo-system:conf/url[proto='http'][host='foobar.com']/path",
|
||||||
|
"/yolo-system:conf/url[proto='http'][host='foobar.com']/enabled",
|
||||||
|
+ "/yolo-system:conf/url[proto='https'][host='github.com']",
|
||||||
|
+ "/yolo-system:conf/url[proto='https'][host='github.com']/proto",
|
||||||
|
+ "/yolo-system:conf/url[proto='https'][host='github.com']/host",
|
||||||
|
+ "/yolo-system:conf/url[proto='https'][host='github.com']/path",
|
||||||
|
+ "/yolo-system:conf/url[proto='https'][host='github.com']/enabled",
|
||||||
|
"/yolo-system:conf/number[.='1000']",
|
||||||
|
"/yolo-system:conf/number[.='2000']",
|
||||||
|
"/yolo-system:conf/number[.='3000']",
|
||||||
|
@@ -906,7 +911,7 @@ def test_find_all(self):
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
- self.assertEqual(urls[0].print_dict(absolute=False), expected_url)
|
||||||
|
+ self.assertEqual(urls[1].print_dict(absolute=False), expected_url)
|
||||||
|
finally:
|
||||||
|
dnode.free()
|
||||||
|
|
||||||
|
@@ -1090,7 +1095,9 @@ def test_dnode_leafref_linking(self):
|
||||||
|
"yolo-leafref-extended:ref1": "val1"
|
||||||
|
}"""
|
||||||
|
self.ctx.destroy()
|
||||||
|
- self.ctx = Context(YANG_DIR, leafref_extended=True, leafref_linking=True)
|
||||||
|
+ self.ctx = Context(
|
||||||
|
+ YANG_DIR, leafref_extended=True, leafref_linking=True, compile_obsolete=True
|
||||||
|
+ )
|
||||||
|
mod = self.ctx.load_module("yolo-leafref-extended")
|
||||||
|
self.assertIsInstance(mod, Module)
|
||||||
|
dnode1 = self.ctx.parse_data_mem(MAIN, "json", parse_only=True)
|
||||||
|
@@ -1121,7 +1128,7 @@ def test_dnode_builtin_plugins_only(self):
|
||||||
|
MAIN = {"yolo-nodetypes:ip-address": "test"}
|
||||||
|
self.tearDown()
|
||||||
|
gc.collect()
|
||||||
|
- self.ctx = Context(YANG_DIR, builtin_plugins_only=True)
|
||||||
|
+ self.ctx = Context(YANG_DIR, builtin_plugins_only=True, compile_obsolete=True)
|
||||||
|
module = self.ctx.load_module("yolo-nodetypes")
|
||||||
|
dnode = dict_to_dnode(MAIN, module, None, validate=False, store_only=True)
|
||||||
|
self.assertIsInstance(dnode, DLeaf)
|
||||||
|
@@ -1140,7 +1147,7 @@ def test_merge_builtin_plugins_only(self):
|
||||||
|
MAIN = {"yolo-nodetypes:ip-address": "test"}
|
||||||
|
self.tearDown()
|
||||||
|
gc.collect()
|
||||||
|
- self.ctx = Context(YANG_DIR, builtin_plugins_only=True)
|
||||||
|
+ self.ctx = Context(YANG_DIR, builtin_plugins_only=True, compile_obsolete=True)
|
||||||
|
module = self.ctx.load_module("yolo-nodetypes")
|
||||||
|
dnode = module.parse_data_dict(MAIN, validate=False, store_only=True)
|
||||||
|
self.assertIsInstance(dnode, DLeaf)
|
||||||
|
diff --git a/tests/test_diff.py b/tests/test_diff.py
|
||||||
|
index d4b7e87..2263e34 100644
|
||||||
|
--- a/tests/test_diff.py
|
||||||
|
+++ b/tests/test_diff.py
|
||||||
|
@@ -97,7 +97,9 @@ class DiffTest(unittest.TestCase):
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_diff(self):
|
||||||
|
- with Context(OLD_YANG_DIR) as ctx_old, Context(NEW_YANG_DIR) as ctx_new:
|
||||||
|
+ with Context(OLD_YANG_DIR, compile_obsolete=True) as ctx_old, Context(
|
||||||
|
+ NEW_YANG_DIR, compile_obsolete=True
|
||||||
|
+ ) as ctx_new:
|
||||||
|
mod = ctx_old.load_module("yolo-system")
|
||||||
|
mod.feature_enable_all()
|
||||||
|
mod = ctx_new.load_module("yolo-system")
|
||||||
|
diff --git a/tests/test_schema.py b/tests/test_schema.py
|
||||||
|
index b1862ab..a1ca412 100644
|
||||||
|
--- a/tests/test_schema.py
|
||||||
|
+++ b/tests/test_schema.py
|
||||||
|
@@ -272,7 +272,7 @@ def test_iffeature_dump(self):
|
||||||
|
# -------------------------------------------------------------------------------------
|
||||||
|
class ContainerTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
- self.ctx = Context(YANG_DIR)
|
||||||
|
+ self.ctx = Context(YANG_DIR, compile_obsolete=True)
|
||||||
|
mod = self.ctx.load_module("yolo-system")
|
||||||
|
mod.feature_enable_all()
|
||||||
|
self.container = next(self.ctx.find_path("/yolo-system:conf"))
|
||||||
|
@@ -549,7 +549,7 @@ def test_rpc_parsed(self):
|
||||||
|
# -------------------------------------------------------------------------------------
|
||||||
|
class LeafTypeTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
- self.ctx = Context(YANG_DIR)
|
||||||
|
+ self.ctx = Context(YANG_DIR, compile_obsolete=True)
|
||||||
|
self.ctx.load_module("yolo-system")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
From e1ebfda6318d25804f3344caa89ce3f723b576e4 Mon Sep 17 00:00:00 2001
|
From efe7706fd7397c2feb384afea00ee97e74287df0 Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Tue, 28 Mar 2023 10:37:53 +0200
|
Date: Tue, 28 Mar 2023 10:37:53 +0200
|
||||||
Subject: [PATCH 1/7] sysrepo-plugind: add support for running in foreground
|
Subject: [PATCH 1/8] sysrepo-plugind: add support for running in foreground
|
||||||
with syslog
|
with syslog
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
+16
-16
@@ -1,7 +1,7 @@
|
|||||||
From a190da4e967fb7ed65bc06794e8d775b4e1a7b84 Mon Sep 17 00:00:00 2001
|
From 11b9938206cf2bafc456bb22e14c7f85a604760c Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Tue, 7 May 2024 15:41:53 +0200
|
Date: Tue, 7 May 2024 15:41:53 +0200
|
||||||
Subject: [PATCH 2/7] Allow SR_EV_DONE to return any error to sysrepocfg
|
Subject: [PATCH 2/8] Allow SR_EV_DONE to return any error to sysrepocfg
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -28,10 +28,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
3 files changed, 34 insertions(+), 12 deletions(-)
|
3 files changed, 34 insertions(+), 12 deletions(-)
|
||||||
|
|
||||||
diff --git a/src/shm_sub.c b/src/shm_sub.c
|
diff --git a/src/shm_sub.c b/src/shm_sub.c
|
||||||
index 61aab4c6..6ba1cd4d 100644
|
index db0a2e0f..f64b85d6 100644
|
||||||
--- a/src/shm_sub.c
|
--- a/src/shm_sub.c
|
||||||
+++ b/src/shm_sub.c
|
+++ b/src/shm_sub.c
|
||||||
@@ -1832,7 +1832,7 @@ cleanup:
|
@@ -1775,7 +1775,7 @@ cleanup:
|
||||||
|
|
||||||
sr_error_info_t *
|
sr_error_info_t *
|
||||||
sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name, const void *orig_data,
|
sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name, const void *orig_data,
|
||||||
@@ -40,7 +40,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
{
|
{
|
||||||
sr_error_info_t *err_info = NULL;
|
sr_error_info_t *err_info = NULL;
|
||||||
struct sr_mod_info_mod_s *mod = NULL;
|
struct sr_mod_info_mod_s *mod = NULL;
|
||||||
@@ -1980,12 +1980,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *
|
@@ -1923,12 +1923,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *
|
||||||
sr_rwunlock(&nsub->sub_shm->lock, 0, SR_LOCK_WRITE, cid, __func__);
|
sr_rwunlock(&nsub->sub_shm->lock, 0, SR_LOCK_WRITE, cid, __func__);
|
||||||
nsub->lock = SR_LOCK_NONE;
|
nsub->lock = SR_LOCK_NONE;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
nsub->pending_event = 0;
|
nsub->pending_event = 0;
|
||||||
}
|
}
|
||||||
} while (1);
|
} while (1);
|
||||||
@@ -3557,7 +3564,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc
|
@@ -3562,7 +3569,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc
|
||||||
sr_error_info_t *
|
sr_error_info_t *
|
||||||
sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_subs, sr_conn_ctx_t *conn)
|
sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_subs, sr_conn_ctx_t *conn)
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
uint32_t i, data_len = 0, valid_subscr_count, remaining_subs;
|
uint32_t i, data_len = 0, valid_subscr_count, remaining_subs;
|
||||||
char *data = NULL, *shm_data_ptr;
|
char *data = NULL, *shm_data_ptr;
|
||||||
int ret = SR_ERR_OK, filter_valid;
|
int ret = SR_ERR_OK, filter_valid;
|
||||||
@@ -3579,7 +3586,6 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub
|
@@ -3584,7 +3591,6 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == change_subs->sub_count) {
|
if (i == change_subs->sub_count) {
|
||||||
@@ -80,7 +80,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3686,6 +3692,11 @@ process_event:
|
@@ -3697,6 +3703,11 @@ process_event:
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* subscription processed this event */
|
/* subscription processed this event */
|
||||||
@@ -3718,6 +3729,11 @@ process_event:
|
@@ -3729,6 +3740,11 @@ process_event:
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SR_SUB_EV_DONE:
|
case SR_SUB_EV_DONE:
|
||||||
@@ -104,7 +104,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
case SR_SUB_EV_ABORT:
|
case SR_SUB_EV_ABORT:
|
||||||
/* nothing to do */
|
/* nothing to do */
|
||||||
break;
|
break;
|
||||||
@@ -3742,14 +3758,20 @@ process_event:
|
@@ -3753,14 +3769,20 @@ process_event:
|
||||||
|
|
||||||
/* SUB WRITE URGE LOCK */
|
/* SUB WRITE URGE LOCK */
|
||||||
if (sr_shmsub_change_listen_relock(sub_shm, SR_LOCK_WRITE_URGE, &sub_info, change_sub, change_subs->module_name,
|
if (sr_shmsub_change_listen_relock(sub_shm, SR_LOCK_WRITE_URGE, &sub_info, change_sub, change_subs->module_name,
|
||||||
@@ -127,7 +127,7 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
} else {
|
} else {
|
||||||
SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " success (remaining %" PRIu32 " subscribers).",
|
SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " success (remaining %" PRIu32 " subscribers).",
|
||||||
change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, remaining_subs);
|
change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, remaining_subs);
|
||||||
@@ -4164,7 +4186,7 @@ finish_iter:
|
@@ -4176,7 +4198,7 @@ finish_iter:
|
||||||
sr_errinfo_free(&cb_err_info);
|
sr_errinfo_free(&cb_err_info);
|
||||||
|
|
||||||
/* publish "done" event */
|
/* publish "done" event */
|
||||||
@@ -137,10 +137,10 @@ index 61aab4c6..6ba1cd4d 100644
|
|||||||
}
|
}
|
||||||
|
|
||||||
diff --git a/src/shm_sub.h b/src/shm_sub.h
|
diff --git a/src/shm_sub.h b/src/shm_sub.h
|
||||||
index 0cd22884..bd697ccc 100644
|
index de21a30f..66fc7b69 100644
|
||||||
--- a/src/shm_sub.h
|
--- a/src/shm_sub.h
|
||||||
+++ b/src/shm_sub.h
|
+++ b/src/shm_sub.h
|
||||||
@@ -159,7 +159,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info,
|
@@ -147,7 +147,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info,
|
||||||
* @return err_info, NULL on success.
|
* @return err_info, NULL on success.
|
||||||
*/
|
*/
|
||||||
sr_error_info_t *sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name,
|
sr_error_info_t *sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name,
|
||||||
@@ -150,10 +150,10 @@ index 0cd22884..bd697ccc 100644
|
|||||||
/**
|
/**
|
||||||
* @brief Notify about (generate) a change "abort" event.
|
* @brief Notify about (generate) a change "abort" event.
|
||||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||||
index 068585d7..72dad00c 100644
|
index fa7b5360..542a55ba 100644
|
||||||
--- a/src/sysrepo.c
|
--- a/src/sysrepo.c
|
||||||
+++ b/src/sysrepo.c
|
+++ b/src/sysrepo.c
|
||||||
@@ -4313,7 +4313,7 @@ sr_error_info_t *
|
@@ -4397,7 +4397,7 @@ sr_error_info_t *
|
||||||
sr_changes_notify_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del,
|
sr_changes_notify_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del,
|
||||||
uint32_t timeout_ms, sr_lock_mode_t has_change_sub_lock, sr_error_info_t **err_info2)
|
uint32_t timeout_ms, sr_lock_mode_t has_change_sub_lock, sr_error_info_t **err_info2)
|
||||||
{
|
{
|
||||||
@@ -162,7 +162,7 @@ index 068585d7..72dad00c 100644
|
|||||||
struct sr_denied denied = {0};
|
struct sr_denied denied = {0};
|
||||||
sr_lock_mode_t change_sub_lock = has_change_sub_lock;
|
sr_lock_mode_t change_sub_lock = has_change_sub_lock;
|
||||||
uint32_t sid = 0, err_count;
|
uint32_t sid = 0, err_count;
|
||||||
@@ -4470,7 +4470,7 @@ store:
|
@@ -4559,7 +4559,7 @@ store:
|
||||||
}
|
}
|
||||||
|
|
||||||
/* publish "done" event, all changes were applied */
|
/* publish "done" event, all changes were applied */
|
||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
From 81b0b781fa41a9e1c7ab2a94e6fc5aa4c52d365d Mon Sep 17 00:00:00 2001
|
From 9e0267d4f20733b2a26df6d0ee0bc4019db8b13f Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||||
Date: Wed, 8 May 2024 17:00:50 +0200
|
Date: Wed, 8 May 2024 17:00:50 +0200
|
||||||
Subject: [PATCH 3/7] Allow to copy from factory default
|
Subject: [PATCH 3/8] Allow to copy from factory default
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -13,12 +13,12 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||||
index 72dad00c..1b54319f 100644
|
index 542a55ba..80bfb42c 100644
|
||||||
--- a/src/sysrepo.c
|
--- a/src/sysrepo.c
|
||||||
+++ b/src/sysrepo.c
|
+++ b/src/sysrepo.c
|
||||||
@@ -4895,7 +4895,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
@@ -5023,7 +5023,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||||
struct sr_mod_info_s mod_info;
|
|
||||||
const struct lys_module *ly_mod = NULL;
|
const struct lys_module *ly_mod = NULL;
|
||||||
|
sr_datastore_t ds1, ds2;
|
||||||
|
|
||||||
- SR_CHECK_ARG_APIRET(!session || !SR_IS_CONVENTIONAL_DS(src_datastore) || !SR_IS_CONVENTIONAL_DS(session->ds),
|
- SR_CHECK_ARG_APIRET(!session || !SR_IS_CONVENTIONAL_DS(src_datastore) || !SR_IS_CONVENTIONAL_DS(session->ds),
|
||||||
+ SR_CHECK_ARG_APIRET(!session || (!SR_IS_CONVENTIONAL_DS(src_datastore) && (src_datastore != SR_DS_FACTORY_DEFAULT)) || !SR_IS_CONVENTIONAL_DS(session->ds),
|
+ SR_CHECK_ARG_APIRET(!session || (!SR_IS_CONVENTIONAL_DS(src_datastore) && (src_datastore != SR_DS_FACTORY_DEFAULT)) || !SR_IS_CONVENTIONAL_DS(session->ds),
|
||||||
+20
-20
@@ -1,7 +1,7 @@
|
|||||||
From 496305b14423d0e0344cd4e52a80699773cd1a34 Mon Sep 17 00:00:00 2001
|
From e0c899ba266b959544d7cc08c917cebba7ac91c7 Mon Sep 17 00:00:00 2001
|
||||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||||
Date: Mon, 6 May 2024 14:49:32 +0200
|
Date: Mon, 6 May 2024 14:49:32 +0200
|
||||||
Subject: [PATCH 4/7] Add -z switch to sysrepoctl to install factory config
|
Subject: [PATCH 4/8] Add -z switch to sysrepoctl to install factory config
|
||||||
from a json file
|
from a json file
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
@@ -19,10 +19,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
4 files changed, 83 insertions(+), 3 deletions(-)
|
4 files changed, 83 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
diff --git a/src/executables/sysrepoctl.c b/src/executables/sysrepoctl.c
|
diff --git a/src/executables/sysrepoctl.c b/src/executables/sysrepoctl.c
|
||||||
index b33a60cd..b60d17af 100644
|
index 510eec2a..497c2cbf 100644
|
||||||
--- a/src/executables/sysrepoctl.c
|
--- a/src/executables/sysrepoctl.c
|
||||||
+++ b/src/executables/sysrepoctl.c
|
+++ b/src/executables/sysrepoctl.c
|
||||||
@@ -664,6 +664,7 @@ main(int argc, char **argv)
|
@@ -659,6 +659,7 @@ main(int argc, char **argv)
|
||||||
{"uninstall", required_argument, NULL, 'u'},
|
{"uninstall", required_argument, NULL, 'u'},
|
||||||
{"change", required_argument, NULL, 'c'},
|
{"change", required_argument, NULL, 'c'},
|
||||||
{"update", required_argument, NULL, 'U'},
|
{"update", required_argument, NULL, 'U'},
|
||||||
@@ -30,7 +30,7 @@ index b33a60cd..b60d17af 100644
|
|||||||
{"plugin-list", no_argument, NULL, 'L'},
|
{"plugin-list", no_argument, NULL, 'L'},
|
||||||
{"plugin-install", required_argument, NULL, 'P'},
|
{"plugin-install", required_argument, NULL, 'P'},
|
||||||
{"search-dirs", required_argument, NULL, 's'},
|
{"search-dirs", required_argument, NULL, 's'},
|
||||||
@@ -688,7 +689,7 @@ main(int argc, char **argv)
|
@@ -683,7 +684,7 @@ main(int argc, char **argv)
|
||||||
|
|
||||||
/* process options */
|
/* process options */
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
@@ -39,7 +39,7 @@ index b33a60cd..b60d17af 100644
|
|||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'h':
|
case 'h':
|
||||||
/* help */
|
/* help */
|
||||||
@@ -880,6 +881,15 @@ main(int argc, char **argv)
|
@@ -875,6 +876,15 @@ main(int argc, char **argv)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -55,7 +55,7 @@ index b33a60cd..b60d17af 100644
|
|||||||
case 'I':
|
case 'I':
|
||||||
/* init-data */
|
/* init-data */
|
||||||
if (operation == 'i') {
|
if (operation == 'i') {
|
||||||
@@ -939,9 +949,15 @@ main(int argc, char **argv)
|
@@ -934,9 +944,15 @@ main(int argc, char **argv)
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ index b33a60cd..b60d17af 100644
|
|||||||
/* list */
|
/* list */
|
||||||
if ((r = srctl_list(conn))) {
|
if ((r = srctl_list(conn))) {
|
||||||
diff --git a/src/lyd_mods.h b/src/lyd_mods.h
|
diff --git a/src/lyd_mods.h b/src/lyd_mods.h
|
||||||
index f3a9b215..b86f24b4 100644
|
index c1c2ea7a..1cdf2436 100644
|
||||||
--- a/src/lyd_mods.h
|
--- a/src/lyd_mods.h
|
||||||
+++ b/src/lyd_mods.h
|
+++ b/src/lyd_mods.h
|
||||||
@@ -103,4 +103,11 @@ sr_error_info_t *sr_lydmods_change_chng_feature(const struct ly_ctx *ly_ctx, con
|
@@ -103,4 +103,11 @@ sr_error_info_t *sr_lydmods_change_chng_feature(const struct ly_ctx *ly_ctx, con
|
||||||
@@ -89,10 +89,10 @@ index f3a9b215..b86f24b4 100644
|
|||||||
+
|
+
|
||||||
#endif
|
#endif
|
||||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||||
index 1b54319f..ca2026b2 100644
|
index 80bfb42c..22fcbc3c 100644
|
||||||
--- a/src/sysrepo.c
|
--- a/src/sysrepo.c
|
||||||
+++ b/src/sysrepo.c
|
+++ b/src/sysrepo.c
|
||||||
@@ -1626,6 +1626,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count)
|
@@ -1689,6 +1689,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count)
|
||||||
free(new_mods);
|
free(new_mods);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,15 +103,15 @@ index 1b54319f..ca2026b2 100644
|
|||||||
+ struct lyd_node *sr_mods = NULL;
|
+ struct lyd_node *sr_mods = NULL;
|
||||||
+ int initialized = 0;
|
+ int initialized = 0;
|
||||||
+ struct lyd_node *mod_data = NULL;
|
+ struct lyd_node *mod_data = NULL;
|
||||||
+ struct sr_data_update_s data_info = {0};
|
+ struct sr_lycc_ds_data_s data_info = {0};
|
||||||
+
|
+
|
||||||
+ /* create new temporary context */
|
+ /* create new temporary context */
|
||||||
+ if ((err_info = sr_ly_ctx_init(conn, &new_ctx))) {
|
+ if ((err_info = sr_ly_ctx_new(&new_ctx))) {
|
||||||
+ goto cleanup;
|
+ goto cleanup;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ /* use temporary context to load current modules */
|
+ /* use temporary context to load current modules */
|
||||||
+ if ((err_info = sr_shmmod_ctx_load_modules(SR_CONN_MOD_SHM(conn), new_ctx, NULL))) {
|
+ if ((err_info = sr_shmmod_ctx_load_modules(SR_CTX_MOD_SHM(sr_yang_ctx), new_ctx, NULL))) {
|
||||||
+ goto cleanup;
|
+ goto cleanup;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
@@ -130,18 +130,18 @@ index 1b54319f..ca2026b2 100644
|
|||||||
+ }
|
+ }
|
||||||
+ data_info.new.fdflt = mod_data;
|
+ data_info.new.fdflt = mod_data;
|
||||||
+ mod_data = NULL;
|
+ mod_data = NULL;
|
||||||
+ if ((err_info = sr_lycc_store_data_if_differ(conn, new_ctx, sr_mods, &data_info))) {
|
+ if ((err_info = sr_lycc_store_ds_data_if_differ(conn, new_ctx, sr_mods, &data_info))) {
|
||||||
+ goto cleanup;
|
+ goto cleanup;
|
||||||
+ }
|
+ }
|
||||||
+
|
+
|
||||||
+ /* update content ID and safely switch the context */
|
+ /* update content ID */
|
||||||
+ SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
+ SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
||||||
+ sr_conn_ctx_switch(conn, &new_ctx, NULL);
|
|
||||||
+cleanup:
|
+cleanup:
|
||||||
+ ly_ctx_destroy(new_ctx);
|
+ sr_lycc_ds_data_set_clear(&data_info.old);
|
||||||
+ sr_lycc_update_data_clear(&data_info);
|
+ sr_lycc_ds_data_set_clear(&data_info.new);
|
||||||
+ lyd_free_siblings(sr_mods);
|
+ lyd_free_siblings(sr_mods);
|
||||||
+ lyd_free_siblings(mod_data);
|
+ lyd_free_siblings(mod_data);
|
||||||
|
+ ly_ctx_destroy(new_ctx);
|
||||||
+ return sr_api_ret(NULL, err_info);
|
+ return sr_api_ret(NULL, err_info);
|
||||||
+}
|
+}
|
||||||
+
|
+
|
||||||
@@ -149,7 +149,7 @@ index 1b54319f..ca2026b2 100644
|
|||||||
sr_install_module(sr_conn_ctx_t *conn, const char *schema_path, const char *search_dirs, const char **features)
|
sr_install_module(sr_conn_ctx_t *conn, const char *schema_path, const char *search_dirs, const char **features)
|
||||||
{
|
{
|
||||||
diff --git a/src/sysrepo.h b/src/sysrepo.h
|
diff --git a/src/sysrepo.h b/src/sysrepo.h
|
||||||
index 466c8294..ee1b47a9 100644
|
index 82620c80..6fdf2b29 100644
|
||||||
--- a/src/sysrepo.h
|
--- a/src/sysrepo.h
|
||||||
+++ b/src/sysrepo.h
|
+++ b/src/sysrepo.h
|
||||||
@@ -34,7 +34,6 @@ extern "C" {
|
@@ -34,7 +34,6 @@ extern "C" {
|
||||||
@@ -160,7 +160,7 @@ index 466c8294..ee1b47a9 100644
|
|||||||
/**
|
/**
|
||||||
* @defgroup log_api Logging API
|
* @defgroup log_api Logging API
|
||||||
* @{
|
* @{
|
||||||
@@ -725,6 +724,15 @@ int sr_get_module_info(sr_conn_ctx_t *conn, sr_data_t **sysrepo_data);
|
@@ -787,6 +786,15 @@ int sr_get_module_info(sr_conn_ctx_t *conn, sr_data_t **sysrepo_data);
|
||||||
*/
|
*/
|
||||||
int sr_is_module_internal(const struct lys_module *ly_mod);
|
int sr_is_module_internal(const struct lys_module *ly_mod);
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
From 6543a03c98fe32180dc95a47d113a3bd2dd369de Mon Sep 17 00:00:00 2001
|
From c7602dc8eabb941e0a163208aaf4de92dd5ef526 Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Wed, 21 Aug 2024 16:00:35 +0200
|
Date: Wed, 21 Aug 2024 16:00:35 +0200
|
||||||
Subject: [PATCH 5/7] Introduce new log level [SEC] for audit trails
|
Subject: [PATCH 5/8] Introduce new log level [SEC] for audit trails
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -86,7 +86,7 @@ index b4f62e7c..62279675 100644
|
|||||||
|
|
||||||
#define SR_CHECK_MEM_GOTO(cond, err_info, go) if (cond) { SR_ERRINFO_MEM(&(err_info)); goto go; }
|
#define SR_CHECK_MEM_GOTO(cond, err_info, go) if (cond) { SR_ERRINFO_MEM(&(err_info)); goto go; }
|
||||||
diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h
|
diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h
|
||||||
index 165e255f..60df80cf 100644
|
index d71af2d4..32990fac 100644
|
||||||
--- a/src/sysrepo_types.h
|
--- a/src/sysrepo_types.h
|
||||||
+++ b/src/sysrepo_types.h
|
+++ b/src/sysrepo_types.h
|
||||||
@@ -65,7 +65,8 @@ typedef enum {
|
@@ -65,7 +65,8 @@ typedef enum {
|
||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
From 0ff2b7562154289de39abb10bcec2bc0d744a64d Mon Sep 17 00:00:00 2001
|
From a86dfdd4a5cb74c1f8c90c8d5aea6f5505c1b88c Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Wed, 21 Aug 2024 16:04:43 +0200
|
Date: Wed, 21 Aug 2024 16:04:43 +0200
|
||||||
Subject: [PATCH 6/7] Add audit trail for high priority system changes
|
Subject: [PATCH 6/8] Add audit trail for high priority system changes
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -22,10 +22,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
1 file changed, 12 insertions(+)
|
1 file changed, 12 insertions(+)
|
||||||
|
|
||||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||||
index ca2026b2..2ea546e9 100644
|
index 22fcbc3c..a1d14eda 100644
|
||||||
--- a/src/sysrepo.c
|
--- a/src/sysrepo.c
|
||||||
+++ b/src/sysrepo.c
|
+++ b/src/sysrepo.c
|
||||||
@@ -4518,6 +4518,9 @@ store:
|
@@ -4607,6 +4607,9 @@ store:
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ index ca2026b2..2ea546e9 100644
|
|||||||
/* publish "done" event, all changes were applied */
|
/* publish "done" event, all changes were applied */
|
||||||
if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, &cb_err_info))) {
|
if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, &cb_err_info))) {
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
@@ -4529,6 +4532,9 @@ store:
|
@@ -4618,6 +4621,9 @@ store:
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -45,7 +45,7 @@ index ca2026b2..2ea546e9 100644
|
|||||||
if (change_sub_lock && !has_change_sub_lock) {
|
if (change_sub_lock && !has_change_sub_lock) {
|
||||||
assert(change_sub_lock == SR_LOCK_READ);
|
assert(change_sub_lock == SR_LOCK_READ);
|
||||||
|
|
||||||
@@ -5031,6 +5037,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
@@ -5164,6 +5170,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ index ca2026b2..2ea546e9 100644
|
|||||||
cleanup:
|
cleanup:
|
||||||
/* MODULES UNLOCK */
|
/* MODULES UNLOCK */
|
||||||
sr_shmmod_modinfo_unlock(&mod_info);
|
sr_shmmod_modinfo_unlock(&mod_info);
|
||||||
@@ -7562,6 +7571,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim
|
@@ -7772,6 +7781,9 @@ sr_rpc_send_tree_internal(sr_session_ctx_t *session, struct lyd_node *input, uin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
From 83e58e43fc1c968a6e007ab0c39d99aeffc8fbce Mon Sep 17 00:00:00 2001
|
From dbf08c67d8f17bdf98466b18fd72a230269e5d46 Mon Sep 17 00:00:00 2001
|
||||||
From: Joachim Wiberg <troglobit@gmail.com>
|
From: Joachim Wiberg <troglobit@gmail.com>
|
||||||
Date: Fri, 23 Aug 2024 12:22:06 +0200
|
Date: Fri, 23 Aug 2024 12:22:06 +0200
|
||||||
Subject: [PATCH 7/7] On error in sr_shmsub_listen_thread(), exit process
|
Subject: [PATCH 7/8] On error in sr_shmsub_listen_thread(), exit process
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: 8bit
|
Content-Transfer-Encoding: 8bit
|
||||||
@@ -18,10 +18,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
|||||||
1 file changed, 2 insertions(+)
|
1 file changed, 2 insertions(+)
|
||||||
|
|
||||||
diff --git a/src/shm_sub.c b/src/shm_sub.c
|
diff --git a/src/shm_sub.c b/src/shm_sub.c
|
||||||
index 6ba1cd4d..5c006a82 100644
|
index f64b85d6..c1a4b960 100644
|
||||||
--- a/src/shm_sub.c
|
--- a/src/shm_sub.c
|
||||||
+++ b/src/shm_sub.c
|
+++ b/src/shm_sub.c
|
||||||
@@ -5049,5 +5049,7 @@ error:
|
@@ -5061,5 +5061,7 @@ error:
|
||||||
/* free our own resources */
|
/* free our own resources */
|
||||||
ATOMIC_STORE_RELAXED(subscr->thread_running, 0);
|
ATOMIC_STORE_RELAXED(subscr->thread_running, 0);
|
||||||
pthread_detach(pthread_self());
|
pthread_detach(pthread_self());
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
From 2549c966c090dd38a7a09907d27d13107d15aedd Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||||
|
Date: Tue, 16 Dec 2025 08:18:32 +0100
|
||||||
|
Subject: [PATCH 8/8] Cross compile fixes
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
Organization: Wires
|
||||||
|
|
||||||
|
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||||
|
---
|
||||||
|
CMakeModules/SetupPrintedContext.cmake | 9 +++++++++
|
||||||
|
1 file changed, 9 insertions(+)
|
||||||
|
|
||||||
|
diff --git a/CMakeModules/SetupPrintedContext.cmake b/CMakeModules/SetupPrintedContext.cmake
|
||||||
|
index f31ddff6..bfce7a5f 100644
|
||||||
|
--- a/CMakeModules/SetupPrintedContext.cmake
|
||||||
|
+++ b/CMakeModules/SetupPrintedContext.cmake
|
||||||
|
@@ -59,6 +59,15 @@ function(SETUP_PRINTED_CONTEXT)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
+ # cannot run test programs when cross-compiling
|
||||||
|
+ if(CMAKE_CROSSCOMPILING)
|
||||||
|
+ message(WARNING "Cannot calculate printed context address when cross-compiling. "
|
||||||
|
+ "Provide PRINTED_CONTEXT_ADDRESS manually to enable printed context.")
|
||||||
|
+ message(STATUS "Printed context: disabled")
|
||||||
|
+ set(PRINTED_CONTEXT_ADDRESS 0 CACHE STRING "${PRINTED_CONTEXT_ADDRESS_DOCSTRING}" FORCE)
|
||||||
|
+ return()
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
# compile and run the address calculator
|
||||||
|
try_run(
|
||||||
|
exec_result compile_result
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
|
|
||||||
+1
-1
@@ -291,7 +291,7 @@ static int sysrepo_export(const struct infix_ds *ds, const char *path)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lyd_print_path(path, data->tree, LYD_JSON, LYD_PRINT_WITHSIBLINGS);
|
err = lyd_print_path(path, data->tree, LYD_JSON, LYD_PRINT_SIBLINGS);
|
||||||
sr_release_data(data);
|
sr_release_data(data);
|
||||||
if (err) {
|
if (err) {
|
||||||
sysrepo_print_error(sess);
|
sysrepo_print_error(sess);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
AC_PREREQ(2.61)
|
AC_PREREQ(2.61)
|
||||||
# confd version is same as system YANG model version, step on breaking changes
|
# confd version is same as system YANG model version, step on breaking changes
|
||||||
AC_INIT([confd], [1.6], [https://github.com/kernelkit/infix/issues])
|
AC_INIT([confd], [1.7], [https://github.com/kernelkit/infix/issues])
|
||||||
AM_INIT_AUTOMAKE(1.11 foreign subdir-objects)
|
AM_INIT_AUTOMAKE(1.11 foreign subdir-objects)
|
||||||
AM_SILENT_RULES(yes)
|
AM_SILENT_RULES(yes)
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ AC_CONFIG_FILES([
|
|||||||
share/migrate/1.4/Makefile
|
share/migrate/1.4/Makefile
|
||||||
share/migrate/1.5/Makefile
|
share/migrate/1.5/Makefile
|
||||||
share/migrate/1.6/Makefile
|
share/migrate/1.6/Makefile
|
||||||
|
share/migrate/1.7/Makefile
|
||||||
yang/Makefile
|
yang/Makefile
|
||||||
yang/confd/Makefile
|
yang/confd/Makefile
|
||||||
yang/test-mode/Makefile
|
yang/test-mode/Makefile
|
||||||
yang/rousette/Makefile
|
|
||||||
src/Makefile
|
src/Makefile
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
"name": "default-ssh",
|
"name": "default-ssh",
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"tcp-server-parameters": {
|
"tcp-server-parameters": {
|
||||||
"local-address": "::"
|
"local-bind": [
|
||||||
|
{
|
||||||
|
"local-address": "::"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ssh-server-parameters": {
|
"ssh-server-parameters": {
|
||||||
"server-identity": {
|
"server-identity": {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Migrate NETCONF server TCP parameters to new YANG schema (RFC 9643)
|
||||||
|
# The 'local-address' leaf has been moved into a 'local-bind' list
|
||||||
|
# Old: tcp-server-parameters/local-address
|
||||||
|
# New: tcp-server-parameters/local-bind[]/local-address
|
||||||
|
|
||||||
|
file=$1
|
||||||
|
temp=${file}.tmp
|
||||||
|
|
||||||
|
jq '
|
||||||
|
if .["ietf-netconf-server:netconf-server"]?.listen?.endpoints?.endpoint then
|
||||||
|
.["ietf-netconf-server:netconf-server"].listen.endpoints.endpoint |= map(
|
||||||
|
if .ssh?."tcp-server-parameters"?."local-address" then
|
||||||
|
# Extract and remove the old local-address value, then add new structure
|
||||||
|
.ssh."tcp-server-parameters"."local-address" as $addr |
|
||||||
|
.ssh."tcp-server-parameters" |= (del(."local-address") | . + {
|
||||||
|
"local-bind": [{
|
||||||
|
"local-address": $addr
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
else
|
||||||
|
.
|
||||||
|
end
|
||||||
|
)
|
||||||
|
else
|
||||||
|
.
|
||||||
|
end
|
||||||
|
' "$file" > "$temp" && mv "$temp" "$file"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Migrate keystore symmetric key syntax to new YANG schema (RFC 9643)
|
||||||
|
# The 'cleartext-key' leaf has been renamed to 'cleartext-symmetric-key'
|
||||||
|
# Old: symmetric-key[]/key-type/cleartext-key
|
||||||
|
# New: symmetric-key[]/key-type/cleartext-symmetric-key
|
||||||
|
|
||||||
|
file=$1
|
||||||
|
temp=${file}.tmp
|
||||||
|
|
||||||
|
jq '
|
||||||
|
if .["ietf-keystore:keystore"]?."symmetric-keys"?."symmetric-key" then
|
||||||
|
.["ietf-keystore:keystore"]."symmetric-keys"."symmetric-key" |= map(
|
||||||
|
if ."key-type"?."cleartext-key" then
|
||||||
|
# Rename cleartext-key to cleartext-symmetric-key
|
||||||
|
."key-type"."cleartext-key" as $key_value |
|
||||||
|
."key-type" |= (del(."cleartext-key") | . + {
|
||||||
|
"cleartext-symmetric-key": $key_value
|
||||||
|
})
|
||||||
|
else
|
||||||
|
.
|
||||||
|
end
|
||||||
|
)
|
||||||
|
else
|
||||||
|
.
|
||||||
|
end
|
||||||
|
' "$file" > "$temp" && mv "$temp" "$file"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
migratedir = $(pkgdatadir)/migrate/1.7
|
||||||
|
dist_migrate_DATA = 10-netconf-server-tcp-params.sh \
|
||||||
|
20-keystore-cleartext-key-rename.sh
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
SUBDIRS = 1.0 1.1 1.2 1.3 1.4 1.5 1.6
|
SUBDIRS = 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7
|
||||||
migratedir = $(pkgdatadir)/migrate
|
migratedir = $(pkgdatadir)/migrate
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod
|
|||||||
/* Debug: print diff to file */
|
/* Debug: print diff to file */
|
||||||
FILE *f = fopen("/tmp/confd-diff.json", "w");
|
FILE *f = fopen("/tmp/confd-diff.json", "w");
|
||||||
if (f) {
|
if (f) {
|
||||||
lyd_print_file(f, diff, LYD_JSON, LYD_PRINT_WITHSIBLINGS);
|
lyd_print_file(f, diff, LYD_JSON, LYD_PRINT_SIBLINGS);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ int wifi_gen(struct lyd_node *dif, struct lyd_node *cif, struct dagger *net)
|
|||||||
country = lydx_get_cattr(wifi, "country-code");
|
country = lydx_get_cattr(wifi, "country-code");
|
||||||
encryption = lydx_get_cattr(wifi, "encryption");
|
encryption = lydx_get_cattr(wifi, "encryption");
|
||||||
secret_node = lydx_get_xpathf(cif, "../../keystore/symmetric-keys/symmetric-key[name='%s']", secret_name);
|
secret_node = lydx_get_xpathf(cif, "../../keystore/symmetric-keys/symmetric-key[name='%s']", secret_name);
|
||||||
secret = lydx_get_cattr(secret_node, "cleartext-key");
|
secret = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
|
||||||
|
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
return wifi_gen_del(cif, net);
|
return wifi_gen_del(cif, net);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
SUBDIRS = confd test-mode rousette
|
SUBDIRS = confd test-mode
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ MODULES=(
|
|||||||
"ietf-bfd-ip-sh@2022-09-22.yang"
|
"ietf-bfd-ip-sh@2022-09-22.yang"
|
||||||
"iana-if-type@2023-01-26.yang"
|
"iana-if-type@2023-01-26.yang"
|
||||||
# NOTE: ietf-tls-client must be version matched with ietf-tls-server, used by netopeer2!
|
# NOTE: ietf-tls-client must be version matched with ietf-tls-server, used by netopeer2!
|
||||||
"ietf-tls-client@2023-12-28.yang"
|
# "ietf-tls-client@2023-12-28.yang"
|
||||||
"ietf-syslog@2024-03-21.yang -e file-action -e file-limit-size -e remote-action -e select-adv-compare -e select-match"
|
"ietf-syslog@2024-03-21.yang -e file-action -e file-limit-size -e remote-action -e select-adv-compare -e select-match"
|
||||||
"infix-syslog@2025-11-17.yang"
|
"infix-syslog@2025-11-17.yang"
|
||||||
"iana-hardware@2018-03-13.yang"
|
"iana-hardware@2018-03-13.yang"
|
||||||
@@ -37,9 +37,9 @@ MODULES=(
|
|||||||
"infix-firewall@2025-04-26.yang"
|
"infix-firewall@2025-04-26.yang"
|
||||||
"infix-firewall-services@2025-04-26.yang"
|
"infix-firewall-services@2025-04-26.yang"
|
||||||
"infix-firewall-icmp-types@2025-04-26.yang"
|
"infix-firewall-icmp-types@2025-04-26.yang"
|
||||||
"infix-meta@2024-10-18.yang"
|
"infix-meta@2025-12-10.yang"
|
||||||
"infix-system@2025-10-18.yang"
|
"infix-system@2025-10-18.yang"
|
||||||
"infix-services@2024-12-03.yang"
|
"infix-services@2025-12-10.yang"
|
||||||
"ieee802-ethernet-interface@2019-06-21.yang"
|
"ieee802-ethernet-interface@2019-06-21.yang"
|
||||||
"infix-ethernet-interface@2024-02-27.yang"
|
"infix-ethernet-interface@2024-02-27.yang"
|
||||||
"infix-factory-default@2023-06-28.yang"
|
"infix-factory-default@2023-06-28.yang"
|
||||||
@@ -47,5 +47,5 @@ MODULES=(
|
|||||||
"ietf-crypto-types -e cleartext-symmetric-keys"
|
"ietf-crypto-types -e cleartext-symmetric-keys"
|
||||||
"infix-crypto-types@2025-06-17.yang"
|
"infix-crypto-types@2025-06-17.yang"
|
||||||
"ietf-keystore -e symmetric-keys"
|
"ietf-keystore -e symmetric-keys"
|
||||||
"infix-keystore@2025-06-17.yang"
|
"infix-keystore@2025-12-10.yang"
|
||||||
)
|
)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user