mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-03 14:23:02 +02:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ae395cff8 | ||
|
|
ab8b174eff | ||
|
|
7560da80a7 | ||
|
|
413cb61112 | ||
|
|
e3c6f749ad | ||
|
|
a990e559f7 | ||
|
|
a0b06b2593 | ||
|
|
354650c0a5 | ||
|
|
12e5b4c1b4 | ||
|
|
cfbdce1b90 | ||
|
|
0b29e9eee4 |
@@ -27,7 +27,7 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.25"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -27,7 +27,7 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.25"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -29,7 +29,7 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image
|
||||
BR2_ROOTFS_POST_SCRIPT_ARGS="-c $(BR2_EXTERNAL_INFIX_PATH)/board/aarch64/r2s/genimage.cfg"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.25"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_INFIX_PATH)/board/aarch64/r2s/rk3328-nanopi-r2s-dts.patch"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/r2s/linux_defconfig"
|
||||
|
||||
@@ -26,7 +26,7 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INF
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.25"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -27,7 +27,7 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INF
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.25"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
|
||||
@@ -3,6 +3,17 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v25.05.0][UNRELEASED]
|
||||
-------------------------
|
||||
### Changes
|
||||
- Upgrade Linux kernel to 6.12.26 (LTS)
|
||||
- Upgrade libyang to 3.12.2
|
||||
- Upgrade sysrepo to 3.6.11
|
||||
- Upgrade netopeer2 (NETCONF) to 2.4.1
|
||||
- Add documentation on Infix upgrading and downgrading, issue #1009
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
[v25.04.0][] - 2025-04-30
|
||||
-------------------------
|
||||
@@ -1547,6 +1558,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
|
||||
[buildroot]: https://buildroot.org/
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.04.0...HEAD
|
||||
[v25.05.0]: https://github.com/kernelkit/infix/compare/v25.04.0...v25.05.0
|
||||
[v25.04.0]: https://github.com/kernelkit/infix/compare/v25.03.0...v25.04.0
|
||||
[v25.03.0]: https://github.com/kernelkit/infix/compare/v25.02.0...v25.03.0
|
||||
[v25.02.0]: https://github.com/kernelkit/infix/compare/v25.01.0...v25.02.0
|
||||
|
||||
+3
-2
@@ -349,5 +349,6 @@ can funtion reasonably well without a persistent `/var`, loosing
|
||||
If `var` is not available, Infix will still persist `/var/lib` using
|
||||
`cfg` as the backing storage.
|
||||
|
||||
[^1]: See [CLI Upgrade](cli/upgrade.md) for information on upgrading
|
||||
via CLI.
|
||||
[^1]: See [Upgrading procedures and boot
|
||||
order](system.md#upgrade-procedures-and-boot-order) for
|
||||
information on upgrading via CLI.
|
||||
|
||||
@@ -39,7 +39,12 @@ The secondary partition (`rootfs.1`) has now been upgraded and will be used as
|
||||
the *active* partition on the next boot. Leaving the primary partition, with
|
||||
the version we are currently running, intact in case of trouble.
|
||||
|
||||
See [upgrading procedures and boot order][2] for more information on
|
||||
upgrading.
|
||||
|
||||
[^1]: It is not possible to upgrade the partition we booted from. Thankfully
|
||||
the underlying "rauc" subsystem keeps track of this. Hence, to upgrade
|
||||
both partitions you must reboot to the new version (to verify it works)
|
||||
and then repeat the same command.
|
||||
|
||||
[2]: ../system.md#upgrade-procedures-and-boot-order
|
||||
|
||||
@@ -193,3 +193,11 @@ admin@example:/config/web/> edit restconf
|
||||
admin@example:/config/web/restconf/> no enabled
|
||||
admin@example:/config/web/restconf/>
|
||||
```
|
||||
|
||||
# System Upgrade
|
||||
|
||||
See [upgrading procedures and boot order][1] for information on
|
||||
upgrading.
|
||||
|
||||
|
||||
[1]: system.md#upgrade-procedures-and-boot-order
|
||||
|
||||
+361
@@ -323,7 +323,368 @@ reference ID, stratum, time offsets, frequency, and root delay.
|
||||
> The system uses `chronyd` Network Time Protocol (NTP) daemon. The
|
||||
> output shown here is best explained in the [Chrony documentation][4].
|
||||
|
||||
## Upgrade procedures and boot order
|
||||
|
||||
For resilience purposes, Infix maintains two software
|
||||
images referred to as the _primary_ and _secondary_ partition image.
|
||||
In addition, some bootloaders support [netbooting][6].
|
||||
|
||||
The _boot order_ defines which image is tried first, and is listed
|
||||
with the CLI `show software` command. It also shows Infix version
|
||||
installed per partition, and which image was used when booting (`STATE
|
||||
booted`).
|
||||
|
||||
```
|
||||
admin@example:/> show software
|
||||
BOOT ORDER
|
||||
primary secondary net
|
||||
|
||||
NAME STATE VERSION DATE
|
||||
primary booted v25.01.0 2025-04-25T10:15:00+00:00
|
||||
secondary inactive v25.01.0 2025-04-25T10:07:20+00:00
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
YANG support for upgrading Infix, inspecting and _modifying_ the
|
||||
boot-order, is defined in [infix-system-software][5].
|
||||
|
||||
|
||||
### Upgrading Infix
|
||||
|
||||
Upgrading Infix is done one partition at a time. If the system has
|
||||
booted from one partition, an `upgrade` will apply to the other
|
||||
(inactive) partition.
|
||||
|
||||
1. Download and unpack the release to install. Make the image *pkg*
|
||||
bundle available at some URL[^10]
|
||||
2. Assume the unit has booted the `primary` image. Then running the
|
||||
`upgrade` command installs a new image on the `secondary`
|
||||
partition
|
||||
3. As part of a successful upgrade, the boot-order is implictly
|
||||
changed to boot the newly installed image
|
||||
4. Reboot the unit
|
||||
5. The unit now runs the new image. To upgrade the remaining partition
|
||||
(`primary`), run the same upgrade command again, and (optionally)
|
||||
reboot to verify the upgrade
|
||||
|
||||
> [!CAUTION]
|
||||
> During boot, the unit may [migrate](#configuration-migration) the
|
||||
> startup configuration for any syntax changes. It is therefore
|
||||
> important that you make sure to upgrade the other partition as well
|
||||
> after reboot, of course after having verified your setup.
|
||||
|
||||
The CLI example below shows steps 2-4.
|
||||
|
||||
Upgrade: here the image *pkg bundle* was made available via TFTP.
|
||||
|
||||
```
|
||||
admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.03.1.pkg
|
||||
installing
|
||||
0% Installing
|
||||
0% Determining slot states
|
||||
10% Determining slot states done.
|
||||
...
|
||||
98% Copying image to rootfs.1
|
||||
99% Copying image to rootfs.1
|
||||
99% Copying image to rootfs.1 done.
|
||||
99% Updating slots done.
|
||||
100% Installing done.
|
||||
Installing `tftp://198.18.117.1/infix-aarch64-25.03.1.pkg` succeeded
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
Reboot: The unit will boot on the other partition, with the newly
|
||||
installed image. The `Loading startup-config` step conducts migration
|
||||
of startup configuration if applicable.
|
||||
|
||||
```
|
||||
admin@example:/> reboot
|
||||
[ OK ] Stopping Static routing daemon
|
||||
[ OK ] Stopping Zebra routing daemon
|
||||
...
|
||||
[ OK ] Loading startup-config
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Update DNS configuration
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
Infix -- a Network Operating System v25.03.1 (ttyS0)
|
||||
example login: admin
|
||||
Password:
|
||||
.-------.
|
||||
| . . | Infix -- a Network Operating System
|
||||
|-. v .-| https://kernelkit.org
|
||||
'-'---'-'
|
||||
|
||||
Run the command 'cli' for interactive OAM
|
||||
|
||||
admin@example:~$ cli
|
||||
|
||||
See the 'help' command for an introduction to the system
|
||||
|
||||
admin@example:/> show software
|
||||
BOOT ORDER
|
||||
secondary primary net
|
||||
|
||||
NAME STATE VERSION DATE
|
||||
primary inactive v25.01.0 2025-04-25T10:15:00+00:00
|
||||
secondary booted v25.03.1 2025-04-25T10:24:31+00:00
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
As shown, the *boot order* has been updated, so that *secondary* is
|
||||
now the preferred boot source.
|
||||
|
||||
To upgrade the remaining partition (`primary`), run the `upgrade URL`
|
||||
command again, and reboot.
|
||||
|
||||
### Configuration Migration
|
||||
|
||||
The example above illustrated an upgrade from Infix v25.01.0 to
|
||||
v25.03.1. Inbetween these versions, YANG configuration definitions
|
||||
changed slightly (more details given below).
|
||||
|
||||
During boot, Infix inspects the `version` meta information within the
|
||||
startup configuration file to determine if configuration migration is
|
||||
needed. In this specific case, the configuration file has version
|
||||
`1.4` while the booted software expects version `1.5` (the
|
||||
configuration version numbering differs from the Infix image version
|
||||
numbering). The startup configuration is migrated to `1.5`
|
||||
definitions and stored, while a backup previous startup configuration
|
||||
is stored in directory `/cfg/backup/`.
|
||||
|
||||
```
|
||||
admin@example:/> dir /cfg/backup/
|
||||
/cfg/backup/ directory
|
||||
startup-config-1.4.cfg
|
||||
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
The modifications made to the startup configuration can be viewed by
|
||||
comparing the files from the *shell*. An example is shown below.
|
||||
|
||||
```
|
||||
admin@example:/> exit
|
||||
admin@example:~$ diff /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg
|
||||
--- /cfg/backup/startup-config-1.4.cfg
|
||||
+++ /cfg/startup-config.cfg
|
||||
...
|
||||
- "public-key-format": "ietf-crypto-types:ssh-public-key-format",
|
||||
+ "public-key-format": "infix-crypto-types:ssh-public-key-format",
|
||||
...
|
||||
- "private-key-format": "ietf-crypto-types:rsa-private-key-format",
|
||||
+ "private-key-format": "infix-crypto-types:rsa-private-key-format",
|
||||
...
|
||||
- "version": "1.4"
|
||||
+ "version": "1.5"
|
||||
...
|
||||
admin@example:~$
|
||||
```
|
||||
|
||||
### Downgrading Infix
|
||||
|
||||
Downgrading to an earlier Infix version is possible, however,
|
||||
downgrading is **not** guaranteed to work smoothly. In particular,
|
||||
when the unit boots up with the downgraded version, it may fail to
|
||||
apply the *startup config*, and instead apply its [failure config][7].
|
||||
|
||||
We consider two cases: downgrading with or without applying a backup
|
||||
startup configuration before rebooting.
|
||||
|
||||
In both cases we start out with a unit running Infix v25.03.1, and
|
||||
wish to downgrade to v25.01.0.
|
||||
|
||||
```
|
||||
admin@example:/> show software
|
||||
BOOT ORDER
|
||||
primary secondary net
|
||||
|
||||
NAME STATE VERSION DATE
|
||||
primary booted v25.03.1 2025-04-25T11:36:26+00:00
|
||||
secondary inactive v25.03.1 2025-04-25T10:24:31+00:00
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
#### Downgrading when applying a backup startup configuration
|
||||
|
||||
This is the recommended approach to downgrade, given that you have a
|
||||
backup configuration available. The objective is to avoid ending up
|
||||
with the unit in *failure config*.
|
||||
|
||||
1. Find the backup configuration file.
|
||||
2. Run `upgrade URL` to install Infix image to downgrade to.
|
||||
3. Copy backup startup configuration to current startup configuration
|
||||
(from shell).
|
||||
4. Reboot.
|
||||
|
||||
*Find the backup configuration file:*
|
||||
|
||||
Assume you have a backup startup config for the version to downgrade
|
||||
to (here Infix v25.01.0, config `version 1.4`).
|
||||
|
||||
```
|
||||
admin@example:/> dir /cfg/backup/
|
||||
/cfg/backup/ directory
|
||||
startup-config-1.4.cfg
|
||||
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Use `upgrade` command to downgrade:*
|
||||
|
||||
```
|
||||
admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg
|
||||
installing
|
||||
0% Installing
|
||||
0% Determining slot states
|
||||
10% Determining slot states done.
|
||||
...
|
||||
99% Copying image to rootfs.1 done.
|
||||
99% Updating slots done.
|
||||
100% Installing done.
|
||||
Installing `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Apply the backup configuration file:*
|
||||
|
||||
```
|
||||
admin@example:/> copy /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg
|
||||
Overwrite existing file /cfg/startup-config.cfg (y/N)? y
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Reboot:*
|
||||
|
||||
The unit will come up with the applied backup configuration instead of
|
||||
failure config.
|
||||
|
||||
```
|
||||
admin@example:/> reboot
|
||||
[ OK ] Saving system clock to file
|
||||
[ OK ] Stopping Software update service
|
||||
[ OK ] Stopping Status daemon
|
||||
...
|
||||
[ OK ] Bootstrapping YANG datastore
|
||||
[ OK ] Starting Configuration daemon
|
||||
[ OK ] Loading startup-config
|
||||
[ OK ] Update DNS configuration
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
Infix -- a Network Operating System v25.01.0 (ttyS0)
|
||||
example login:
|
||||
```
|
||||
|
||||
#### Downgrading without applying a backup startup configuration
|
||||
|
||||
This procedure assumes you have access to the unit's console port and
|
||||
its default login credentials[^9].
|
||||
|
||||
1. Downgrade
|
||||
2. Reboot
|
||||
3. Login with unit's default credentials
|
||||
4. Conduct factory reset
|
||||
5. (Then go on configure the unit as you wish)
|
||||
|
||||
*Use `upgrade` command to downgrade:*
|
||||
|
||||
```
|
||||
admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg
|
||||
installing
|
||||
0% Installing
|
||||
0% Determining slot states
|
||||
10% Determining slot states done.
|
||||
...
|
||||
99% Copying image to rootfs.1 done.
|
||||
99% Updating slots done.
|
||||
100% Installing done.
|
||||
Installing `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Reboot:*
|
||||
|
||||
Conduct a reboot. During boot, the unit fails to apply the existing
|
||||
startup configuration (config version `1.5` while software expects
|
||||
version `1.4` or earlier), and instead applies its [failure
|
||||
config][7]. This is what is seen on the console when this situation
|
||||
occurs. Note that the login prompt displays `failed` as part of the
|
||||
*hostname*.
|
||||
|
||||
```
|
||||
admin@example:/> reboot
|
||||
[ OK ] Saving system clock to file
|
||||
[ OK ] Stopping Software update service
|
||||
[ OK ] Stopping Status daemon
|
||||
...
|
||||
[ OK ] Verifying SSH host keys
|
||||
[ OK ] Bootstrapping YANG datastore
|
||||
[ OK ] Starting Configuration daemon
|
||||
[FAIL] Loading startup-config
|
||||
[ OK ] Loading failure-config
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
Infix -- a Network Operating System v25.01.0 (ttyS0)
|
||||
|
||||
ERROR: Corrupt startup-config, system has reverted to default login credentials
|
||||
failed-00-00-00 login:
|
||||
```
|
||||
|
||||
To remedy a situation like this, you can login with the unit's *default
|
||||
login credentials*, preferrably via a [console port][8].
|
||||
The unit's default credentials are typically printed on a sticker on
|
||||
the unit.
|
||||
|
||||
```
|
||||
failed-00-00-00 login: admin
|
||||
Password:
|
||||
|
||||
Run the command 'cli' for interactive OAM
|
||||
|
||||
admin@failed-00-00-00:~$
|
||||
```
|
||||
|
||||
When it is *safe* from a network operations perspective, you can
|
||||
conduct a factory reset and reboot. It is recommended to remove the
|
||||
unit from any production network before doing this, as a factory reset
|
||||
may enable undesired connectivity between the unit's ports.
|
||||
|
||||
```
|
||||
admin@failed-00-00-00:~$ factory
|
||||
Factory reset device (y/N)? y
|
||||
factory: scheduled factory reset on next boot.
|
||||
Reboot now to perform reset, (y/N)? y
|
||||
[ OK ] Saving system time (UTC) to RTC
|
||||
[ OK ] Stopping mDNS alias advertiser
|
||||
...
|
||||
[ OK ] Starting Configuration daemon
|
||||
[ OK ] Loading startup-config
|
||||
[ OK ] Update DNS configuration
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Starting Status daemon
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
|
||||
Please press Enter to activate this console.
|
||||
|
||||
Infix -- a Network Operating System v25.01.0 (ttyS0)
|
||||
example login:
|
||||
```
|
||||
|
||||
Continued configuration is done as with any unit after factory reset.
|
||||
|
||||
[1]: https://www.rfc-editor.org/rfc/rfc7317
|
||||
[2]: https://github.com/kernelkit/infix/blob/main/src/confd/yang/infix-system%402024-02-29.yang
|
||||
[3]: https://www.rfc-editor.org/rfc/rfc8341
|
||||
[4]: https://chrony-project.org/doc/4.6.1/chronyc.html
|
||||
[5]: https://github.com/kernelkit/infix/blob/main/src/confd/yang/confd/infix-system-software.yang
|
||||
[6]: boot.md#system-configuration
|
||||
[7]: introduction.md#system-boot
|
||||
[8]: management.md#console-port
|
||||
[^9]: In failure config, Infix puts all Ethernet ports as individual
|
||||
interfaces. With direct access, one can connect with e.g., SSH,
|
||||
using link local IPv6 addresses. This as an alternative to
|
||||
connecting via a console port.
|
||||
[^10]: Set up an FTP/TFTP/SFTP or HTTP/HTTPS server on the same LAN.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From d0f6422fee7a46fcb7445c88f499f61b3eb0ead0 Mon Sep 17 00:00:00 2001
|
||||
From: Adam Piecek <Adam.Piecek@cesnet.cz>
|
||||
Date: Wed, 23 Oct 2024 14:37:09 +0200
|
||||
Subject: [PATCH 1/8] added support for RpcYang in Context::parseOp
|
||||
Subject: [PATCH 01/18] added support for RpcYang in Context::parseOp
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I25182ea2d042be1e6e4246e18aee260cc032e547
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 7e015f3486bdbb54f1dcc2e2ce51102b1d623081 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 23 Oct 2024 12:52:24 +0200
|
||||
Subject: [PATCH 2/8] throw when lyd_validate_all returns error
|
||||
Subject: [PATCH 02/18] throw when lyd_validate_all returns error
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Bug: https://github.com/CESNET/libyang-cpp/issues/20
|
||||
Change-Id: I005a2f1b057978573a4046e7b4cc31d77e36fde3
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 490d8bb242d33213b948485f5b94c55e22cf86a6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 21 Nov 2024 11:32:44 +0100
|
||||
Subject: [PATCH 3/8] remove a misleading comment
|
||||
Subject: [PATCH 03/18] remove a misleading comment
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The whole intention within action's input/output handling here was to
|
||||
put some emphasis on the fact that we aren't tracking the input/output
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From e1b17386cf61048d2fe27fffb3b763981a225f52 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Wed, 27 Nov 2024 09:47:47 +0100
|
||||
Subject: [PATCH 4/8] schema: improve `List::keys()` not to use `std::move`
|
||||
Subject: [PATCH 04/18] schema: improve `List::keys()` not to use `std::move`
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
`List::keys()` used `std::move` while iterating over array of leafs.
|
||||
This was solved without using `std::move`.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 1102ecdcafbc9206f59b383769687e418557838e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Mon, 25 Nov 2024 15:54:02 +0100
|
||||
Subject: [PATCH 5/8] schema: make leaf-list's `default` statement available
|
||||
Subject: [PATCH 05/18] schema: make leaf-list's `default` statement available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Make leaf-list's `default` statement available so that it can be
|
||||
accessed if end-user requires reading schema nodes.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 01f2633cef60495d5cafc4b4b1f25273b03ab3cd Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Tue, 22 Oct 2024 15:11:30 +0200
|
||||
Subject: [PATCH 6/8] schema: Make choice and case statements available
|
||||
Subject: [PATCH 06/18] schema: Make choice and case statements available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Make choice and case statements available so that they can be accessed
|
||||
if end-user requires reading schema nodes.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From a1acdc794facf8cbf113f73274ecebd5898c81a1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 17 Dec 2024 15:08:43 +0100
|
||||
Subject: [PATCH 7/8] Wrap lyd_change_term for changing the value for a
|
||||
Subject: [PATCH 07/18] Wrap lyd_change_term for changing the value for a
|
||||
terminal node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Previously, the code would require a newPath(...,
|
||||
libyang::CreationOptions::Update), which is quite a mouthful.
|
||||
|
||||
@@ -0,0 +1,652 @@
|
||||
From 32b200ed06e9adb44a8d4ce6771f18812a54d06e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Wed, 20 Nov 2024 10:20:19 +0100
|
||||
Subject: [PATCH 08/18] Add `Module::child()`, `Module::childrenDfs()` and
|
||||
`Module::immediateChildren()`
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Those functions are implemented in the same manner as in `SchemaNode`
|
||||
and allows to walk through modules children. This is counterpart to
|
||||
already implemented `Module::childInstantiables()` that returns
|
||||
instantiables schema nodes. These return all nodes, including
|
||||
the schema-only nodes such as choice and case if end-user needs
|
||||
to read its schema.
|
||||
|
||||
While the implementation is inspired by functions in `SchemaNode`,
|
||||
imlementation of `Module::parent()` and `Module::siblings()` was omitted
|
||||
as those do no make sense on `Module`.
|
||||
|
||||
Change-Id: I38c8374304f859d65343d04d08302e07deb05f27
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/Collection.hpp | 1 +
|
||||
include/libyang-cpp/Module.hpp | 5 +
|
||||
src/Module.cpp | 40 +++
|
||||
tests/context.cpp | 5 +
|
||||
tests/example_schema.hpp | 21 ++
|
||||
tests/schema_node.cpp | 409 +++++++++++++++++++----------
|
||||
6 files changed, 346 insertions(+), 135 deletions(-)
|
||||
|
||||
diff --git a/include/libyang-cpp/Collection.hpp b/include/libyang-cpp/Collection.hpp
|
||||
index 557a0a2..4324791 100644
|
||||
--- a/include/libyang-cpp/Collection.hpp
|
||||
+++ b/include/libyang-cpp/Collection.hpp
|
||||
@@ -98,6 +98,7 @@ class LIBYANG_CPP_EXPORT Collection {
|
||||
public:
|
||||
friend DataNode;
|
||||
friend Iterator<NodeType, ITER_TYPE>;
|
||||
+ friend Module;
|
||||
friend SchemaNode;
|
||||
~Collection();
|
||||
Collection(const Collection<NodeType, ITER_TYPE>&);
|
||||
diff --git a/include/libyang-cpp/Module.hpp b/include/libyang-cpp/Module.hpp
|
||||
index f10c36f..ab20d36 100644
|
||||
--- a/include/libyang-cpp/Module.hpp
|
||||
+++ b/include/libyang-cpp/Module.hpp
|
||||
@@ -34,6 +34,8 @@ class ChildInstanstiables;
|
||||
class Identity;
|
||||
class SchemaNode;
|
||||
class SubmoduleParsed;
|
||||
+template <typename NodeType, IterationType ITER_TYPE>
|
||||
+class Collection;
|
||||
|
||||
namespace types {
|
||||
class IdentityRef;
|
||||
@@ -86,7 +88,10 @@ public:
|
||||
|
||||
std::vector<Identity> identities() const;
|
||||
|
||||
+ std::optional<SchemaNode> child() const;
|
||||
ChildInstanstiables childInstantiables() const;
|
||||
+ libyang::Collection<SchemaNode, IterationType::Dfs> childrenDfs() const;
|
||||
+ Collection<SchemaNode, IterationType::Sibling> immediateChildren() const;
|
||||
std::vector<SchemaNode> actionRpcs() const;
|
||||
|
||||
std::string printStr(const SchemaOutputFormat format, const std::optional<SchemaPrintFlags> flags = std::nullopt, std::optional<size_t> lineLength = std::nullopt) const;
|
||||
diff --git a/src/Module.cpp b/src/Module.cpp
|
||||
index 4dc9e3b..d6d4023 100644
|
||||
--- a/src/Module.cpp
|
||||
+++ b/src/Module.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <libyang-cpp/ChildInstantiables.hpp>
|
||||
+#include <libyang-cpp/Collection.hpp>
|
||||
#include <libyang-cpp/Module.hpp>
|
||||
#include <libyang-cpp/Utils.hpp>
|
||||
#include <libyang/libyang.h>
|
||||
@@ -178,6 +179,23 @@ std::vector<Identity> Module::identities() const
|
||||
return res;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns the first child node of this module.
|
||||
+ * @return The child, or std::nullopt if there are no children.
|
||||
+ */
|
||||
+std::optional<SchemaNode> Module::child() const
|
||||
+{
|
||||
+ if (!m_module->implemented) {
|
||||
+ throw Error{"Module::child: module is not implemented"};
|
||||
+ }
|
||||
+
|
||||
+ if (!m_module->compiled->data) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ return SchemaNode{m_module->compiled->data, m_ctx};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection of data instantiable top-level nodes of this module.
|
||||
*
|
||||
@@ -191,6 +209,28 @@ ChildInstanstiables Module::childInstantiables() const
|
||||
return ChildInstanstiables{nullptr, m_module->compiled, m_ctx};
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns a collection for iterating depth-first over the subtree this module points to.
|
||||
+ */
|
||||
+Collection<SchemaNode, IterationType::Dfs> Module::childrenDfs() const
|
||||
+{
|
||||
+ if (!m_module->implemented) {
|
||||
+ throw Error{"Module::childrenDfs: module is not implemented"};
|
||||
+ }
|
||||
+ return Collection<SchemaNode, IterationType::Dfs>{m_module->compiled->data, m_ctx};
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Returns a collection for iterating over the immediate children of where this module points to.
|
||||
+ *
|
||||
+ * This is a convenience function for iterating over this->child().siblings() which does not throw even when module has no children.
|
||||
+ */
|
||||
+Collection<SchemaNode, IterationType::Sibling> Module::immediateChildren() const
|
||||
+{
|
||||
+ auto c = child();
|
||||
+ return c ? c->siblings() : Collection<SchemaNode, IterationType::Sibling>{nullptr, nullptr};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection of RPC nodes (not action nodes) as SchemaNode
|
||||
*
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 5929b75..25343db 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -713,6 +713,11 @@ TEST_CASE("context")
|
||||
+--rw anydataWithMandatoryChild anydata
|
||||
+--rw anyxmlBasic? anyxml
|
||||
+--rw anyxmlWithMandatoryChild anyxml
|
||||
+ +--rw (choiceOnModule)?
|
||||
+ | +--:(case1)
|
||||
+ | | +--rw choiceOnModuleLeaf1? string
|
||||
+ | +--:(case2)
|
||||
+ | +--rw choiceOnModuleLeaf2? string
|
||||
+--rw choiceBasicContainer
|
||||
| +--rw (choiceBasic)?
|
||||
| +--:(case1)
|
||||
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||
index ae3b4de..0d8acb9 100644
|
||||
--- a/tests/example_schema.hpp
|
||||
+++ b/tests/example_schema.hpp
|
||||
@@ -390,6 +390,19 @@ module type_module {
|
||||
mandatory true;
|
||||
}
|
||||
|
||||
+ choice choiceOnModule {
|
||||
+ case case1 {
|
||||
+ leaf choiceOnModuleLeaf1 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ case case2 {
|
||||
+ leaf choiceOnModuleLeaf2 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
container choiceBasicContainer {
|
||||
choice choiceBasic {
|
||||
case case1 {
|
||||
@@ -787,6 +800,14 @@ module type_module {
|
||||
}
|
||||
)"s;
|
||||
|
||||
+const auto empty_module = R"(
|
||||
+module empty_module {
|
||||
+ yang-version 1.1;
|
||||
+ namespace "e";
|
||||
+ prefix "e";
|
||||
+}
|
||||
+)"s;
|
||||
+
|
||||
const auto with_inet_types_module = R"(
|
||||
module with-inet-types {
|
||||
yang-version 1.1;
|
||||
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||
index 8d74bd2..0001377 100644
|
||||
--- a/tests/schema_node.cpp
|
||||
+++ b/tests/schema_node.cpp
|
||||
@@ -24,8 +24,10 @@ TEST_CASE("SchemaNode")
|
||||
libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
|
||||
ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
ctx->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ ctx->parseModule(empty_module, libyang::SchemaFormat::YANG);
|
||||
ctxWithParsed->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
ctxWithParsed->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ ctxWithParsed->parseModule(empty_module, libyang::SchemaFormat::YANG);
|
||||
|
||||
DOCTEST_SUBCASE("context lifetime")
|
||||
{
|
||||
@@ -74,10 +76,34 @@ TEST_CASE("SchemaNode")
|
||||
REQUIRE(node->schema().path() == "/example-schema:person");
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::child")
|
||||
+ DOCTEST_SUBCASE("child")
|
||||
{
|
||||
- REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first");
|
||||
- REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value());
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("SchemaNode::child")
|
||||
+ {
|
||||
+ REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first");
|
||||
+ REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value());
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Module::child")
|
||||
+ {
|
||||
+ REQUIRE(ctx->getModule("type_module", std::nullopt)->child()->name() == "anydataBasic");
|
||||
+ REQUIRE(!ctx->getModule("empty_module", std::nullopt)->child());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::child")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->child(), "Module::child: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("SchemaNode::config")
|
||||
@@ -160,162 +186,275 @@ TEST_CASE("SchemaNode")
|
||||
|
||||
DOCTEST_SUBCASE("childInstantiables")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
- std::optional<libyang::ChildInstanstiables> children;
|
||||
-
|
||||
- DOCTEST_SUBCASE("SchemaNode::childInstantiables")
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithOneKey/lol",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey1",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey2",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey3",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey4",
|
||||
- };
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::ChildInstanstiables> children;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("SchemaNode::childInstantiables")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithOneKey/lol",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey1",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey2",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey3",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey4",
|
||||
+ };
|
||||
+
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables();
|
||||
+ }
|
||||
|
||||
- children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables();
|
||||
- }
|
||||
+ DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ "/type_module:anydataWithMandatoryChild",
|
||||
+ "/type_module:anyxmlBasic",
|
||||
+ "/type_module:anyxmlWithMandatoryChild",
|
||||
+ "/type_module:choiceOnModuleLeaf1",
|
||||
+ "/type_module:choiceOnModuleLeaf2",
|
||||
+ "/type_module:choiceBasicContainer",
|
||||
+ "/type_module:choiceWithMandatoryContainer",
|
||||
+ "/type_module:choiceWithDefaultContainer",
|
||||
+ "/type_module:implicitCaseContainer",
|
||||
+ "/type_module:leafBinary",
|
||||
+ "/type_module:leafBits",
|
||||
+ "/type_module:leafEnum",
|
||||
+ "/type_module:leafEnum2",
|
||||
+ "/type_module:leafNumber",
|
||||
+ "/type_module:leafRef",
|
||||
+ "/type_module:leafRefRelaxed",
|
||||
+ "/type_module:leafString",
|
||||
+ "/type_module:leafUnion",
|
||||
+ "/type_module:meal",
|
||||
+ "/type_module:leafWithConfigFalse",
|
||||
+ "/type_module:leafWithDefaultValue",
|
||||
+ "/type_module:leafWithDescription",
|
||||
+ "/type_module:leafWithMandatoryTrue",
|
||||
+ "/type_module:leafWithStatusDeprecated",
|
||||
+ "/type_module:leafWithStatusObsolete",
|
||||
+ "/type_module:leafWithUnits",
|
||||
+ "/type_module:iid-valid",
|
||||
+ "/type_module:iid-relaxed",
|
||||
+ "/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
+ "/type_module:leafListWithMinMaxElements",
|
||||
+ "/type_module:leafListWithUnits",
|
||||
+ "/type_module:listBasic",
|
||||
+ "/type_module:listAdvancedWithOneKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listWithMinMaxElements",
|
||||
+ "/type_module:numeric",
|
||||
+ "/type_module:container",
|
||||
+ "/type_module:containerWithMandatoryChild",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->childInstantiables();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:anydataBasic",
|
||||
- "/type_module:anydataWithMandatoryChild",
|
||||
- "/type_module:anyxmlBasic",
|
||||
- "/type_module:anyxmlWithMandatoryChild",
|
||||
- "/type_module:choiceBasicContainer",
|
||||
- "/type_module:choiceWithMandatoryContainer",
|
||||
- "/type_module:choiceWithDefaultContainer",
|
||||
- "/type_module:implicitCaseContainer",
|
||||
- "/type_module:leafBinary",
|
||||
- "/type_module:leafBits",
|
||||
- "/type_module:leafEnum",
|
||||
- "/type_module:leafEnum2",
|
||||
- "/type_module:leafNumber",
|
||||
- "/type_module:leafRef",
|
||||
- "/type_module:leafRefRelaxed",
|
||||
- "/type_module:leafString",
|
||||
- "/type_module:leafUnion",
|
||||
- "/type_module:meal",
|
||||
- "/type_module:leafWithConfigFalse",
|
||||
- "/type_module:leafWithDefaultValue",
|
||||
- "/type_module:leafWithDescription",
|
||||
- "/type_module:leafWithMandatoryTrue",
|
||||
- "/type_module:leafWithStatusDeprecated",
|
||||
- "/type_module:leafWithStatusObsolete",
|
||||
- "/type_module:leafWithUnits",
|
||||
- "/type_module:iid-valid",
|
||||
- "/type_module:iid-relaxed",
|
||||
- "/type_module:leafListBasic",
|
||||
- "/type_module:leafListWithDefault",
|
||||
- "/type_module:leafListWithMinMaxElements",
|
||||
- "/type_module:leafListWithUnits",
|
||||
- "/type_module:listBasic",
|
||||
- "/type_module:listAdvancedWithOneKey",
|
||||
- "/type_module:listAdvancedWithTwoKey",
|
||||
- "/type_module:listWithMinMaxElements",
|
||||
- "/type_module:numeric",
|
||||
- "/type_module:container",
|
||||
- "/type_module:containerWithMandatoryChild",
|
||||
- };
|
||||
- children = ctx->getModule("type_module", std::nullopt)->childInstantiables();
|
||||
- }
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& child : *children) {
|
||||
+ actualPaths.emplace_back(child.path());
|
||||
+ }
|
||||
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& child : *children) {
|
||||
- actualPaths.emplace_back(child.path());
|
||||
+ REQUIRE(expectedPaths == actualPaths);
|
||||
}
|
||||
|
||||
- REQUIRE(expectedPaths == actualPaths);
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->childInstantiables(), "Module::childInstantiables: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::childrenDfs")
|
||||
+ DOCTEST_SUBCASE("childrenDfs")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
+ {
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::Collection<libyang::SchemaNode, libyang::IterationType::Dfs>> children;
|
||||
|
||||
- const char* path;
|
||||
+ DOCTEST_SUBCASE("SchemaNode::childrenDfs")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey/first",
|
||||
+ "/type_module:listAdvancedWithTwoKey/second",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").childrenDfs();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithTwoKey",
|
||||
- "/type_module:listAdvancedWithTwoKey/first",
|
||||
- "/type_module:listAdvancedWithTwoKey/second",
|
||||
- };
|
||||
+ DOCTEST_SUBCASE("DFS on a leaf")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:leafString",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:leafString").childrenDfs();
|
||||
+ }
|
||||
+ }
|
||||
|
||||
- path = "/type_module:listAdvancedWithTwoKey";
|
||||
- }
|
||||
+ DOCTEST_SUBCASE("Module::childrenDfs")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->childrenDfs();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("DFS on a leaf")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:leafString",
|
||||
- };
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& it : *children) {
|
||||
+ actualPaths.emplace_back(it.path());
|
||||
+ }
|
||||
|
||||
- path = "/type_module:leafString";
|
||||
+ REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& it : ctx->findPath(path).childrenDfs()) {
|
||||
- actualPaths.emplace_back(it.path());
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::childrenDfs")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->childrenDfs(), "Module::childrenDfs: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
}
|
||||
-
|
||||
- REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::immediateChildren")
|
||||
+ DOCTEST_SUBCASE("immediateChildren")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
- const char* path;
|
||||
- DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithTwoKey/first",
|
||||
- "/type_module:listAdvancedWithTwoKey/second",
|
||||
- };
|
||||
- path = "/type_module:listAdvancedWithTwoKey";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("leaf")
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- };
|
||||
- path = "/type_module:leafString";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("no recursion")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/x",
|
||||
- "/type_module:container/y",
|
||||
- "/type_module:container/z",
|
||||
- };
|
||||
- path = "/type_module:container";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("empty container")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- };
|
||||
- path = "/type_module:container/y";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("one item")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/z/z1",
|
||||
- };
|
||||
- path = "/type_module:container/z";
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::Collection<libyang::SchemaNode, libyang::IterationType::Sibling>> children;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("SchemaNode::immediateChildren")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithTwoKey/first",
|
||||
+ "/type_module:listAdvancedWithTwoKey/second",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("leaf")
|
||||
+ {
|
||||
+ expectedPaths = {};
|
||||
+ children = ctx->findPath("/type_module:leafString").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("no recursion")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/x",
|
||||
+ "/type_module:container/y",
|
||||
+ "/type_module:container/z",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("empty container")
|
||||
+ {
|
||||
+ expectedPaths = {};
|
||||
+ children = ctx->findPath("/type_module:container/y").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("one item")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/z/z1",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container/z").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("two items")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/x/x1",
|
||||
+ "/type_module:container/x/x2",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container/x").immediateChildren();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Module::immediateChildren")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ "/type_module:anydataWithMandatoryChild",
|
||||
+ "/type_module:anyxmlBasic",
|
||||
+ "/type_module:anyxmlWithMandatoryChild",
|
||||
+ // choiceOnModule is a choice, so it doesn't have path "/type_module:choiceOnModule".
|
||||
+ // This node is tested at the end of the test subcase.
|
||||
+ "/",
|
||||
+ "/type_module:choiceBasicContainer",
|
||||
+ "/type_module:choiceWithMandatoryContainer",
|
||||
+ "/type_module:choiceWithDefaultContainer",
|
||||
+ "/type_module:implicitCaseContainer",
|
||||
+ "/type_module:leafBinary",
|
||||
+ "/type_module:leafBits",
|
||||
+ "/type_module:leafEnum",
|
||||
+ "/type_module:leafEnum2",
|
||||
+ "/type_module:leafNumber",
|
||||
+ "/type_module:leafRef",
|
||||
+ "/type_module:leafRefRelaxed",
|
||||
+ "/type_module:leafString",
|
||||
+ "/type_module:leafUnion",
|
||||
+ "/type_module:meal",
|
||||
+ "/type_module:leafWithConfigFalse",
|
||||
+ "/type_module:leafWithDefaultValue",
|
||||
+ "/type_module:leafWithDescription",
|
||||
+ "/type_module:leafWithMandatoryTrue",
|
||||
+ "/type_module:leafWithStatusDeprecated",
|
||||
+ "/type_module:leafWithStatusObsolete",
|
||||
+ "/type_module:leafWithUnits",
|
||||
+ "/type_module:iid-valid",
|
||||
+ "/type_module:iid-relaxed",
|
||||
+ "/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
+ "/type_module:leafListWithMinMaxElements",
|
||||
+ "/type_module:leafListWithUnits",
|
||||
+ "/type_module:listBasic",
|
||||
+ "/type_module:listAdvancedWithOneKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listWithMinMaxElements",
|
||||
+ "/type_module:numeric",
|
||||
+ "/type_module:container",
|
||||
+ "/type_module:containerWithMandatoryChild",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->immediateChildren();
|
||||
+
|
||||
+ std::vector<std::string> actualNames;
|
||||
+ for (auto it : children.value()) {
|
||||
+ actualNames.emplace_back(it.name());
|
||||
+ }
|
||||
+ // choiceOnModule is a choice, so it doesn't have path, just name.
|
||||
+ REQUIRE(actualNames[4] == "choiceOnModule");
|
||||
+ }
|
||||
+
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& it : *children) {
|
||||
+ actualPaths.emplace_back(it.path());
|
||||
+ }
|
||||
+ REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
- DOCTEST_SUBCASE("two items")
|
||||
+
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/x/x1",
|
||||
- "/type_module:container/x/x2",
|
||||
- };
|
||||
- path = "/type_module:container/x";
|
||||
- }
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& it : ctx->findPath(path).immediateChildren()) {
|
||||
- actualPaths.emplace_back(it.path());
|
||||
+ DOCTEST_SUBCASE("Module::immediateChildren")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->immediateChildren(), "Module::child: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
}
|
||||
- REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("SchemaNode::siblings")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
From 39c7530caa510144c17521278b721ba1e6d8ff40 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Thu, 9 Jan 2025 15:31:37 +0100
|
||||
Subject: [PATCH 8/8] upstream stopped reporting schema-mounts node
|
||||
Subject: [PATCH 09/18] upstream stopped reporting schema-mounts node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
@@ -0,0 +1,39 @@
|
||||
From 82c963766ad4e4a802db7be656acbedb640745e9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 30 Jan 2025 16:34:12 +0100
|
||||
Subject: [PATCH 10/18] CI: temporarily pin version due to anyxml behavior
|
||||
changes upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Id977f4d045098c1b93656c0efb871c9a1b650e2d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index b41c490..19f0def 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,13 +4,13 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-29
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-29
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
From 50a216e35a555961f94a32a71bb2d45ac611d0aa Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 29 Jan 2025 22:49:08 +0100
|
||||
Subject: [PATCH 11/18] build: a single place to define package version
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I2cd7397895ed4852f852e99b97543dde76eaff8f
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 732f52b..c518ca8 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -21,7 +21,8 @@ add_custom_target(libyang-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-prepare_git_version(LIBYANG_CPP_VERSION "3")
|
||||
+set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
+prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${DOXYGEN_FOUND})
|
||||
@@ -29,7 +30,6 @@ option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a st
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET)
|
||||
-set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
From 1533458346b4f395b1187c646b61bbcb1fddc615 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 4 Nov 2024 14:52:12 +0100
|
||||
Subject: [PATCH 12/18] YANG-flavored regular expressions
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I93b2756d0f470585280c076308df3f384bd7765d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 +++
|
||||
include/libyang-cpp/Regex.hpp | 28 ++++++++++++++++++++++
|
||||
src/Regex.cpp | 45 +++++++++++++++++++++++++++++++++++
|
||||
tests/regex.cpp | 30 +++++++++++++++++++++++
|
||||
4 files changed, 106 insertions(+)
|
||||
create mode 100644 include/libyang-cpp/Regex.hpp
|
||||
create mode 100644 src/Regex.cpp
|
||||
create mode 100644 tests/regex.cpp
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c518ca8..512af8c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -54,6 +54,7 @@ add_library(yang-cpp
|
||||
src/Enum.cpp
|
||||
src/Collection.cpp
|
||||
src/Module.cpp
|
||||
+ src/Regex.cpp
|
||||
src/SchemaNode.cpp
|
||||
src/Set.cpp
|
||||
src/Type.cpp
|
||||
@@ -83,6 +84,7 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24")
|
||||
libyang-cpp/Enum.hpp
|
||||
libyang-cpp/ChildInstantiables.hpp
|
||||
libyang-cpp/Module.hpp
|
||||
+ libyang-cpp/Regex.hpp
|
||||
libyang-cpp/Set.hpp
|
||||
libyang-cpp/SchemaNode.hpp
|
||||
libyang-cpp/Time.hpp
|
||||
@@ -119,6 +121,7 @@ if(BUILD_TESTING)
|
||||
libyang_cpp_test(schema_node)
|
||||
libyang_cpp_test(unsafe)
|
||||
target_link_libraries(test_unsafe PkgConfig::LIBYANG)
|
||||
+ libyang_cpp_test(regex)
|
||||
|
||||
if(date_FOUND)
|
||||
add_executable(test_time-stl-hhdate tests/time.cpp)
|
||||
diff --git a/include/libyang-cpp/Regex.hpp b/include/libyang-cpp/Regex.hpp
|
||||
new file mode 100644
|
||||
index 0000000..31935f2
|
||||
--- /dev/null
|
||||
+++ b/include/libyang-cpp/Regex.hpp
|
||||
@@ -0,0 +1,28 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+#pragma once
|
||||
+#include <libyang-cpp/export.h>
|
||||
+#include <string>
|
||||
+
|
||||
+namespace libyang {
|
||||
+class Context;
|
||||
+
|
||||
+/**
|
||||
+ * @brief A regular expression pattern which uses the YANG-flavored regex engine
|
||||
+ */
|
||||
+class LIBYANG_CPP_EXPORT Regex {
|
||||
+public:
|
||||
+ Regex(const std::string& pattern);
|
||||
+ ~Regex();
|
||||
+ bool matches(const std::string& input);
|
||||
+
|
||||
+private:
|
||||
+ void* code;
|
||||
+};
|
||||
+
|
||||
+}
|
||||
diff --git a/src/Regex.cpp b/src/Regex.cpp
|
||||
new file mode 100644
|
||||
index 0000000..a34fcd5
|
||||
--- /dev/null
|
||||
+++ b/src/Regex.cpp
|
||||
@@ -0,0 +1,45 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+
|
||||
+// The following header MUST be included before anything else which "might" use PCRE2
|
||||
+// because that library uses the preprocessor to prepare the "correct" versions of symbols.
|
||||
+#include <libyang/tree_data.h>
|
||||
+
|
||||
+#include <libyang-cpp/Regex.hpp>
|
||||
+#include <pcre2.h>
|
||||
+#include "utils/exception.hpp"
|
||||
+
|
||||
+#define THE_PCRE2_CODE_P reinterpret_cast<pcre2_code*>(this->code)
|
||||
+#define THE_PCRE2_CODE_P_P reinterpret_cast<pcre2_code**>(&this->code)
|
||||
+
|
||||
+namespace libyang {
|
||||
+
|
||||
+Regex::Regex(const std::string& pattern)
|
||||
+ : code(nullptr)
|
||||
+{
|
||||
+ auto res = ly_pattern_compile(nullptr, pattern.c_str(), THE_PCRE2_CODE_P_P);
|
||||
+ throwIfError(res, ly_last_logmsg());
|
||||
+}
|
||||
+
|
||||
+Regex::~Regex()
|
||||
+{
|
||||
+ pcre2_code_free(THE_PCRE2_CODE_P);
|
||||
+}
|
||||
+
|
||||
+bool Regex::matches(const std::string& input)
|
||||
+{
|
||||
+ auto res = ly_pattern_match(nullptr, nullptr /* we have a precompiled pattern */, input.c_str(), input.size(), THE_PCRE2_CODE_P_P);
|
||||
+ if (res == LY_SUCCESS) {
|
||||
+ return true;
|
||||
+ } else if (res == LY_ENOT) {
|
||||
+ return false;
|
||||
+ } else {
|
||||
+ throwError(res, ly_last_logmsg());
|
||||
+ }
|
||||
+}
|
||||
+}
|
||||
diff --git a/tests/regex.cpp b/tests/regex.cpp
|
||||
new file mode 100644
|
||||
index 0000000..7594f43
|
||||
--- /dev/null
|
||||
+++ b/tests/regex.cpp
|
||||
@@ -0,0 +1,30 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+
|
||||
+#include <doctest/doctest.h>
|
||||
+#include <libyang-cpp/Regex.hpp>
|
||||
+#include <libyang-cpp/Utils.hpp>
|
||||
+
|
||||
+TEST_CASE("regex")
|
||||
+{
|
||||
+ using libyang::Regex;
|
||||
+ using namespace std::string_literals;
|
||||
+
|
||||
+ REQUIRE_THROWS_WITH_AS(Regex{"\\"}, R"(Regular expression "\" is not valid ("": \ at end of pattern).: LY_EVALID)", libyang::ErrorWithCode);
|
||||
+
|
||||
+ Regex re{"ahoj"};
|
||||
+ REQUIRE(re.matches("ahoj"));
|
||||
+ REQUIRE(!re.matches("cau"));
|
||||
+ REQUIRE(re.matches("ahoj")); // test repeated calls as well
|
||||
+ REQUIRE(!re.matches("oj"));
|
||||
+ REQUIRE(!re.matches("aho"));
|
||||
+
|
||||
+ // Testing runtime errors during pattern *matching* is tricky. There's a limit on backtracking,
|
||||
+ // so testing a pattern like x+x+y on an obscenely long string of "x" characters *will* do the trick, eventually,
|
||||
+ // but the PCRE2 library has a default limit of 10M attempts. That's a VERY big number to hit during a test :(.
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
From 8d406728a53c2e77a4fe7393b7e30d42b8f9b9bb Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 30 Jan 2025 15:40:22 +0100
|
||||
Subject: [PATCH 13/18] Adapt to upstream changes in anyxml JSON printing
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5f6de28cebc95a446549017c2768b450f4fd6526
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
CMakeLists.txt | 3 ++-
|
||||
tests/data_node.cpp | 2 +-
|
||||
3 files changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index 19f0def..b41c490 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,13 +4,13 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-01-29
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-01-29
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 512af8c..a40fd52 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -29,7 +29,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET)
|
||||
+# FIXME: it's actually 3.7.12, but that hasn't been released yet
|
||||
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET)
|
||||
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 45fd6c1..14470dd 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1568,7 +1568,7 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||
== R"|({"example-schema:ax":[1,2,3]})|"s);
|
||||
REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||
- == R"|(<ax xmlns="http://example.com/coze"/>)|"s);
|
||||
+ == R"|(<ax xmlns="http://example.com/coze">)|"s + origJSON + "</ax>");
|
||||
}
|
||||
|
||||
REQUIRE(!!val);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
From f050e7e4a17ef2e221ca000a544042c33c9541fc Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 19:21:26 +0100
|
||||
Subject: [PATCH 14/18] fix DataNode::insertSibling() return value
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
When such an insert happens, the C library returns the node which is now
|
||||
the first sibling among all of the siblings of the node which was used
|
||||
as a reference during the insert. Our API was also documented this way,
|
||||
but we were not doing that.
|
||||
|
||||
Reported-by: Irfan <irfan.haslanded@gmail.com>
|
||||
Bug: https://github.com/CESNET/libyang-cpp/issues/29
|
||||
Change-Id: Id7f84a31e50212d6e2cbce9ab03a351a3721f767
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/DataNode.cpp | 2 +-
|
||||
tests/data_node.cpp | 7 +++++++
|
||||
2 files changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 84591e5..b899b18 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -711,7 +711,7 @@ DataNode DataNode::insertSibling(DataNode toInsert)
|
||||
lyd_insert_sibling(this->m_node, toInsert.m_node, &firstSibling);
|
||||
}, toInsert.parent() ? OperationScope::JustThisNode : OperationScope::AffectsFollowingSiblings, m_refs);
|
||||
|
||||
- return DataNode{m_node, m_refs};
|
||||
+ return DataNode{firstSibling, m_refs};
|
||||
}
|
||||
|
||||
/**
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 14470dd..b6ee455 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -973,6 +973,13 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(getNumberOrder() == expected);
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("DataNode::insertSibling")
|
||||
+ {
|
||||
+ auto node = ctx.newPath("/example-schema:leafUInt8", "10");
|
||||
+ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:leafUInt16", "10")).path() == "/example-schema:leafUInt8");
|
||||
+ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:dummy", "10")).path() == "/example-schema:dummy");
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("DataNode::duplicate")
|
||||
{
|
||||
auto root = ctx.parseData(data2, libyang::DataFormat::JSON);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
From f958af42bf5d9fbd901ed59ebc1359ac0ddcc00f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 11:36:50 +0100
|
||||
Subject: [PATCH 15/18] API/ABI change: opaque node naming
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Our C++ API would ignore the "module name or XML prefix", which turns
|
||||
out to be *the* relevant part when it comes to opaque node naming. The
|
||||
prefix is, instead, just that string that might have been inherited from
|
||||
the parent node when parsing the serialized data; it's an optional
|
||||
thingy which, if not set explicitly, is implicitly inherited.
|
||||
|
||||
Adapt the API for this, and since this *will* break the build, let's
|
||||
bump the package version.
|
||||
|
||||
Change-Id: I199afe5fa7a571034b744531c63b93b9c656563a
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 5 ++---
|
||||
include/libyang-cpp/Context.hpp | 4 ++--
|
||||
include/libyang-cpp/DataNode.hpp | 11 ++++++++--
|
||||
src/Context.cpp | 32 ++++++++++++++++++++--------
|
||||
src/DataNode.cpp | 19 ++++++++++++++---
|
||||
tests/data_node.cpp | 36 ++++++++++++++++++++++++++------
|
||||
6 files changed, 82 insertions(+), 25 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index a40fd52..c5fec45 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -21,7 +21,7 @@ add_custom_target(libyang-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
+set(LIBYANG_CPP_PKG_VERSION "4")
|
||||
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,8 +29,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
-# FIXME: it's actually 3.7.12, but that hasn't been released yet
|
||||
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET)
|
||||
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET)
|
||||
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp
|
||||
index baa47b3..ca89063 100644
|
||||
--- a/include/libyang-cpp/Context.hpp
|
||||
+++ b/include/libyang-cpp/Context.hpp
|
||||
@@ -115,8 +115,8 @@ public:
|
||||
CreatedNodes newPath2(const std::string& path, libyang::JSON json, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
CreatedNodes newPath2(const std::string& path, libyang::XML xml, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
std::optional<DataNode> newExtPath(const ExtensionInstance& ext, const std::string& path, const std::optional<std::string>& value, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
- std::optional<DataNode> newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional<libyang::JSON>& value) const;
|
||||
- std::optional<DataNode> newOpaqueXML(const std::string& moduleName, const std::string& name, const std::optional<libyang::XML>& value) const;
|
||||
+ std::optional<DataNode> newOpaqueJSON(const OpaqueName& name, const std::optional<libyang::JSON>& value) const;
|
||||
+ std::optional<DataNode> newOpaqueXML(const OpaqueName& name, const std::optional<libyang::XML>& value) const;
|
||||
SchemaNode findPath(const std::string& dataPath, const InputOutputNodes inputOutputNodes = InputOutputNodes::Input) const;
|
||||
Set<SchemaNode> findXPath(const std::string& path) const;
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 851681b..310b5e3 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -225,15 +225,22 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
- * @brief Contains a (possibly module-qualified) name of an opaque node.
|
||||
+ * @brief Contains a name of an opaque node.
|
||||
*
|
||||
- * This is generic container of a prefix/module string and a name string.
|
||||
+ * An opaque node always has a name, and a module (or XML namespace) to which this node belongs.
|
||||
+ * Sometimes, it also has a prefix.
|
||||
+ *
|
||||
+ * If the prefix is set *and* the underlying node is an opaque JSON one, then the prefix must be the same as the "module or namespace" name.
|
||||
+ * If the underlying node is an opaque XML one, then the XML prefix might be something completely different, and in that case the real fun begins.
|
||||
+ * Review the libayng C manual, this is something that the C++ wrapper doesn't really have under control.
|
||||
*
|
||||
* Wraps `ly_opaq_name`.
|
||||
*/
|
||||
struct LIBYANG_CPP_EXPORT OpaqueName {
|
||||
+ std::string moduleOrNamespace;
|
||||
std::optional<std::string> prefix;
|
||||
std::string name;
|
||||
+ std::string pretty() const;
|
||||
};
|
||||
|
||||
/**
|
||||
diff --git a/src/Context.cpp b/src/Context.cpp
|
||||
index 287f8c8..fec2f27 100644
|
||||
--- a/src/Context.cpp
|
||||
+++ b/src/Context.cpp
|
||||
@@ -378,17 +378,25 @@ std::optional<DataNode> Context::newExtPath(const ExtensionInstance& ext, const
|
||||
*
|
||||
* Wraps `lyd_new_opaq`.
|
||||
*
|
||||
- * @param moduleName Node module name, used as a prefix as well
|
||||
* @param name Name of the created node
|
||||
* @param value JSON data blob, if any
|
||||
* @return Returns the newly created node (if created)
|
||||
*/
|
||||
-std::optional<DataNode> Context::newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional<libyang::JSON>& value) const
|
||||
+std::optional<DataNode> Context::newOpaqueJSON(const OpaqueName& name, const std::optional<libyang::JSON>& value) const
|
||||
{
|
||||
+ if (name.prefix && *name.prefix != name.moduleOrNamespace) {
|
||||
+ throw Error{"invalid opaque JSON node: prefix \"" + *name.prefix + "\" doesn't match module name \"" + name.moduleOrNamespace + "\""};
|
||||
+ }
|
||||
lyd_node* out;
|
||||
- auto err = lyd_new_opaq(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, moduleName.c_str(), &out);
|
||||
+ auto err = lyd_new_opaq(nullptr,
|
||||
+ m_ctx.get(),
|
||||
+ name.name.c_str(),
|
||||
+ value ? value->content.c_str() : nullptr,
|
||||
+ name.prefix ? name.prefix->c_str() : nullptr,
|
||||
+ name.moduleOrNamespace.c_str(),
|
||||
+ &out);
|
||||
|
||||
- throwIfError(err, "Couldn't create an opaque JSON node '"s + moduleName + ':' + name + "'");
|
||||
+ throwIfError(err, "Couldn't create an opaque JSON node " + name.pretty());
|
||||
|
||||
if (out) {
|
||||
return DataNode{out, std::make_shared<internal_refcount>(m_ctx)};
|
||||
@@ -403,17 +411,23 @@ std::optional<DataNode> Context::newOpaqueJSON(const std::string& moduleName, co
|
||||
*
|
||||
* Wraps `lyd_new_opaq2`.
|
||||
*
|
||||
- * @param xmlNamespace Node module namespace
|
||||
* @param name Name of the created node
|
||||
* @param value XML data blob, if any
|
||||
* @return Returns the newly created node (if created)
|
||||
*/
|
||||
-std::optional<DataNode> Context::newOpaqueXML(const std::string& xmlNamespace, const std::string& name, const std::optional<libyang::XML>& value) const
|
||||
+std::optional<DataNode> Context::newOpaqueXML(const OpaqueName& name, const std::optional<libyang::XML>& value) const
|
||||
{
|
||||
+ // the XML node naming is "complex", we cannot really check the XML namespace for sanity here
|
||||
lyd_node* out;
|
||||
- auto err = lyd_new_opaq2(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, xmlNamespace.c_str(), &out);
|
||||
-
|
||||
- throwIfError(err, "Couldn't create an opaque XML node '"s + name +"' from namespace '" + xmlNamespace + "'");
|
||||
+ auto err = lyd_new_opaq2(nullptr,
|
||||
+ m_ctx.get(),
|
||||
+ name.name.c_str(),
|
||||
+ value ? value->content.c_str() : nullptr,
|
||||
+ name.prefix ? name.prefix->c_str() : nullptr,
|
||||
+ name.moduleOrNamespace.c_str(),
|
||||
+ &out);
|
||||
+
|
||||
+ throwIfError(err, "Couldn't create an opaque XML node " + name.pretty());
|
||||
|
||||
if (out) {
|
||||
return DataNode{out, std::make_shared<internal_refcount>(m_ctx)};
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index b899b18..344f1b6 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1112,9 +1112,9 @@ OpaqueName DataNodeOpaque::name() const
|
||||
{
|
||||
auto opaq = reinterpret_cast<lyd_node_opaq*>(m_node);
|
||||
return OpaqueName{
|
||||
- .prefix = opaq->name.prefix ? std::optional(opaq->name.prefix) : std::nullopt,
|
||||
- .name = opaq->name.name
|
||||
- };
|
||||
+ .moduleOrNamespace = opaq->name.module_name,
|
||||
+ .prefix = opaq->name.prefix ? std::optional{opaq->name.prefix} : std::nullopt,
|
||||
+ .name = opaq->name.name};
|
||||
}
|
||||
|
||||
std::string DataNodeOpaque::value() const
|
||||
@@ -1122,6 +1122,19 @@ std::string DataNodeOpaque::value() const
|
||||
return reinterpret_cast<lyd_node_opaq*>(m_node)->value;
|
||||
}
|
||||
|
||||
+std::string OpaqueName::pretty() const
|
||||
+{
|
||||
+ if (prefix) {
|
||||
+ if (*prefix == moduleOrNamespace) {
|
||||
+ return *prefix + ':' + name;
|
||||
+ } else {
|
||||
+ return "{" + moduleOrNamespace + "}, " + *prefix + ':' + name;
|
||||
+ }
|
||||
+ } else {
|
||||
+ return "{" + moduleOrNamespace + "}, " + name;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Wraps a raw non-null lyd_node pointer.
|
||||
* @param node The pointer to be wrapped. Must not be null.
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index b6ee455..a1096a6 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1969,9 +1969,11 @@ TEST_CASE("Data Node manipulation")
|
||||
|
||||
DOCTEST_SUBCASE("opaque nodes for sysrepo ops data discard")
|
||||
{
|
||||
- auto discard1 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:a"});
|
||||
+ // the "short" form with no prefix
|
||||
+ auto discard1 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", std::nullopt, "discard-items"}, libyang::JSON{"/example-schema:a"});
|
||||
REQUIRE(!!discard1);
|
||||
- auto discard2 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:b"});
|
||||
+ // let's use a prefix form here
|
||||
+ auto discard2 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", "sysrepo", "discard-items"}, libyang::JSON{"/example-schema:b"});
|
||||
REQUIRE(!!discard2);
|
||||
discard1->insertSibling(*discard2);
|
||||
REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
@@ -2001,16 +2003,38 @@ TEST_CASE("Data Node manipulation")
|
||||
auto data = ctx.newPath2("/example-schema:myRpc/outputLeaf", "AHOJ", libyang::CreationOptions::Output).createdNode;
|
||||
REQUIRE(data);
|
||||
data->newPath("/example-schema:myRpc/another", "yay", libyang::CreationOptions::Output);
|
||||
+ std::string prettyName;
|
||||
|
||||
- DOCTEST_SUBCASE("JSON") {
|
||||
- out = ctx.newOpaqueJSON(data->schema().module().name(), "output", std::nullopt);
|
||||
+ DOCTEST_SUBCASE("JSON no prefix") {
|
||||
+ out = ctx.newOpaqueJSON({data->schema().module().name(), std::nullopt, "output"}, std::nullopt);
|
||||
+ prettyName = "{example-schema}, output";
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("XML") {
|
||||
- out = ctx.newOpaqueXML(data->schema().module().ns(), "output", std::nullopt);
|
||||
+ DOCTEST_SUBCASE("JSON with prefix") {
|
||||
+ out = ctx.newOpaqueJSON({data->schema().module().name(), data->schema().module().name(), "output"}, std::nullopt);
|
||||
+ prettyName = "example-schema:output";
|
||||
+
|
||||
+ // wrong prefix is detected
|
||||
+ REQUIRE_THROWS_WITH_AS(ctx.newOpaqueJSON({data->schema().module().name(), "xxx", "output"}, std::nullopt),
|
||||
+ R"(invalid opaque JSON node: prefix "xxx" doesn't match module name "example-schema")",
|
||||
+ libyang::Error);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XML no prefix") {
|
||||
+ out = ctx.newOpaqueXML({data->schema().module().ns(), std::nullopt, "output"}, std::nullopt);
|
||||
+ prettyName = "{http://example.com/coze}, output";
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XML with prefix") {
|
||||
+ out = ctx.newOpaqueXML({data->schema().module().ns(),
|
||||
+ data->schema().module().name() /* prefix is a module name, not the XML NS*/,
|
||||
+ "output"},
|
||||
+ std::nullopt);
|
||||
+ prettyName = "{http://example.com/coze}, example-schema:output";
|
||||
}
|
||||
|
||||
REQUIRE(out);
|
||||
+ REQUIRE(prettyName == out->asOpaque().name().pretty());
|
||||
data->unlinkWithSiblings();
|
||||
out->insertChild(*data);
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
From 8c59ecc5c687f8ee2ce62825835a378b422185f2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 13:41:01 +0100
|
||||
Subject: [PATCH 16/18] Add a helper for finding opaque nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is needed for sysrepo where we want to search through the
|
||||
sysrepo:discard-items top-level opaque nodes.
|
||||
|
||||
At first I wanted to simply wrap lyd_find_sibling_opaq_next(), but its
|
||||
semantics is quite different to what is needed in the code which uses
|
||||
sysrepo, so here's my attempt at a nice-ish API.
|
||||
|
||||
Change-Id: I2571961e42f6d7a121e27c881cacdcfec0e87762
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/DataNode.hpp | 2 +
|
||||
src/DataNode.cpp | 49 ++++++
|
||||
tests/data_node.cpp | 74 +++++++++
|
||||
tests/yang/sysrepo@2024-10-25.yang | 257 +++++++++++++++++++++++++++++
|
||||
4 files changed, 382 insertions(+)
|
||||
create mode 100644 tests/yang/sysrepo@2024-10-25.yang
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 310b5e3..50d6c0e 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -102,6 +102,7 @@ public:
|
||||
|
||||
bool isOpaque() const;
|
||||
DataNodeOpaque asOpaque() const;
|
||||
+ std::optional<DataNodeOpaque> firstOpaqueSibling() const;
|
||||
|
||||
// TODO: allow setting the `parent` argument
|
||||
DataNode duplicate(const std::optional<DuplicationOptions> opts = std::nullopt) const;
|
||||
@@ -241,6 +242,7 @@ struct LIBYANG_CPP_EXPORT OpaqueName {
|
||||
std::optional<std::string> prefix;
|
||||
std::string name;
|
||||
std::string pretty() const;
|
||||
+ bool matches(const std::string& prefixIsh, const std::string& name) const;
|
||||
};
|
||||
|
||||
/**
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 344f1b6..7e87917 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1135,6 +1135,20 @@ std::string OpaqueName::pretty() const
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short Fuzzy-match a real-world name against a combination of "something like a prefix" and "unqualified name"
|
||||
+ *
|
||||
+ * Because libyang doesn't propagate inherited prefixes, and because opaque nodes are magic, we seem to require
|
||||
+ * this "fuzzy matching". It won't properly report a match on opaque nodes with a prefix that's inherited when
|
||||
+ * using XML namespaces, though.
|
||||
+ * */
|
||||
+bool OpaqueName::matches(const std::string& prefixIsh, const std::string& name) const
|
||||
+{
|
||||
+ return name == this->name
|
||||
+ && (prefixIsh == moduleOrNamespace
|
||||
+ || (!!prefix && prefixIsh == *prefix));
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Wraps a raw non-null lyd_node pointer.
|
||||
* @param node The pointer to be wrapped. Must not be null.
|
||||
@@ -1305,4 +1319,39 @@ bool DataNode::siblingsEqual(const libyang::DataNode& other, const DataCompare f
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short Find the first opaque node among the siblings
|
||||
+ *
|
||||
+ * This function was inspired by `lyd_find_sibling_opaq_next()`.
|
||||
+ * */
|
||||
+std::optional<DataNodeOpaque> DataNode::firstOpaqueSibling() const
|
||||
+{
|
||||
+ struct lyd_node *candidate = m_node;
|
||||
+
|
||||
+ // Skip all non-opaque nodes; libyang guarantees to have them first, followed by a (possibly empty) set
|
||||
+ // of opaque nodes. This is not documented anywhere, but it was explicitly confirmed by the maintainer:
|
||||
+ //
|
||||
+ // JK: can I rely on all non-opaque nodes being listed first among the siblings, and then all opaque nodes
|
||||
+ // in one continuous sequence (but with an unspecified order among the opaque nodes themselves)?
|
||||
+ //
|
||||
+ // MV: yep
|
||||
+ while (candidate && candidate->schema) {
|
||||
+ candidate = candidate->next;
|
||||
+ }
|
||||
+
|
||||
+ // walk back through the opaque nodes; however, libyang lists are not your regular linked lists
|
||||
+ while (candidate
|
||||
+ && !candidate->prev->schema // don't go from the first opaque node through the non-opaque ones
|
||||
+ && candidate->prev->next // don't wrap from the first node to the last one in case all of them are opaque
|
||||
+ ) {
|
||||
+ candidate = candidate->prev;
|
||||
+ }
|
||||
+
|
||||
+ if (candidate) {
|
||||
+ return DataNode{candidate, m_refs}.asOpaque();
|
||||
+ }
|
||||
+
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
}
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index a1096a6..db5a28e 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1982,6 +1982,80 @@ TEST_CASE("Data Node manipulation")
|
||||
"sysrepo:discard-items": "/example-schema:b"
|
||||
}
|
||||
)");
|
||||
+
|
||||
+ // check that a list which only consists of opaque nodes doesn't break our iteration
|
||||
+ REQUIRE(!!discard1->firstOpaqueSibling());
|
||||
+ REQUIRE(*discard1->firstOpaqueSibling() == *discard1);
|
||||
+ REQUIRE(!!discard2->firstOpaqueSibling());
|
||||
+ REQUIRE(*discard2->firstOpaqueSibling() == *discard1);
|
||||
+
|
||||
+ auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666");
|
||||
+ leafInt16.insertSibling(*discard1);
|
||||
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
+ == R"({
|
||||
+ "example-schema:leafInt16": 666,
|
||||
+ "sysrepo:discard-items": "/example-schema:a",
|
||||
+ "sysrepo:discard-items": "/example-schema:b"
|
||||
+}
|
||||
+)");
|
||||
+ REQUIRE(leafInt16.firstSibling().path() == "/example-schema:leafInt16");
|
||||
+ REQUIRE(discard1->firstSibling().path() == "/example-schema:leafInt16");
|
||||
+ REQUIRE(discard2->firstSibling().path() == "/example-schema:leafInt16");
|
||||
+
|
||||
+ auto dummy = ctx.newPath("/example-schema:dummy", "blah");
|
||||
+ auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque);
|
||||
+ opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete");
|
||||
+ dummy.insertSibling(opaqueLeaf);
|
||||
+
|
||||
+ // FIXME reword this: this one might not be handled by sysrepo, but we want it for our fuzzy matcher testing anyway
|
||||
+ auto discard3 = ctx.newOpaqueXML(libyang::OpaqueName{"http://www.sysrepo.org/yang/sysrepo", "sysrepo", "discard-items"}, libyang::XML{"/example-schema:c"});
|
||||
+ REQUIRE(!!discard3);
|
||||
+ // notice that it's printed without a proper prefix at first...
|
||||
+ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink)
|
||||
+ == R"({"discard-items":"/example-schema:c"})");
|
||||
+ // ...but after loading the module, the proper module is added back
|
||||
+ ctx.parseModule(TESTS_DIR / "yang" / "sysrepo@2024-10-25.yang", libyang::SchemaFormat::YANG);
|
||||
+ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink)
|
||||
+ == R"({"sysrepo:discard-items":"/example-schema:c"})");
|
||||
+
|
||||
+ dummy.insertSibling(*discard3);
|
||||
+ leafInt16.insertSibling(dummy);
|
||||
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
+ == R"({
|
||||
+ "example-schema:dummy": "blah",
|
||||
+ "example-schema:leafInt16": 666,
|
||||
+ "sysrepo:discard-items": "/example-schema:a",
|
||||
+ "sysrepo:discard-items": "/example-schema:b",
|
||||
+ "example-schema:leafInt32": "",
|
||||
+ "@example-schema:leafInt32": {
|
||||
+ "ietf-netconf:operation": "delete"
|
||||
+ },
|
||||
+ "sysrepo:discard-items": "/example-schema:c"
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ REQUIRE(dummy.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(dummy.firstOpaqueSibling() != discard2);
|
||||
+ REQUIRE(leafInt16.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(opaqueLeaf.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard1->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard2->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard3->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard1->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(!discard1->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard2->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(!discard2->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard3->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard3->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(!opaqueLeaf.asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling() == discard2);
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling() == opaqueLeaf);
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling() == discard3);
|
||||
+ REQUIRE(!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling()->nextSibling());
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("RESTCONF RPC output")
|
||||
diff --git a/tests/yang/sysrepo@2024-10-25.yang b/tests/yang/sysrepo@2024-10-25.yang
|
||||
new file mode 100644
|
||||
index 0000000..f5bc32c
|
||||
--- /dev/null
|
||||
+++ b/tests/yang/sysrepo@2024-10-25.yang
|
||||
@@ -0,0 +1,257 @@
|
||||
+module sysrepo {
|
||||
+ namespace "http://www.sysrepo.org/yang/sysrepo";
|
||||
+ prefix sr;
|
||||
+
|
||||
+ yang-version 1.1;
|
||||
+
|
||||
+ import ietf-yang-types {
|
||||
+ prefix yang;
|
||||
+ }
|
||||
+
|
||||
+ import ietf-datastores {
|
||||
+ prefix ds;
|
||||
+ }
|
||||
+
|
||||
+ import ietf-yang-metadata {
|
||||
+ prefix md;
|
||||
+ revision-date 2016-08-05;
|
||||
+ }
|
||||
+
|
||||
+ organization
|
||||
+ "CESNET";
|
||||
+
|
||||
+ contact
|
||||
+ "Author: Michal Vasko
|
||||
+ <mvasko@cesnet.cz>";
|
||||
+
|
||||
+ description
|
||||
+ "Sysrepo YANG datastore internal attributes and information.";
|
||||
+
|
||||
+ revision "2024-10-25" {
|
||||
+ description
|
||||
+ "Removed redundant metadata used for push operational data.";
|
||||
+ }
|
||||
+
|
||||
+ revision "2019-07-10" {
|
||||
+ description
|
||||
+ "Initial revision.";
|
||||
+ }
|
||||
+
|
||||
+ typedef module-ref {
|
||||
+ description
|
||||
+ "Reference to a module.";
|
||||
+ type leafref {
|
||||
+ path "/sysrepo-modules/module/name";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ md:annotation operation {
|
||||
+ type enumeration {
|
||||
+ enum none {
|
||||
+ description
|
||||
+ "Node with this operation must exist but does not affect the datastore in any way.";
|
||||
+ reference
|
||||
+ "RFC 6241 section 7.2.: default-operation";
|
||||
+ }
|
||||
+ enum ether {
|
||||
+ description
|
||||
+ "Node with this operation does not have to exist and does not affect the datastore in any way.";
|
||||
+ }
|
||||
+ enum purge {
|
||||
+ description
|
||||
+ "Node with this operation represents an arbitrary generic node instance and all
|
||||
+ the instances will be deleted.";
|
||||
+ }
|
||||
+ }
|
||||
+ description
|
||||
+ "Additional proprietary <edit-config> operations used internally.";
|
||||
+ reference
|
||||
+ "RFC 6241 section 7.2.";
|
||||
+ }
|
||||
+
|
||||
+ identity notification {
|
||||
+ base ds:datastore;
|
||||
+ description
|
||||
+ "Special datastore for storing notifications for replay.";
|
||||
+ }
|
||||
+
|
||||
+ grouping module-info-grp {
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "Module name.";
|
||||
+ }
|
||||
+
|
||||
+ leaf revision {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "Module revision.";
|
||||
+ }
|
||||
+
|
||||
+ leaf-list enabled-feature {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "List of all the enabled features.";
|
||||
+ }
|
||||
+
|
||||
+ list plugin {
|
||||
+ key "datastore";
|
||||
+ description
|
||||
+ "Module datastore plugin handling specific datastore data.";
|
||||
+
|
||||
+ leaf datastore {
|
||||
+ type identityref {
|
||||
+ base ds:datastore;
|
||||
+ }
|
||||
+ description
|
||||
+ "Datastore of this plugin.";
|
||||
+ }
|
||||
+
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Specific plugin name as present in the plugin structures.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ grouping deps-grp {
|
||||
+ list lref {
|
||||
+ description
|
||||
+ "Dependency of a leafref node.";
|
||||
+
|
||||
+ leaf target-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Path identifying the leafref target node.";
|
||||
+ }
|
||||
+
|
||||
+ leaf target-module {
|
||||
+ type module-ref;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Foreign target module of the leafref.";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list inst-id {
|
||||
+ description
|
||||
+ "Dependency of an instance-identifier node.";
|
||||
+
|
||||
+ leaf source-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Path identifying the instance-identifier node.";
|
||||
+ }
|
||||
+
|
||||
+ leaf default-target-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Default instance-identifier value.";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list xpath {
|
||||
+ description
|
||||
+ "Dependency of an XPath expression - must or when statement.";
|
||||
+
|
||||
+ leaf expression {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "XPath expression of the dependency - must or when statement argument.";
|
||||
+ }
|
||||
+
|
||||
+ leaf-list target-module {
|
||||
+ type module-ref;
|
||||
+ description
|
||||
+ "Foreign modules with the data needed for evaluation of the XPath.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ container sysrepo-modules {
|
||||
+ config false;
|
||||
+ description
|
||||
+ "All installed Sysrepo modules.";
|
||||
+
|
||||
+ leaf content-id {
|
||||
+ type uint32;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Sysrepo module-set content-id to be used for its generated yang-library data.";
|
||||
+ }
|
||||
+
|
||||
+ list module {
|
||||
+ key "name";
|
||||
+ description
|
||||
+ "Sysrepo module.";
|
||||
+
|
||||
+ uses module-info-grp;
|
||||
+
|
||||
+ leaf replay-support {
|
||||
+ type yang:date-and-time;
|
||||
+ description
|
||||
+ "Present only if the module supports replay. Means the earliest stored notification if any present.
|
||||
+ Otherwise the time the replay support was switched on.";
|
||||
+ }
|
||||
+
|
||||
+ container deps {
|
||||
+ description
|
||||
+ "Module data dependencies on other modules.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+
|
||||
+ leaf-list inverse-deps {
|
||||
+ type module-ref;
|
||||
+ description
|
||||
+ "List of modules that depend on this module.";
|
||||
+ }
|
||||
+
|
||||
+ list rpc {
|
||||
+ key "path";
|
||||
+ description
|
||||
+ "Module RPC/actions.";
|
||||
+
|
||||
+ leaf path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Path identifying the operation.";
|
||||
+ }
|
||||
+
|
||||
+ container in {
|
||||
+ description
|
||||
+ "Operation input dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+
|
||||
+ container out {
|
||||
+ description
|
||||
+ "Operation output dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list notification {
|
||||
+ key "path";
|
||||
+ description
|
||||
+ "Module notifications.";
|
||||
+
|
||||
+ leaf path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Path identifying the notification.";
|
||||
+ }
|
||||
+
|
||||
+ container deps {
|
||||
+ description
|
||||
+ "Notification dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
From dab8c9aac96063ccb8541d5d1795ee89b75faeee Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 2 Apr 2025 11:50:24 -0700
|
||||
Subject: [PATCH 17/18] build: link to libpcre2 explicitly
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is apparently a problem on Mac OS X builds where the transitive
|
||||
dependency via libyang doesn't take effect:
|
||||
|
||||
Undefined symbols for architecture arm64:
|
||||
"_pcre2_code_free_8", referenced from:
|
||||
libyang::Regex::~Regex() in Regex.cpp.o
|
||||
libyang::Regex::~Regex() in Regex.cpp.o
|
||||
ld: symbol(s) not found for architecture arm64
|
||||
|
||||
Let's fix this by linking with libpcre2-8 directly.
|
||||
|
||||
We're using a different approach than libyang; they have their own CMake
|
||||
wrapper. I find it easier to work with pkg-config modules, so let's try
|
||||
it that way.
|
||||
|
||||
Change-Id: I1a64407d852161595b7dca654433190002cc3600
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c5fec45..4e009aa 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -34,6 +34,9 @@ pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET)
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
|
||||
+# libyang::Regex::~Regex() bypasses libyang and calls out to libpcre directly
|
||||
+pkg_check_modules(LIBPCRE2 REQUIRED libpcre2-8 IMPORTED_TARGET)
|
||||
+
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
include(CheckIncludeFileCXX)
|
||||
@@ -64,7 +67,7 @@ add_library(yang-cpp
|
||||
src/utils/newPath.cpp
|
||||
)
|
||||
|
||||
-target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG)
|
||||
+target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG PkgConfig::LIBPCRE2)
|
||||
# We do not offer any long-term API/ABI guarantees. To make stuff easier for downstream consumers,
|
||||
# we will be bumping both API and ABI versions very deliberately.
|
||||
# There will be no attempts at semver tracking, for example.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
From 7519fcd35216072a6b1eebe2a79e19789be345a9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:35:37 +0200
|
||||
Subject: [PATCH 18/18] CI: renamed project upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
README.md | 2 +-
|
||||
ci/build.sh | 2 +-
|
||||
3 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index b41c490..7b92766 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -5,13 +5,13 @@
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
required-projects: *projects
|
||||
diff --git a/README.md b/README.md
|
||||
index d76975b..8291e00 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -11,7 +11,7 @@ Object lifetimes are managed automatically via RAII.
|
||||
- [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
|
||||
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||
- CMake 3.19+
|
||||
-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for the docs, Doxygen
|
||||
|
||||
## Building
|
||||
diff --git a/ci/build.sh b/ci/build.sh
|
||||
index d06b646..0867f07 100755
|
||||
--- a/ci/build.sh
|
||||
+++ b/ci/build.sh
|
||||
@@ -73,7 +73,7 @@ build_n_test() {
|
||||
}
|
||||
|
||||
build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF
|
||||
-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
+build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
build_n_test ${ZUUL_PROJECT_NAME} -DBUILD_TESTING=ON
|
||||
|
||||
pushd ${BUILD_DIR}/${ZUUL_PROJECT_NAME}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 9622a68eba4aeaa60619b4c33d050ce91b27653d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 8 Oct 2024 12:22:49 +0200
|
||||
Subject: [PATCH 01/20] restconf: report the list key values when they differ
|
||||
Subject: [PATCH 01/44] restconf: report the list key values when they differ
|
||||
between URI and data
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
For creating (leaf-)list instances with PUT and PATCH methods one has
|
||||
to specify the list key values in the URI and in the data as well.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 070cffb48fda789910581930265d4624a7213e1b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 16 Oct 2024 11:02:45 +0200
|
||||
Subject: [PATCH 02/20] uri: rename url-encoding to percent-encoding
|
||||
Subject: [PATCH 02/44] uri: rename url-encoding to percent-encoding
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
RFC 3986 [1] calls it the percent-encoding, let's be consistent.
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 8a233202647f24538f2bbb8fff1e38d52e3599a4 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 16 Oct 2024 11:05:09 +0200
|
||||
Subject: [PATCH 03/20] uri: correct x3 rule class names
|
||||
Subject: [PATCH 03/44] uri: correct x3 rule class names
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Fixes: e06d5bf ("server: parser for data resources in URI paths")
|
||||
Change-Id: Ic953e568d841032113ede1c0e896574361c0ebe2
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 96cbf730010ee9539d05d0d72697dc960b3a938c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 7 Oct 2024 20:46:24 +0200
|
||||
Subject: [PATCH 04/20] restconf: parser should work on raw percent-encoded
|
||||
Subject: [PATCH 04/44] restconf: parser should work on raw percent-encoded
|
||||
paths
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The difference between nghttp2's uri_ref::path and raw_path is that the
|
||||
raw_path keeps the percent encoding, while the path converts the percent
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 8b13c1e4ccaa61a241674c27063439e257fa88de Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 2 Oct 2024 20:21:28 +0200
|
||||
Subject: [PATCH 05/20] restconf: list key values checking must respect
|
||||
Subject: [PATCH 05/44] restconf: list key values checking must respect
|
||||
libyang's canonicalization
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
While dealing with the issue that was fixed in the previous commit we
|
||||
realized that the issue is deeper. Not only that our parser rejected
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From fda47b6a6cfdaecc24e96c4d6138c6de3ef116e0 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 7 Oct 2024 21:21:22 +0200
|
||||
Subject: [PATCH 06/20] tests: test querying lists with union keys
|
||||
Subject: [PATCH 06/44] tests: test querying lists with union keys
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Test that we correctly work with keys that are unions of something
|
||||
that can have a module namespace and that must not have it.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 27ef5bc87fdeb70a77609da6ec18ee5c28656bb6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 29 Oct 2024 18:54:55 +0100
|
||||
Subject: [PATCH 07/20] error handling in sysrepo has changed
|
||||
Subject: [PATCH 07/44] error handling in sysrepo has changed
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/7969
|
||||
Change-Id: Id028806ed49114cba4c55e2874bcf3fc98308bdc
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 6819561d97e38569c319e36ca2e99768036b4032 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 21 Aug 2024 19:18:08 +0200
|
||||
Subject: [PATCH 08/20] restconf: support fields query parameter
|
||||
Subject: [PATCH 08/44] restconf: support fields query parameter
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
This patch adds support for fields query parameter [1].
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 48d9b6ba3f3f892b9060b76b505d2f9a3aeb9e02 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 25 Nov 2024 09:15:55 +0100
|
||||
Subject: [PATCH 09/20] cmake: adhere to CMP0167
|
||||
Subject: [PATCH 09/44] cmake: adhere to CMP0167
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
After locally updating to cmake 3.30 I have seen a warning that our way
|
||||
of finding boost library is deprecated [1].
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 64997543d48236cd2aae417568bc54d32c54df21 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 14:43:36 +0100
|
||||
Subject: [PATCH 10/20] Fix compatibility with pam_wrapper 1.1.6+
|
||||
Subject: [PATCH 10/44] Fix compatibility with pam_wrapper 1.1.6+
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
An year ago I reported a bug that the pam_wrapper project says that they
|
||||
use a variable called `PAM_WRAPPER_DISABLE_DEEPBIND`, but in fact they
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 849aa35274d5e07726cb849fe724962754b3fa29 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:20:48 +0100
|
||||
Subject: [PATCH 11/20] tests: add missing pragma once
|
||||
Subject: [PATCH 11/44] tests: add missing pragma once
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I269c20e5a914aa8c7bc9431147dd9785ea1aedda
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 60eac2b2d60f8f1918a0914272975dd53f527c01 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 19:36:10 +0100
|
||||
Subject: [PATCH 12/20] tests: extend clientRequest wrappers interface and use
|
||||
Subject: [PATCH 12/44] tests: extend clientRequest wrappers interface and use
|
||||
it
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
This is a preparation for refactoring in the next few commits.
|
||||
I will generalize the clientRequest interface to accept server
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 0beaee041fd4fcfbebcbb2912eb6b26ec71f50c7 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:01:54 +0100
|
||||
Subject: [PATCH 13/20] tests: move stuff from the header file into cpp file
|
||||
Subject: [PATCH 13/44] tests: move stuff from the header file into cpp file
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Allright, I have had enough. I am no longer waiting for a minute for a
|
||||
recompilation of one test.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 53aa2e23ee8acc1881487f3969c2c58fb29437be Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:08:53 +0100
|
||||
Subject: [PATCH 14/20] tests: rename datastoreUtils to event_watchers
|
||||
Subject: [PATCH 14/44] tests: rename datastoreUtils to event_watchers
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The next commit will move RESTCONF notification watchers there, I think
|
||||
the new name is more appropriate.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From f86df829979a26d5192a86c3b43c1393dcba7140 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 11:45:41 +0100
|
||||
Subject: [PATCH 15/20] tests: rename NotificationWatcher
|
||||
Subject: [PATCH 15/44] tests: rename NotificationWatcher
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
This watches notifications going through RESTCONF, so I think
|
||||
RestconfNotificatonWatcher is better name for this.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 30d704588fa7eb9d32f66296ec5f6784f082869e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:11:32 +0100
|
||||
Subject: [PATCH 16/20] tests: make NotificationWatcher reusable
|
||||
Subject: [PATCH 16/44] tests: make NotificationWatcher reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
We will need some parts of this in yang push tests
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 36943051d8563bee0c9cf89eec28acf5d3617272 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 12:02:24 +0100
|
||||
Subject: [PATCH 17/20] tests: helper function to construct server URI
|
||||
Subject: [PATCH 17/44] tests: helper function to construct server URI
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Ic7c03b32a29b07464291f99027530010a7902f77
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 7d15f59d20079ba94224e0bc308682aa5a004483 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:15:05 +0100
|
||||
Subject: [PATCH 18/20] tests: make SSEClient reusable
|
||||
Subject: [PATCH 18/44] tests: make SSEClient reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
We will need it in yang push tests
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 3e3e492f4801df56226a5aff5d82bfa86c9d3812 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 13:59:52 +0100
|
||||
Subject: [PATCH 19/20] tests: fix deadlock in tests
|
||||
Subject: [PATCH 19/44] tests: fix deadlock in tests
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
In the case the client never requests (because of misconfiguration or
|
||||
something else) the test would still be waiting for somebody to
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From ed0ff23f7ad341d663484f0b2a617cd3bc4923c8 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 19:27:49 +0100
|
||||
Subject: [PATCH 20/20] restconf: make as_restconf_notification reusable
|
||||
Subject: [PATCH 20/44] restconf: make as_restconf_notification reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The YANG-PUSH notifications are supposed to be wrapped in this too, so
|
||||
this needs to be accessible from multiple places.
|
||||
|
||||
+18
-7
@@ -1,17 +1,25 @@
|
||||
From f7307481fc4ca167592acf925136e5f8a51e2150 Mon Sep 17 00:00:00 2001
|
||||
From: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:26:15 +0100
|
||||
Subject: [PATCH] Update dependencies
|
||||
Subject: [PATCH 21/44] Update dependencies
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8134
|
||||
Change-Id: Ib170aa61600ffb9089460247f762ff5a947dd6c6
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
tests/restconf-writing.cpp | 8 ++++----
|
||||
2 files changed, 5 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 22bce32..5ae8062 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,7 +74,7 @@
|
||||
@@ -74,7 +74,7 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
@@ -24,7 +32,7 @@ diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index 0932984..d37ab03 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -772,7 +772,7 @@
|
||||
@@ -772,7 +772,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -33,7 +41,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -855,7 +855,7 @@
|
||||
@@ -855,7 +855,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -42,7 +50,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1560,7 +1560,7 @@
|
||||
@@ -1560,7 +1560,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -51,7 +59,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1626,7 +1626,7 @@
|
||||
@@ -1626,7 +1626,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -60,3 +68,6 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
From c967e688c53000519ceb6030e8282296e4846d3c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 18:29:29 +0100
|
||||
Subject: [PATCH 22/44] restconf: add RAII datastore switch
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Copied from sysrepo-ietf-alarms[1].
|
||||
|
||||
[1] https://github.com/CESNET/sysrepo-ietf-alarms/blob/4870dbee30a5ec56135d4f4cb5818f191c089c34/src/utils/sysrepo.cpp
|
||||
|
||||
Change-Id: I51738d06187e141ebd662e7b9421ca995380cafc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/restconf/utils/sysrepo.cpp | 25 +++++++++++++++++++++++++
|
||||
src/restconf/utils/sysrepo.h | 29 +++++++++++++++++++++++++++++
|
||||
3 files changed, 55 insertions(+)
|
||||
create mode 100644 src/restconf/utils/sysrepo.cpp
|
||||
create mode 100644 src/restconf/utils/sysrepo.h
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 5ae8062..7f3a9c0 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -114,6 +114,7 @@ add_library(rousette-restconf STATIC
|
||||
src/restconf/YangSchemaLocations.cpp
|
||||
src/restconf/uri.cpp
|
||||
src/restconf/utils/dataformat.cpp
|
||||
+ src/restconf/utils/sysrepo.cpp
|
||||
src/restconf/utils/yang.cpp
|
||||
)
|
||||
target_link_libraries(rousette-restconf PUBLIC rousette-http rousette-sysrepo rousette-auth Boost::system Threads::Threads PRIVATE date::date-tz)
|
||||
diff --git a/src/restconf/utils/sysrepo.cpp b/src/restconf/utils/sysrepo.cpp
|
||||
new file mode 100644
|
||||
index 0000000..622ab27
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/utils/sysrepo.cpp
|
||||
@@ -0,0 +1,25 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2020, 2022 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ */
|
||||
+
|
||||
+#include <sysrepo-cpp/Connection.hpp>
|
||||
+#include "sysrepo.h"
|
||||
+
|
||||
+namespace rousette::restconf {
|
||||
+
|
||||
+ScopedDatastoreSwitch::ScopedDatastoreSwitch(sysrepo::Session session, sysrepo::Datastore ds)
|
||||
+ : m_session(std::move(session))
|
||||
+ , m_oldDatastore(m_session.activeDatastore())
|
||||
+{
|
||||
+ m_session.switchDatastore(ds);
|
||||
+}
|
||||
+
|
||||
+ScopedDatastoreSwitch::~ScopedDatastoreSwitch()
|
||||
+{
|
||||
+ m_session.switchDatastore(m_oldDatastore);
|
||||
+}
|
||||
+
|
||||
+}
|
||||
diff --git a/src/restconf/utils/sysrepo.h b/src/restconf/utils/sysrepo.h
|
||||
new file mode 100644
|
||||
index 0000000..da6c359
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/utils/sysrepo.h
|
||||
@@ -0,0 +1,29 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2020, 2022 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ */
|
||||
+
|
||||
+#pragma once
|
||||
+
|
||||
+#include <libyang-cpp/Context.hpp>
|
||||
+#include <sysrepo-cpp/Session.hpp>
|
||||
+
|
||||
+namespace rousette::restconf {
|
||||
+
|
||||
+/** @brief Ensures that session switches to provided datastore and when the object gets destroyed the session switches back to the original datastore. */
|
||||
+class ScopedDatastoreSwitch {
|
||||
+ sysrepo::Session m_session;
|
||||
+ sysrepo::Datastore m_oldDatastore;
|
||||
+
|
||||
+public:
|
||||
+ ScopedDatastoreSwitch(sysrepo::Session session, sysrepo::Datastore ds);
|
||||
+ ~ScopedDatastoreSwitch();
|
||||
+ ScopedDatastoreSwitch(const ScopedDatastoreSwitch&) = delete;
|
||||
+ ScopedDatastoreSwitch(ScopedDatastoreSwitch&&) = delete;
|
||||
+ ScopedDatastoreSwitch& operator=(const ScopedDatastoreSwitch&) = delete;
|
||||
+ ScopedDatastoreSwitch& operator=(ScopedDatastoreSwitch&&) = delete;
|
||||
+};
|
||||
+
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
From 05274bd27b886e67accc86be7153ed76465b156e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 15 Jan 2025 17:56:26 +0100
|
||||
Subject: [PATCH 23/44] restconf: add missing pragma once
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I6d337c99351e24226fb322a40ba2e96655104860
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/NotificationStream.h | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/src/restconf/NotificationStream.h b/src/restconf/NotificationStream.h
|
||||
index 6840f11..3029a0d 100644
|
||||
--- a/src/restconf/NotificationStream.h
|
||||
+++ b/src/restconf/NotificationStream.h
|
||||
@@ -5,6 +5,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
+#pragma once
|
||||
#include <optional>
|
||||
#include <sysrepo-cpp/Session.hpp>
|
||||
#include <sysrepo-cpp/Subscription.hpp>
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
From bd7def1768e360cc491d56805f7c16784c2f4fbe Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 15 Jan 2025 18:27:25 +0100
|
||||
Subject: [PATCH 24/44] restconf: make fetching replay info reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We are querying sysrepo for replay support in our modules for NETCONF
|
||||
stream. This part of code will be reused for dynamic subscriptions to
|
||||
NETCONF stream as well, so let's make it reusable.
|
||||
|
||||
Change-Id: Id5bc656619acb8e56dfafde23b03107139d34e94
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/NotificationStream.cpp | 57 +++++++++++++++++------------
|
||||
1 file changed, 34 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/NotificationStream.cpp b/src/restconf/NotificationStream.cpp
|
||||
index eeddc04..e00c39d 100644
|
||||
--- a/src/restconf/NotificationStream.cpp
|
||||
+++ b/src/restconf/NotificationStream.cpp
|
||||
@@ -50,6 +50,36 @@ bool canBeSubscribed(const libyang::Module& mod)
|
||||
{
|
||||
return mod.implemented() && mod.name() != "sysrepo";
|
||||
}
|
||||
+
|
||||
+struct SysrepoReplayInfo {
|
||||
+ bool enabled;
|
||||
+ std::optional<sysrepo::NotificationTimeStamp> earliestNotification;
|
||||
+};
|
||||
+
|
||||
+SysrepoReplayInfo sysrepoReplayInfo(sysrepo::Session& session)
|
||||
+{
|
||||
+ decltype(sysrepo::ModuleReplaySupport::earliestNotification) globalEarliestNotification;
|
||||
+ bool replayEnabled = false;
|
||||
+
|
||||
+ for (const auto& mod : session.getContext().modules()) {
|
||||
+ if (!canBeSubscribed(mod)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ auto replay = session.getConnection().getModuleReplaySupport(mod.name());
|
||||
+ replayEnabled |= replay.enabled;
|
||||
+
|
||||
+ if (replay.earliestNotification) {
|
||||
+ if (!globalEarliestNotification) {
|
||||
+ globalEarliestNotification = replay.earliestNotification;
|
||||
+ } else {
|
||||
+ globalEarliestNotification = std::min(*replay.earliestNotification, *globalEarliestNotification);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return {replayEnabled, globalEarliestNotification};
|
||||
+}
|
||||
}
|
||||
|
||||
namespace rousette::restconf {
|
||||
@@ -114,28 +144,9 @@ void NotificationStream::activate()
|
||||
/** @brief Creates and fills ietf-restconf-monitoring:restconf-state/stream. To be called in oper callback. */
|
||||
void notificationStreamList(sysrepo::Session& session, std::optional<libyang::DataNode>& parent, const std::string& streamsPrefix)
|
||||
{
|
||||
+ const auto replayInfo = sysrepoReplayInfo(session);
|
||||
static const auto prefix = "/ietf-restconf-monitoring:restconf-state/streams/stream[name='NETCONF']"s;
|
||||
|
||||
- decltype(sysrepo::ModuleReplaySupport::earliestNotification) globalEarliestNotification;
|
||||
- bool replayEnabled = false;
|
||||
-
|
||||
- for (const auto& mod : session.getContext().modules()) {
|
||||
- if (!canBeSubscribed(mod)) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- auto replay = session.getConnection().getModuleReplaySupport(mod.name());
|
||||
- replayEnabled |= replay.enabled;
|
||||
-
|
||||
- if (replay.earliestNotification) {
|
||||
- if (!globalEarliestNotification) {
|
||||
- globalEarliestNotification = replay.earliestNotification;
|
||||
- } else {
|
||||
- globalEarliestNotification = std::min(*replay.earliestNotification, *globalEarliestNotification);
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
if (!parent) {
|
||||
parent = session.getContext().newPath(prefix + "/description", "Default NETCONF notification stream");
|
||||
} else {
|
||||
@@ -144,11 +155,11 @@ void notificationStreamList(sysrepo::Session& session, std::optional<libyang::Da
|
||||
parent->newPath(prefix + "/access[encoding='xml']/location", streamsPrefix + "NETCONF/XML");
|
||||
parent->newPath(prefix + "/access[encoding='json']/location", streamsPrefix + "NETCONF/JSON");
|
||||
|
||||
- if (replayEnabled) {
|
||||
+ if (replayInfo.enabled) {
|
||||
parent->newPath(prefix + "/replay-support", "true");
|
||||
|
||||
- if (globalEarliestNotification) {
|
||||
- parent->newPath(prefix + "/replay-log-creation-time", libyang::yangTimeFormat(*globalEarliestNotification, libyang::TimezoneInterpretation::Local));
|
||||
+ if (replayInfo.earliestNotification) {
|
||||
+ parent->newPath(prefix + "/replay-log-creation-time", libyang::yangTimeFormat(*replayInfo.earliestNotification, libyang::TimezoneInterpretation::Local));
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
From 08458725211e707dec7bfebf7c5188e766136677 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 7 Feb 2025 17:34:04 +0100
|
||||
Subject: [PATCH 25/44] sysrepo upstream API change: NULL output on RPCs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8289
|
||||
Change-Id: I8db21637246492b0f79d1adf7df1704df5173e39
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
src/restconf/Server.cpp | 4 ++--
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 7f3a9c0..cdd0eb4 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,7 +74,7 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
-pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=4)
|
||||
+pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 7c66ea4..daf23ed 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -462,13 +462,13 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
|
||||
auto rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout);
|
||||
|
||||
- if (rpcReply.immediateChildren().empty()) {
|
||||
+ if (!rpcReply || rpcReply->immediateChildren().empty()) {
|
||||
requestCtx->res.write_head(204, {CORS});
|
||||
requestCtx->res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
- auto responseNode = rpcReply.child();
|
||||
+ auto responseNode = rpcReply->child();
|
||||
responseNode->unlinkWithSiblings();
|
||||
|
||||
auto envelope = ctx.newOpaqueJSON(rpcNode->schema().module().name(), "output", std::nullopt);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From d69697a719781ef06ecb0545a58c2d055ca53c6d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 3 Mar 2025 13:32:51 +0100
|
||||
Subject: [PATCH 26/44] boost 1.87 support
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
In Asio 1.33 (boost 1.87) some of the deprecated functionalities were
|
||||
removed [1]. For rousette there are two changes that needed to be
|
||||
addressed:
|
||||
|
||||
* A removed include in clock.cpp. But actually, we do not need that
|
||||
header at all.
|
||||
* boost::asio::io_service is no longer around, it was actually renamed
|
||||
to boost::asio::io_context a while ago. Its post() method is no
|
||||
longer around as well, it was deprecated in favour of post()
|
||||
function [2].
|
||||
This function is there since boost 1.66, so let's set minimal boost
|
||||
version.
|
||||
|
||||
A bigger problem is that our dependency nghttp2-asio seems to be a dead
|
||||
project. At the time of writing this, there are no patches for more than
|
||||
3 years. However, we are not the only consumers of that project and
|
||||
somebody else already took care of that before us [3,4].
|
||||
|
||||
[1] https://www.boost.org/doc/libs/1_87_0/doc/html/boost_asio/history.html
|
||||
[2] https://live.boost.org/doc/libs/1_86_0/doc/html/boost_asio/reference/io_context/post.html
|
||||
[3] https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
[3] https://github.com/microsoft/vcpkg/commit/8eecddf7f18792d41c58a404604eed7f87b4b462
|
||||
|
||||
Change-Id: Ia59bb06e5f8ed222aa6e9f1c9b3947b05afeb9ec
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
src/clock.cpp | 1 -
|
||||
src/http/EventStream.cpp | 2 +-
|
||||
3 files changed, 3 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index cdd0eb4..ca41ef3 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -71,8 +71,8 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(date REQUIRED) # FIXME: Remove when we have STL with __cpp_lib_chrono >= 201907 (gcc 14)
|
||||
find_package(PkgConfig)
|
||||
-pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
-find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
+pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
+find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||
index 16a49b2..7ac6c3e 100644
|
||||
--- a/src/clock.cpp
|
||||
+++ b/src/clock.cpp
|
||||
@@ -5,7 +5,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
-#include <boost/asio/io_service.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <chrono>
|
||||
#include <nghttp2/asio_http2_server.h>
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 34038f4..6c765c5 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -117,6 +117,6 @@ void EventStream::enqueue(const std::string& what)
|
||||
spdlog::trace("{}: new event, ∑ queue size = {}", peer, len);
|
||||
queue.push_back(buf);
|
||||
state = HasEvents;
|
||||
- res.io_service().post([&res = this->res]() { res.resume(); });
|
||||
+ boost::asio::post(res.io_service(), [&res = this->res]() { res.resume(); });
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
From 97ceef119c900c37bbaa27860c3b43cfa6d69f95 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 13:01:07 +0100
|
||||
Subject: [PATCH 27/44] restconf: refactor uri parser for stream URIs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is a preparation for the upcoming patch that will add support for
|
||||
subscribed streams. I will use boost spirit x3 parser to parse such
|
||||
URIs so I need to refactor the existing parser, which is not really a
|
||||
parser, but we only match for the two known URIs.
|
||||
|
||||
Change-Id: Ib76973cd967fc3e9558ceb6be8f51239ce4c5ef3
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 15 +--------------
|
||||
src/restconf/uri.cpp | 36 +++++++++++++++++++++++++++---------
|
||||
src/restconf/uri.h | 11 ++++++-----
|
||||
src/restconf/uri_impl.h | 2 ++
|
||||
tests/uri-parser.cpp | 4 ++--
|
||||
5 files changed, 38 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index daf23ed..d356f7e 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -874,7 +874,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
auto sess = conn.sessionStart();
|
||||
- libyang::DataFormat dataFormat;
|
||||
std::optional<std::string> xpathFilter;
|
||||
std::optional<sysrepo::NotificationTimeStamp> startTime;
|
||||
std::optional<sysrepo::NotificationTimeStamp> stopTime;
|
||||
@@ -890,18 +889,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
|
||||
auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
|
||||
|
||||
- switch(streamRequest.type) {
|
||||
- case RestconfStreamRequest::Type::NetconfNotificationJSON:
|
||||
- dataFormat = libyang::DataFormat::JSON;
|
||||
- break;
|
||||
- case RestconfStreamRequest::Type::NetconfNotificationXML:
|
||||
- dataFormat = libyang::DataFormat::XML;
|
||||
- break;
|
||||
- default:
|
||||
- // GCC 14 complains about uninitialized variable, but asRestconfStreamRequest() would have thrown
|
||||
- __builtin_unreachable();
|
||||
- }
|
||||
-
|
||||
if (auto it = streamRequest.queryParams.find("filter"); it != streamRequest.queryParams.end()) {
|
||||
xpathFilter = std::get<std::string>(it->second);
|
||||
}
|
||||
@@ -916,7 +903,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
// The signal is constructed outside NotificationStream class because it is required to be passed to
|
||||
// NotificationStream's parent (EventStream) constructor where it already must be constructed
|
||||
// Yes, this is a hack.
|
||||
- auto client = std::make_shared<NotificationStream>(req, res, std::make_shared<rousette::http::EventStream::Signal>(), sess, dataFormat, xpathFilter, startTime, stopTime);
|
||||
+ auto client = std::make_shared<NotificationStream>(req, res, std::make_shared<rousette::http::EventStream::Signal>(), sess, streamRequest.type.encoding, xpathFilter, startTime, stopTime);
|
||||
client->activate();
|
||||
} catch (const auth::Error& e) {
|
||||
processAuthError(req, res, e, [&res]() {
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index da1e3a5..e95d70e 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -57,6 +57,13 @@ const auto resources = x3::rule<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 netconfStream = x3::rule<class netconfStream, RestconfStreamRequest::NetconfStream>{"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;
|
||||
+
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
@@ -197,6 +204,17 @@ std::optional<queryParams::QueryParams> parseQueryParams(const std::string& quer
|
||||
return ret;
|
||||
}
|
||||
|
||||
+std::optional<RestconfStreamRequest::NetconfStream> parseStreamUri(const std::string& uriPath)
|
||||
+{
|
||||
+ std::optional<RestconfStreamRequest::NetconfStream> ret;
|
||||
+
|
||||
+ if (!x3::parse(std::begin(uriPath), std::end(uriPath), streamUriGrammar >> x3::eoi, ret)) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
URIPrefix::URIPrefix()
|
||||
: resourceType(URIPrefix::Type::BasicRestconfData)
|
||||
{
|
||||
@@ -637,20 +655,20 @@ std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> asYangMod
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
-RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
|
||||
+RestconfStreamRequest::NetconfStream::NetconfStream() = default;
|
||||
+RestconfStreamRequest::NetconfStream::NetconfStream(const libyang::DataFormat& encoding)
|
||||
+ : encoding(encoding)
|
||||
{
|
||||
- static const auto netconfStreamRoot = "/streams/NETCONF/";
|
||||
- RestconfStreamRequest::Type type;
|
||||
+}
|
||||
|
||||
+RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
|
||||
+{
|
||||
if (httpMethod != "GET" && httpMethod != "HEAD") {
|
||||
throw ErrorResponse(405, "application", "operation-not-supported", "Method not allowed.");
|
||||
}
|
||||
|
||||
- if (uriPath == netconfStreamRoot + "XML"s) {
|
||||
- type = RestconfStreamRequest::Type::NetconfNotificationXML;
|
||||
- } else if (uriPath == netconfStreamRoot + "JSON"s) {
|
||||
- type = RestconfStreamRequest::Type::NetconfNotificationJSON;
|
||||
- } else {
|
||||
+ auto type = impl::parseStreamUri(uriPath);
|
||||
+ if (!type) {
|
||||
throw ErrorResponse(404, "application", "invalid-value", "Invalid stream");
|
||||
}
|
||||
|
||||
@@ -661,7 +679,7 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
|
||||
|
||||
validateQueryParametersForStream(*queryParameters);
|
||||
|
||||
- return {type, *queryParameters};
|
||||
+ return {*type, *queryParameters};
|
||||
}
|
||||
|
||||
/** @brief Returns a set of allowed HTTP methods for given URI. Usable for the 'allow' header */
|
||||
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||
index f6df724..6f1f69f 100644
|
||||
--- a/src/restconf/uri.h
|
||||
+++ b/src/restconf/uri.h
|
||||
@@ -197,11 +197,12 @@ struct RestconfRequest {
|
||||
};
|
||||
|
||||
struct RestconfStreamRequest {
|
||||
- enum class Type {
|
||||
- NetconfNotificationJSON,
|
||||
- NetconfNotificationXML,
|
||||
- };
|
||||
- Type type;
|
||||
+ struct NetconfStream {
|
||||
+ libyang::DataFormat encoding;
|
||||
+
|
||||
+ NetconfStream();
|
||||
+ NetconfStream(const libyang::DataFormat& encoding);
|
||||
+ } type;
|
||||
queryParams::QueryParams queryParams;
|
||||
};
|
||||
|
||||
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||
index 2bcdb3f..00b22f6 100644
|
||||
--- a/src/restconf/uri_impl.h
|
||||
+++ b/src/restconf/uri_impl.h
|
||||
@@ -66,6 +66,8 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::YangModule, name, revision);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
|
||||
|
||||
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::RestconfStreamRequest::NetconfStream, encoding);
|
||||
+
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SemiExpr, lhs, rhs);
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index fd5dd8b..53d1fbb 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -1072,13 +1072,13 @@ TEST_CASE("URI path parser")
|
||||
|
||||
{
|
||||
auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||
- REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationXML);
|
||||
+ REQUIRE(type.encoding == libyang::DataFormat::XML);
|
||||
REQUIRE(queryParams.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||
- REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationJSON);
|
||||
+ REQUIRE(type.encoding == libyang::DataFormat::JSON);
|
||||
REQUIRE(queryParams.empty());
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
From cc815dac4ea17ccb09a0481ad82745a194efe95f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 27 Jan 2025 19:17:07 +0100
|
||||
Subject: [PATCH 28/44] server: prepare checking if yang features are enabled
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We are not requiring any now, but next commits will need it. I am
|
||||
splitting this into a separate commit for clarity.
|
||||
|
||||
Change-Id: I0a8874b55b21d5ed6c7222f0c36a36c3c5ff52c5
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 22 ++++++++++++++--------
|
||||
1 file changed, 14 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index d356f7e..55e504a 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -817,14 +817,20 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||
{
|
||||
- for (const auto& [module, version] : {
|
||||
- std::pair<std::string, std::string>{"ietf-restconf", "2017-01-26"},
|
||||
- {"ietf-restconf-monitoring", "2017-01-26"},
|
||||
- {"ietf-netconf", ""},
|
||||
- {"ietf-yang-library", "2019-01-04"},
|
||||
- {"ietf-yang-patch", "2017-02-22"},
|
||||
- }) {
|
||||
- if (!conn.sessionStart().getContext().getModuleImplemented(module)) {
|
||||
+ for (const auto& [module, version, features] : {
|
||||
+ std::tuple<std::string, std::string, std::vector<std::string>>{"ietf-restconf", "2017-01-26", {}},
|
||||
+ {"ietf-restconf-monitoring", "2017-01-26", {}},
|
||||
+ {"ietf-netconf", "", {}},
|
||||
+ {"ietf-yang-library", "2019-01-04", {}},
|
||||
+ {"ietf-yang-patch", "2017-02-22", {}},
|
||||
+ }) {
|
||||
+ if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) {
|
||||
+ for (const auto& feature : features) {
|
||||
+ if (!mod->featureEnabled(feature)) {
|
||||
+ throw std::runtime_error("Module "s + module + "@" + version + " does not implement feature " + feature);
|
||||
+ }
|
||||
+ }
|
||||
+ } else {
|
||||
throw std::runtime_error("Module "s + module + "@" + version + " is not implemented in sysrepo");
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
From f45d7cab63431c94a4db859b07ea6f503214ebaa Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 17:10:17 +0100
|
||||
Subject: [PATCH 29/44] tests: refactor default handling
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...so that a followup commit which uses
|
||||
libyang::PrintFlags::KeepEmptyCont introduces less noise.
|
||||
|
||||
Change-Id: I587cf8a5fc0e87b37f4bb91a74d848f58b61e336
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-defaults.cpp | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp
|
||||
index dd8b4da..d5c6004 100644
|
||||
--- a/tests/restconf-defaults.cpp
|
||||
+++ b/tests/restconf-defaults.cpp
|
||||
@@ -32,7 +32,7 @@ TEST_CASE("default handling")
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||
|
||||
// default value of /example:a/b/c/enabled is implicitly set so it should not be printed
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
)"});
|
||||
@@ -72,7 +72,7 @@ TEST_CASE("default handling")
|
||||
)"});
|
||||
|
||||
// default value is explicitly set so it should be printed
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:a": {
|
||||
"b": {
|
||||
"c": {
|
||||
@@ -86,8 +86,8 @@ TEST_CASE("default handling")
|
||||
// RFC 6243, sec. 2.3.3: A valid 'delete' operation attribute for a data node that has been set by a client to its schema default value MUST succeed.
|
||||
REQUIRE(httpDelete(RESTCONF_DATA_ROOT "/example:a/b/c/enabled", {AUTH_ROOT}) == Response{204, noContentTypeHeaders, ""});
|
||||
|
||||
- // default value is implicitly set so it should be printed
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({
|
||||
+ // default value is only there implicitly, so it should *not* be printed
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
)"});
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
From 22442d322bda3d83457dd0f1da45d95a10e93081 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 17:32:59 +0100
|
||||
Subject: [PATCH 30/44] Always print empty containers in user-defined content
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
There's some subtle interaction between depth-limited printing, default
|
||||
values, and empty non-presence containers. In order to prepare for an
|
||||
upstream change in sysrepo which fixed depth-limited printing, let's
|
||||
stick to "always" printing the empty non-presence containers.
|
||||
|
||||
(It actually isn't "always" because the data such as error reports,
|
||||
patches, etc., are still printed using the default settings. But the
|
||||
data which is subject to user-defined constraints is now consistent.)
|
||||
|
||||
Change-Id: Ie2d6ad2e851d667e7e7582fcd0ac75dabce2c05e
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 4 +-
|
||||
src/restconf/utils/yang.cpp | 2 +-
|
||||
src/sr/AllEvents.cpp | 2 +-
|
||||
src/sr/OpticalEvents.cpp | 2 +-
|
||||
tests/restconf-defaults.cpp | 12 +++++-
|
||||
tests/restconf-nacm.cpp | 12 +++++-
|
||||
tests/restconf-reading.cpp | 79 ++++++++++++++++++++++++++++++++++---
|
||||
7 files changed, 98 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 55e504a..bae3992 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -478,7 +478,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
contentType(requestCtx->dataFormat.response),
|
||||
CORS,
|
||||
});
|
||||
- requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings));
|
||||
+ requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
}
|
||||
|
||||
void processPost(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
@@ -768,7 +768,7 @@ libyang::PrintFlags libyangPrintFlags(const libyang::DataNode& dataNode, const s
|
||||
} catch(const libyang::Error& e) {
|
||||
}
|
||||
|
||||
- libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings;
|
||||
+ libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont;
|
||||
|
||||
if (!withDefaults && node && (node->schema().nodeType() == libyang::NodeType::Leaf || node->schema().nodeType() == libyang::NodeType::Leaflist) && node->asTerm().isImplicitDefault()) {
|
||||
return ret | libyang::PrintFlags::WithDefaultsAll;
|
||||
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||
index 30661fc..8acb160 100644
|
||||
--- a/src/restconf/utils/yang.cpp
|
||||
+++ b/src/restconf/utils/yang.cpp
|
||||
@@ -111,7 +111,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
envelope->insertChild(*eventTime);
|
||||
envelope->insertChild(notification);
|
||||
|
||||
- auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings);
|
||||
+ auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
|
||||
// notification node comes from sysrepo and sysrepo will free this; if not unlinked then envelope destructor would try to free this as well
|
||||
notification.unlink();
|
||||
diff --git a/src/sr/AllEvents.cpp b/src/sr/AllEvents.cpp
|
||||
index 6e2a21c..2aca4c7 100644
|
||||
--- a/src/sr/AllEvents.cpp
|
||||
+++ b/src/sr/AllEvents.cpp
|
||||
@@ -109,7 +109,7 @@ sysrepo::ErrorCode AllEvents::onChange(sysrepo::Session session, const std::stri
|
||||
break;
|
||||
}
|
||||
};
|
||||
- auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
|
||||
+ auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
spdlog::info("JSON: {}", json);
|
||||
spdlog::warn("FULL JSON: {}",
|
||||
*session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||
diff --git a/src/sr/OpticalEvents.cpp b/src/sr/OpticalEvents.cpp
|
||||
index 8c5feff..25906a8 100644
|
||||
--- a/src/sr/OpticalEvents.cpp
|
||||
+++ b/src/sr/OpticalEvents.cpp
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace {
|
||||
std::string dumpDataFrom(sysrepo::Session session, const std::string& module)
|
||||
{
|
||||
- return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
|
||||
+ return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp
|
||||
index d5c6004..89083f1 100644
|
||||
--- a/tests/restconf-defaults.cpp
|
||||
+++ b/tests/restconf-defaults.cpp
|
||||
@@ -33,7 +33,11 @@ TEST_CASE("default handling")
|
||||
|
||||
// default value of /example:a/b/c/enabled is implicitly set so it should not be printed
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
|
||||
@@ -88,7 +92,11 @@ TEST_CASE("default handling")
|
||||
|
||||
// default value is only there implicitly, so it should *not* be printed
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
}
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 29d7723..51328f3 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -130,6 +130,9 @@ TEST_CASE("NACM")
|
||||
"clock": {
|
||||
"timezone-utc-offset": 2
|
||||
},
|
||||
+ "dns-resolver": {
|
||||
+ "options": {}
|
||||
+ },
|
||||
"radius": {
|
||||
"server": [
|
||||
{
|
||||
@@ -139,7 +142,8 @@ TEST_CASE("NACM")
|
||||
"shared-secret": "shared-secret"
|
||||
}
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,6 +211,9 @@ TEST_CASE("NACM")
|
||||
"clock": {
|
||||
"timezone-utc-offset": 2
|
||||
},
|
||||
+ "dns-resolver": {
|
||||
+ "options": {}
|
||||
+ },
|
||||
"radius": {
|
||||
"server": [
|
||||
{
|
||||
@@ -216,7 +223,8 @@ TEST_CASE("NACM")
|
||||
"shared-secret": "shared-secret"
|
||||
}
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index e709486..ffdb047 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -62,10 +62,22 @@ TEST_CASE("reading data")
|
||||
// this relies on a NACM rule for anonymous access that filters out "a lot of stuff"
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT, {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:tlc": {},
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ },
|
||||
+ "example:two-leafs": {},
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true",
|
||||
"nonconfig-node": "foo-config-false"
|
||||
},
|
||||
+ "example:ordered-lists": {},
|
||||
"ietf-restconf-monitoring:restconf-state": {
|
||||
"capabilities": {
|
||||
"capability": [
|
||||
@@ -107,10 +119,22 @@ TEST_CASE("reading data")
|
||||
|
||||
REQUIRE(get(RESTCONF_ROOT_DS("operational"), {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:tlc": {},
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ },
|
||||
+ "example:two-leafs": {},
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true",
|
||||
"nonconfig-node": "foo-config-false"
|
||||
},
|
||||
+ "example:ordered-lists": {},
|
||||
"ietf-restconf-monitoring:restconf-state": {
|
||||
"capabilities": {
|
||||
"capability": [
|
||||
@@ -152,9 +176,21 @@ TEST_CASE("reading data")
|
||||
|
||||
REQUIRE(get(RESTCONF_ROOT_DS("running"), {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:tlc": {},
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ },
|
||||
+ "example:two-leafs": {},
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true"
|
||||
- }
|
||||
+ },
|
||||
+ "example:ordered-lists": {}
|
||||
}
|
||||
)"});
|
||||
}
|
||||
@@ -213,7 +249,8 @@ TEST_CASE("reading data")
|
||||
{
|
||||
"name": "a"
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,7 +281,8 @@ TEST_CASE("reading data")
|
||||
"shared-secret": "shared-secret"
|
||||
}
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,6 +752,7 @@ TEST_CASE("reading data")
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true
|
||||
@@ -723,9 +762,18 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=explicit", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
+ // FIXME: libyang is not really consistent in printing of NP-containers when trimming away the defaults...
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
@@ -740,6 +788,7 @@ TEST_CASE("reading data")
|
||||
}
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true,
|
||||
@@ -766,6 +815,7 @@ TEST_CASE("reading data")
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true
|
||||
@@ -781,13 +831,24 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true
|
||||
}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
)"});
|
||||
|
||||
+ // FIXME: libyang is not consistent here:
|
||||
+ // - /example:a/b/c NP-container *is* printed,
|
||||
+ // - /example:a/example-augment:b/c NP-container is *not* printed
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
|
||||
@@ -801,6 +862,7 @@ TEST_CASE("reading data")
|
||||
}
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true,
|
||||
@@ -830,10 +892,15 @@ TEST_CASE("reading data")
|
||||
)"});
|
||||
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled?with-defaults=explicit", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
|
||||
+ // again, libyang is not 100% consistent in the `trim` mode
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From 818af7ec1dc890774603dbf11ed81cf64d89a628 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 17:56:50 +0100
|
||||
Subject: [PATCH 31/44] fix a typo in RFC number
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I81b68b3920c41129cb98728481580699e44e8c20
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-reading.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index ffdb047..38f5496 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -879,7 +879,7 @@ TEST_CASE("reading data")
|
||||
|
||||
SECTION("Implicit node with default value")
|
||||
{
|
||||
- // RFC 4080, 3.5.4: If target of the query is implicitly created node with default value, ignore basic mode
|
||||
+ // RFC 8040, 3.5.4: If target of the query is implicitly created node with default value, ignore basic mode
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:a": {
|
||||
"b": {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
From 9997d61dc43775444e4d78c37054a30b602c2603 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 18:11:39 +0100
|
||||
Subject: [PATCH 32/44] Fix bug in depth= processing
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...which got fixed upstream in sysrepo.
|
||||
|
||||
Change-Id: Ida37c48058488be32230165b8b4f45c6bd5a4d3a
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8429
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
tests/restconf-reading.cpp | 45 ++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 46 insertions(+)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index ca41ef3..272caad 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,6 +74,7 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index 38f5496..f87c4f5 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -243,6 +243,13 @@ TEST_CASE("reading data")
|
||||
)"});
|
||||
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius?depth=1", {AUTH_DWDM}) == Response{200, jsonHeaders, R"({
|
||||
+ "ietf-system:system": {
|
||||
+ "radius": {}
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius?depth=2", {AUTH_DWDM}) == Response{200, jsonHeaders, R"({
|
||||
"ietf-system:system": {
|
||||
"radius": {
|
||||
"server": [
|
||||
@@ -1026,6 +1033,25 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=nested/data&depth=1", {}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "list": [
|
||||
+ {
|
||||
+ "name": "blabla",
|
||||
+ "nested": [
|
||||
+ {
|
||||
+ "first": "1",
|
||||
+ "second": 2,
|
||||
+ "third": "3",
|
||||
+ "data": {}
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=nested/data&depth=2", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:tlc": {
|
||||
"list": [
|
||||
{
|
||||
@@ -1049,6 +1075,25 @@ TEST_CASE("reading data")
|
||||
|
||||
// whole datastore with fields filtering
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/list/nested/data&depth=1", {}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "list": [
|
||||
+ {
|
||||
+ "name": "blabla",
|
||||
+ "nested": [
|
||||
+ {
|
||||
+ "first": "1",
|
||||
+ "second": 2,
|
||||
+ "third": "3",
|
||||
+ "data": {}
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/list/nested/data&depth=2", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:tlc": {
|
||||
"list": [
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
From 009b2b01fb4a1ca854402debd66d899a264db8f2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 13:57:26 +0100
|
||||
Subject: [PATCH 33/44] Adapt to libyang-cpp API break in opaque nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I was wondering for a while whether libyang will do the right thing when
|
||||
printing the RPC's output nodes in XML, but apparently it does (and we
|
||||
have a test for that).
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8438
|
||||
Change-Id: I82425743fd2613fcbe2b696848e2feb1c47fc658
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
src/restconf/Server.cpp | 5 +++--
|
||||
src/restconf/utils/yang.cpp | 9 +++++----
|
||||
3 files changed, 9 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 272caad..bcabe84 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -76,7 +76,7 @@ find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
+pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=4)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||
pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index bae3992..28e2e4c 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -471,7 +471,8 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
auto responseNode = rpcReply->child();
|
||||
responseNode->unlinkWithSiblings();
|
||||
|
||||
- auto envelope = ctx.newOpaqueJSON(rpcNode->schema().module().name(), "output", std::nullopt);
|
||||
+ // libyang auto-resolves the XML namespace when the result is printed into XML
|
||||
+ auto envelope = ctx.newOpaqueJSON({rpcNode->schema().module().name(), rpcNode->schema().module().name(), "output"}, std::nullopt);
|
||||
envelope->insertChild(*responseNode);
|
||||
|
||||
requestCtx->res.write_head(200, {
|
||||
@@ -735,7 +736,7 @@ libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest
|
||||
}
|
||||
|
||||
for (const auto& rpc : mod.actionRpcs()) {
|
||||
- operations.insertChild(*ctx.newOpaqueJSON(rpc.module().name(), rpc.name(), libyang::JSON{"[null]"}));
|
||||
+ operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||
index 8acb160..9358840 100644
|
||||
--- a/src/restconf/utils/yang.cpp
|
||||
+++ b/src/restconf/utils/yang.cpp
|
||||
@@ -89,6 +89,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
{
|
||||
static const auto jsonNamespace = "ietf-restconf";
|
||||
static const auto xmlNamespace = "urn:ietf:params:xml:ns:netconf:notification:1.0";
|
||||
+ static const auto xmlPrefix = "ietf-netconf-notifications";
|
||||
|
||||
std::optional<libyang::DataNode> envelope;
|
||||
std::optional<libyang::DataNode> eventTime;
|
||||
@@ -96,11 +97,11 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
|
||||
/* The namespaces for XML and JSON envelopes are different. See https://datatracker.ietf.org/doc/html/rfc8040#section-6.4 */
|
||||
if (dataFormat == libyang::DataFormat::JSON) {
|
||||
- envelope = ctx.newOpaqueJSON(jsonNamespace, "notification", std::nullopt);
|
||||
- eventTime = ctx.newOpaqueJSON(jsonNamespace, "eventTime", libyang::JSON{timeStr});
|
||||
+ envelope = ctx.newOpaqueJSON({jsonNamespace, jsonNamespace, "notification"}, std::nullopt);
|
||||
+ eventTime = ctx.newOpaqueJSON({jsonNamespace, jsonNamespace, "eventTime"}, libyang::JSON{timeStr});
|
||||
} else {
|
||||
- envelope = ctx.newOpaqueXML(xmlNamespace, "notification", std::nullopt);
|
||||
- eventTime = ctx.newOpaqueXML(xmlNamespace, "eventTime", libyang::XML{timeStr});
|
||||
+ envelope = ctx.newOpaqueXML({xmlNamespace, xmlPrefix, "notification"}, std::nullopt);
|
||||
+ eventTime = ctx.newOpaqueXML({xmlNamespace, xmlPrefix, "eventTime"}, libyang::XML{timeStr});
|
||||
}
|
||||
|
||||
// the notification data node holds only the notification data tree but for nested notification we should print the whole YANG data tree
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
From 061d960d9435018aaba3b2d1b4d857ddb8836d1b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 15:38:44 +0100
|
||||
Subject: [PATCH 34/44] Fix XML serialization when listing RPCs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The RFC says [1] that each RPC entry that's listed in the reply should
|
||||
look like this when serialized into the XML format:
|
||||
|
||||
<system-restart xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
|
||||
|
||||
Our old code would, due to the way how libyang deals with opaque nodes,
|
||||
just try to dump the JSON-ish `[null]` bits in there, which is wrong.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/html/rfc8040#page-84
|
||||
|
||||
Change-Id: I6ea433dbd3fcf3ca2883d3fa00c9d215c0e1bb4f
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/br2-external/+/8443
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 16 +++++++++++++---
|
||||
tests/restconf-rpc.cpp | 33 ++++++++++++++++++++++++++++++++-
|
||||
2 files changed, 45 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 28e2e4c..05239e9 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -712,7 +712,7 @@ void processPutOrPlainPatch(std::shared_ptr<RequestContext> requestCtx, const st
|
||||
}
|
||||
|
||||
/** @brief Build data trees for endpoints returning ietf-restconf:restconf data */
|
||||
-libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest::Type& type)
|
||||
+libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest::Type& type, libyang::DataFormat dataFormat)
|
||||
{
|
||||
const auto yangLib = *ctx.getModuleLatest("ietf-yang-library");
|
||||
const auto yangApiExt = ctx.getModuleImplemented("ietf-restconf")->extensionInstance("yang-api");
|
||||
@@ -736,7 +736,16 @@ libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest
|
||||
}
|
||||
|
||||
for (const auto& rpc : mod.actionRpcs()) {
|
||||
- operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"}));
|
||||
+ switch (dataFormat) {
|
||||
+ case libyang::DataFormat::JSON:
|
||||
+ operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"}));
|
||||
+ break;
|
||||
+ case libyang::DataFormat::XML:
|
||||
+ operations.insertChild(*ctx.newOpaqueXML({rpc.module().ns(), rpc.module().name(), rpc.name()}, std::nullopt));
|
||||
+ break;
|
||||
+ default:
|
||||
+ throw std::logic_error{"Invalid data format for apiResource()"};
|
||||
+ }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -987,7 +996,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
case RestconfRequest::Type::YangLibraryVersion:
|
||||
case RestconfRequest::Type::ListRPC:
|
||||
res.write_head(200, {contentType(dataFormat.response), CORS});
|
||||
- res.end(*apiResource(sess.getContext(), restconfRequest.type).printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
+ res.end(*apiResource(sess.getContext(), restconfRequest.type, dataFormat.response)
|
||||
+ .printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
break;
|
||||
|
||||
case RestconfRequest::Type::GetData: {
|
||||
diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp
|
||||
index c4229a0..d49b121 100644
|
||||
--- a/tests/restconf-rpc.cpp
|
||||
+++ b/tests/restconf-rpc.cpp
|
||||
@@ -79,7 +79,9 @@ TEST_CASE("invoking actions and rpcs")
|
||||
|
||||
SECTION("List RPCs")
|
||||
{
|
||||
- REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ SECTION("JSON")
|
||||
+ {
|
||||
+ REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
"ietf-restconf:restconf": {
|
||||
"operations": {
|
||||
"ietf-factory-default:factory-reset": [null],
|
||||
@@ -103,6 +105,35 @@ TEST_CASE("invoking actions and rpcs")
|
||||
}
|
||||
}
|
||||
)"});
|
||||
+ }
|
||||
+
|
||||
+ SECTION("XML")
|
||||
+ {
|
||||
+ REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT, CONTENT_TYPE_XML})
|
||||
+ == Response{200, xmlHeaders,
|
||||
+ R"(<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
|
||||
+ <operations>
|
||||
+ <factory-reset xmlns="urn:ietf:params:xml:ns:yang:ietf-factory-default"/>
|
||||
+ <get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <set-current-datetime xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>
|
||||
+ <system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>
|
||||
+ <system-shutdown xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>
|
||||
+ <test-rpc xmlns="http://example.tld/example"/>
|
||||
+ <test-rpc-no-output xmlns="http://example.tld/example"/>
|
||||
+ <test-rpc-no-input xmlns="http://example.tld/example"/>
|
||||
+ <test-rpc-no-input-no-output xmlns="http://example.tld/example"/>
|
||||
+ </operations>
|
||||
+</restconf>
|
||||
+)"});
|
||||
+ }
|
||||
}
|
||||
|
||||
SECTION("RPC")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
From 6ad606cb9ad49ca7bfb8056b9695ab271a33e790 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 26 Mar 2025 18:56:06 +0100
|
||||
Subject: [PATCH 35/44] make sure that edit tree is the first sibling
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
After a bug in velia [1], where we passed to Session::editBatch a tree
|
||||
which was not the first sibling (and this caused that all the left
|
||||
siblings of the node were not considered in the edit), I am making sure
|
||||
that we do not lose any data in this project due to this too.
|
||||
|
||||
[1] https://github.com/CESNET/velia/commit/2b7187405740fe61e8b37e0efab8c3173d398a26
|
||||
|
||||
Fixes: 25f3e3b ("restconf: support for YANG patch")
|
||||
Change-Id: I7211168325fbfddd75dc1520c9dd83e31138d3bc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 05239e9..c454541 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -600,7 +600,7 @@ void processYangPatchEdit(const std::shared_ptr<RequestContext>& requestCtx, con
|
||||
if (!mergedEdits) {
|
||||
mergedEdits = *singleEdit;
|
||||
} else {
|
||||
- mergedEdits->insertSibling(*singleEdit);
|
||||
+ mergedEdits = mergedEdits->insertSibling(*singleEdit); // make sure we point to the first sibling, sysrepo::editBatch requires that
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
From 10c6ae778724e5c15a12b58bc95c814cb17b772e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 16:59:48 +0200
|
||||
Subject: [PATCH 37/44] don't create throwaway sysrepo sessions during startup
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Ic3fc71c08c589a8e87bbdc1aa1b1089c88c7cb1e
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 9fd3fa9..a840e6e 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -847,7 +847,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
{"ietf-subscribed-notifications", "2019-09-09", {}},
|
||||
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||
}) {
|
||||
- if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) {
|
||||
+ if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||
for (const auto& feature : features) {
|
||||
if (!mod->featureEnabled(feature)) {
|
||||
throw std::runtime_error("Module "s + module + "@" + version + " does not implement feature " + feature);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
From 4e287e1e2af95c1ec077689e4f353f657469e5e5 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 17:00:44 +0200
|
||||
Subject: [PATCH 38/44] requests: create sysrepo session later
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I was looking at all places where a sysrepo session is used, and tried
|
||||
to defer their creations until the last moment possible, just to
|
||||
(potentially) save some resources when it isn't needed. Some of these
|
||||
changes are cosmetic, but I was touching that code anyway.
|
||||
|
||||
This cannot be done for the generic `restconfRoot` handler because of
|
||||
its associated error handling which requires a libyang context. Well, it
|
||||
*could* be done, but I don't feel like doing some m_monitoringSession
|
||||
magic for little to no purpose.
|
||||
|
||||
Change-Id: Ia84c90abc1464e4a0f9682779d7471aefad9e2de
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 5 ++---
|
||||
1 file changed, 2 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index a840e6e..f3c515b 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -902,7 +902,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
});
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
- auto sess = conn.sessionStart();
|
||||
std::optional<std::string> xpathFilter;
|
||||
std::optional<sysrepo::NotificationTimeStamp> startTime;
|
||||
std::optional<sysrepo::NotificationTimeStamp> stopTime;
|
||||
@@ -914,6 +913,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
}
|
||||
|
||||
try {
|
||||
+ auto sess = conn.sessionStart();
|
||||
authorizeRequest(nacm, sess, req);
|
||||
|
||||
auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
|
||||
@@ -962,9 +962,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
return;
|
||||
}
|
||||
|
||||
- auto sess = conn.sessionStart(sysrepo::Datastore::Operational);
|
||||
-
|
||||
try {
|
||||
+ auto sess = conn.sessionStart(sysrepo::Datastore::Operational);
|
||||
authorizeRequest(nacm, sess, req);
|
||||
|
||||
if (auto mod = asYangModule(sess.getContext(), req.uri().path); mod && hasAccessToYangSchema(sess, *mod)) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From 422eae2ae879872bd618e3ba19418d20c197650d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:52:34 +0200
|
||||
Subject: [PATCH 39/44] docs: doctest has moved
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I45d605f080434a0726d729c7b186ab1bbeb8d8e1
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
README.md | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
index 3fbfd21..646976d 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -94,7 +94,7 @@ In practical terms, this means that the NACM access rules for the following XPat
|
||||
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||
- CMake 3.19+
|
||||
- optionally systemd - the shared library for logging to `sd-journal`
|
||||
-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for built-in tests, [trompeloeil](https://github.com/rollbear/trompeloeil) for mock objects in C++
|
||||
- optionally for built-in tests, [`pam_matrix` and `pam_wrapper`](https://cwrap.org/pam_wrapper.html) for PAM mocking
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
From b60c30e4eb482ce7b72ba95c8e6edc94f232ca37 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 18:12:18 +0200
|
||||
Subject: [PATCH 40/44] tests: capture wrongly-formatted SSE messages
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Sometimes during development I do things wrong(TM) and the Server Sent
|
||||
Events data stream is not prefixed.
|
||||
The log failure is unhelpful, so this patch should make the failures
|
||||
more clear in such cases.
|
||||
|
||||
Change-Id: I0d4643a6d7fe56ea5ce694c734a76b6dd471ff4c
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf_utils.cpp | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index c3ff1de..72c2c5a 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -224,6 +224,7 @@ std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
|
||||
res.emplace_back(std::move(event));
|
||||
event.clear();
|
||||
} else {
|
||||
+ CAPTURE(msg);
|
||||
FAIL("Unprefixed response");
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 2e866f4076df5cf68e7f8015e088baf45b45ddb3 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 14 Apr 2025 13:15:29 +0200
|
||||
Subject: [PATCH 41/44] tests: perform factory-reset between individual
|
||||
sections of NACM test
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I3b60439a8235e2a0efed58fcdb2ad8eb95f21714
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-nacm.cpp | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 51328f3..546d694 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -20,6 +20,8 @@ TEST_CASE("NACM")
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||
|
||||
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
+
|
||||
// something we can read
|
||||
srSess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
srSess.setItem("/ietf-system:system/contact", "contact");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
From 0ef0307d6664affc0547c2e65c8c33900bd7cfc9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 14 Apr 2025 15:12:00 +0200
|
||||
Subject: [PATCH 42/44] restconf: simplify condition in NACM anonymous access
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The condition required that all access-operations values are "read". But
|
||||
access-operations is a union, so it is enough to check that we have only
|
||||
one value in the union and that it is "read".
|
||||
|
||||
Also test what happens if anonymous rule access-operation is a union of
|
||||
read and something else.
|
||||
|
||||
Change-Id: I291243d9e2167601b24d49684d53c361407f66be
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/auth/Nacm.cpp | 4 +---
|
||||
tests/restconf-nacm.cpp | 18 ++++++++++++++++++
|
||||
2 files changed, 19 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/auth/Nacm.cpp b/src/auth/Nacm.cpp
|
||||
index 61a7dcc..d0dc4a2 100644
|
||||
--- a/src/auth/Nacm.cpp
|
||||
+++ b/src/auth/Nacm.cpp
|
||||
@@ -12,9 +12,7 @@ namespace {
|
||||
bool isRuleReadOnly(const libyang::DataNode& rule)
|
||||
{
|
||||
auto accessOperations = rule.findXPath("access-operations");
|
||||
- return !accessOperations.empty() && std::all_of(accessOperations.begin(), accessOperations.end(), [](const auto& e) {
|
||||
- return e.asTerm().valueStr() == "read";
|
||||
- });
|
||||
+ return accessOperations.size() == 1 && accessOperations.begin()->asTerm().valueStr() == "read";
|
||||
}
|
||||
|
||||
bool isRuleWildcardDeny(const libyang::DataNode& rule)
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 546d694..30f4210 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -95,6 +95,24 @@ TEST_CASE("NACM")
|
||||
srSess.applyChanges();
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("Anonymous has one rule with both read and update access operations")
|
||||
+ {
|
||||
+ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
+ srSess.applyChanges();
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/group[.='yangnobody']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/access-operations", "read update");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/path", "/ietf-system:system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/module-name", "*");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/action", "deny");
|
||||
+
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Anonymous rulelist OK, but not at first place")
|
||||
{
|
||||
srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
From f97dcabf8bf74c463d3291d31e9b36fabec0654f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 21:17:19 +0200
|
||||
Subject: [PATCH 43/44] restconf: allow specifying exec permissions for
|
||||
anonymous user access
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We want to be able to allow anonymous users to call the RPC
|
||||
ietf-subscribed-notifications:establish-subscription. Our current NACM
|
||||
anonymous setup did not allow specifying exec permissions.
|
||||
After this patch admins are allowed to setup a rule in the anonymous
|
||||
user rule-list that allows anonymous users to call the
|
||||
establish-subscriptions RPC. We allow only this one RPC, specifying any
|
||||
other will result in disabling the anonymous user access.
|
||||
|
||||
Change-Id: I5aa6fee2bbdabc0b7deac7fb9afecac6c411aca0
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
README.md | 2 +-
|
||||
src/auth/Nacm.cpp | 23 ++++++++++++++++++-----
|
||||
tests/restconf-nacm.cpp | 21 +++++++++++++++++++++
|
||||
tests/restconf_utils.cpp | 4 ++++
|
||||
4 files changed, 44 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
index 646976d..0796dce 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -66,7 +66,7 @@ When certain conditions are met, the anonymous access will be mapped to a NACM a
|
||||
There must be some specific access rights set up in `ietf-netconf-acm` model (these are currently very opinionated for our use-case):
|
||||
|
||||
1. The first entry of `rule-list` list must be configured for `ANONYMOUS_USER_GROUP`.
|
||||
-2. All the rules except the last one in this rule-list entry must enable only "read" access operation.
|
||||
+2. All the rules except the last one in this rule-list entry must enable either only "read" access operation or only "exec" operation on `ietf-subscribed-notifications:establish-subscription` RPC.
|
||||
3. The last rule in the first rule-set must be a wildcard rule that disables all operations over all modules.
|
||||
|
||||
The anonymous user access is disabled whenever these rules are not met.
|
||||
diff --git a/src/auth/Nacm.cpp b/src/auth/Nacm.cpp
|
||||
index d0dc4a2..284fa85 100644
|
||||
--- a/src/auth/Nacm.cpp
|
||||
+++ b/src/auth/Nacm.cpp
|
||||
@@ -9,10 +9,23 @@
|
||||
#include "NacmIdentities.h"
|
||||
|
||||
namespace {
|
||||
-bool isRuleReadOnly(const libyang::DataNode& rule)
|
||||
+bool isWhitelistedRPC(const std::optional<libyang::DataNode>& moduleNameNode, const std::optional<libyang::DataNode>& rpcNameNode)
|
||||
+{
|
||||
+ if (!moduleNameNode || !rpcNameNode) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ return moduleNameNode->asTerm().valueStr() == "ietf-subscribed-notifications" && rpcNameNode->asTerm().valueStr() == "establish-subscription";
|
||||
+}
|
||||
+
|
||||
+bool isRuleForAnonymousAccess(const libyang::DataNode& rule)
|
||||
{
|
||||
auto accessOperations = rule.findXPath("access-operations");
|
||||
- return accessOperations.size() == 1 && accessOperations.begin()->asTerm().valueStr() == "read";
|
||||
+ auto rpcName = rule.findPath("rpc-name");
|
||||
+ return accessOperations.size() == 1 && // Combining access operations is not allowed in anonymous user rules
|
||||
+ (accessOperations.begin()->asTerm().valueStr() == "read" || // Either read...
|
||||
+ (accessOperations.begin()->asTerm().valueStr() == "exec" && isWhitelistedRPC(rule.findPath("module-name"), rule.findPath("rpc-name"))) // ...or exec on whitelisted RPC.
|
||||
+ );
|
||||
}
|
||||
|
||||
bool isRuleWildcardDeny(const libyang::DataNode& rule)
|
||||
@@ -25,7 +38,7 @@ bool isRuleWildcardDeny(const libyang::DataNode& rule)
|
||||
*
|
||||
* The first rule-list element contains rules for anonymous user access, i.e.:
|
||||
* - The group is set to @p anonGroup (this one should contain the anonymous user)
|
||||
- * - In rules (except the last one) the access-operation allowed is "read"
|
||||
+ * - In rules (except the last one) the access-operation allowed is either "read" or "exec" on a whitelisted RPC.
|
||||
* - The last rule has module-name="*" and action "deny".
|
||||
*
|
||||
* @return boolean indicating whether the rules are configured properly for anonymous user access
|
||||
@@ -60,8 +73,8 @@ bool validAnonymousNacmRules(sysrepo::Session session, const std::string& anonGr
|
||||
return false;
|
||||
}
|
||||
|
||||
- if (!std::all_of(rules.begin(), rules.end() - 1, isRuleReadOnly)) {
|
||||
- spdlog::debug("NACM config validation: First n-1 rules in the anonymous rule-list must be configured for read-access only");
|
||||
+ if (!std::all_of(rules.begin(), rules.end() - 1, isRuleForAnonymousAccess)) {
|
||||
+ spdlog::debug("NACM config validation: First n-1 rules in the anonymous rule-list must be configured either for read-access only or exec on listed RPC paths");
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 30f4210..7799fe8 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -113,6 +113,27 @@ TEST_CASE("NACM")
|
||||
srSess.applyChanges();
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("This RPC is not allowed")
|
||||
+ {
|
||||
+ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
+ srSess.applyChanges();
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/group[.='yangnobody']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/access-operations", "read");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/path", "/ietf-system:system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/module-name", "ietf-subscribed-notifications");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/action", "permit");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/access-operations", "read update");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/rpc-name", "/ietf-subscribed-notifications:modify-subscription");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='3']/module-name", "*");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='3']/action", "deny");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Anonymous rulelist OK, but not at first place")
|
||||
{
|
||||
srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index 72c2c5a..a137463 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -152,6 +152,10 @@ void setupRealNacm(sysrepo::Session session)
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/action", "permit");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/access-operations", "read");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/path", "/example-delete:immutable");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/module-name", "ietf-subscribed-notifications");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/action", "permit");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/access-operations", "exec");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/rpc-name", "establish-subscription");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/module-name", "*");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/action", "deny");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 438a18f5884ace3063f00a2dfc53dd2b4034f53e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 16 Apr 2025 10:56:50 +0200
|
||||
Subject: [PATCH 44/44] restconf: add access log to stream root
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5e1db3f40bc6a7f294e6a5fef9402e0855055258
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index f3c515b..5b1b2f9 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -902,6 +902,9 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
});
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
+ const auto& peer = http::peer_from_request(req);
|
||||
+ spdlog::info("{}: {} {}", peer, req.method(), req.uri().raw_path);
|
||||
+
|
||||
std::optional<std::string> xpathFilter;
|
||||
std::optional<sysrepo::NotificationTimeStamp> startTime;
|
||||
std::optional<sysrepo::NotificationTimeStamp> stopTime;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 23fa270a747c35d65aeeba691599ed6b21b501f0 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 29 Oct 2024 15:15:52 +0100
|
||||
Subject: [PATCH 1/6] error handling changes in upstream sysrepo
|
||||
Subject: [PATCH 01/20] error handling changes in upstream sysrepo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Upstream commit 396e331e4634a417420a71ad723567b42d75c443 removed these
|
||||
extra error entries.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From a73ffe1a5bbab51a67cf56dd2864c71a29c6685b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 21 Nov 2024 11:18:25 +0100
|
||||
Subject: [PATCH 2/6] Fix error message when sr_session_start fails
|
||||
Subject: [PATCH 02/20] Fix error message when sr_session_start fails
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Thanks to Jason Patterson for sending a bugreport; its investigation led
|
||||
to discovering this bug.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 552a366990fe1fe2cdd8d155fa5cecb3ff3fbc13 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 4 Dec 2024 15:51:41 +0100
|
||||
Subject: [PATCH 3/6] We don't support sysrepo v3 yet
|
||||
Subject: [PATCH 03/20] We don't support sysrepo v3 yet
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
That version introduced some backward-incompatible changes to the way
|
||||
how operational data are treated, especially in presence of "discards"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From f2db30721d909d127645721f4149de0cd36d2b1c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 17 Dec 2024 11:41:10 +0100
|
||||
Subject: [PATCH 4/6] refactor: don't use `module` as a variable name
|
||||
Subject: [PATCH 04/20] refactor: don't use `module` as a variable name
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
...to unify stuff with all other occurrences, and to not use that magic
|
||||
identifier.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From a923f490350dc61b9f3e6000c06c1d7950a78a61 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 16:12:14 +0100
|
||||
Subject: [PATCH 5/6] DRY: a dummy leaf XPath
|
||||
Subject: [PATCH 05/20] DRY: a dummy leaf XPath
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I47fca61d27380af07b2327f3ee5b984a8a8afb66
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 80e9659b41ea1de2e20d62f94319fe5af26adcee Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:57:36 +0100
|
||||
Subject: [PATCH 6/6] build: a single place to define package version
|
||||
Subject: [PATCH 06/20] build: a single place to define package version
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Don't ask me how I found out that I need this :).
|
||||
|
||||
|
||||
+36
-21
@@ -1,7 +1,11 @@
|
||||
From b34fb200812f9e6bc72e80bf46bd006ebc06a9da Mon Sep 17 00:00:00 2001
|
||||
From: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
From 32bdd8bcad04f890fe724fb6cc2fc74f9b982576 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:16:55 +0100
|
||||
Subject: [PATCH] API/ABI break: Change how pushed ops data work
|
||||
Subject: [PATCH 07/20] API/ABI break: Change how pushed ops data work
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Upstream has changed the way how pushed operational data are stored and
|
||||
managed. From now on, each session has a separate edit, these edits are
|
||||
@@ -33,10 +37,18 @@ avoiding the possible confusion at the C++ layer:
|
||||
by `Session::applyChanges()`.
|
||||
|
||||
Change-Id: Iba05a88411fd4c47c03d8c2b48cb7aadfd5dcd2a
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 +-
|
||||
CMakeLists.txt | 4 +-
|
||||
README.md | 1 -
|
||||
include/sysrepo-cpp/Session.hpp | 6 ++-
|
||||
src/Session.cpp | 96 ++++++++++++++++++++++++---------
|
||||
tests/session.cpp | 59 ++++++++++++++++----
|
||||
6 files changed, 128 insertions(+), 42 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index c3aea22..dc87c6f 100644
|
||||
index c3aea22..70c5e49 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -6,7 +6,7 @@
|
||||
@@ -44,7 +56,7 @@ index c3aea22..dc87c6f 100644
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2024-10-before-oper-changes
|
||||
+ override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-30--00
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
@@ -53,15 +65,15 @@ index c3aea22..dc87c6f 100644
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2024-10-before-oper-changes
|
||||
+ override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-30--00
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 68ade53..cc4dba9 100644
|
||||
index 68ade53..9e09d3d 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -20,7 +20,7 @@
|
||||
@@ -20,7 +20,7 @@ add_custom_target(sysrepo-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
@@ -70,12 +82,12 @@ index 68ade53..cc4dba9 100644
|
||||
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,7 +29,7 @@
|
||||
@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=2.12.0 sysrepo<3 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.0 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.7 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
@@ -83,7 +95,7 @@ diff --git a/README.md b/README.md
|
||||
index e14b772..27dc748 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -9,7 +9,6 @@
|
||||
@@ -9,7 +9,6 @@ It uses RAII for automatic memory management.
|
||||
|
||||
## Dependencies
|
||||
- [sysrepo](https://github.com/sysrepo/sysrepo) - the `devel` branch (even for the `master` branch of *sysrepo-cpp*)
|
||||
@@ -95,7 +107,7 @@ diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 477e604..238f480 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -85,13 +85,15 @@
|
||||
@@ -85,13 +85,15 @@ public:
|
||||
void setItem(const std::string& path, const std::optional<std::string>& value, const EditOptions opts = sysrepo::EditOptions::Default);
|
||||
void editBatch(libyang::DataNode edit, const DefaultOperation op);
|
||||
void deleteItem(const std::string& path, const EditOptions opts = sysrepo::EditOptions::Default);
|
||||
@@ -117,7 +129,7 @@ diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 94a0fad..634e450 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -23,6 +23,26 @@
|
||||
@@ -23,6 +23,26 @@ extern "C" {
|
||||
|
||||
using namespace std::string_literals;
|
||||
namespace sysrepo {
|
||||
@@ -144,7 +156,7 @@ index 94a0fad..634e450 100644
|
||||
/**
|
||||
* Wraps a pointer to sr_session_ctx_s and manages the lifetime of it. Also extends the lifetime of the connection
|
||||
* specified by the `conn` argument.
|
||||
@@ -119,20 +139,61 @@
|
||||
@@ -119,20 +139,61 @@ void Session::deleteItem(const std::string& path, const EditOptions opts)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +221,7 @@ index 94a0fad..634e450 100644
|
||||
/**
|
||||
* Moves item (a list or a leaf-list) specified by `path`.
|
||||
* @param path Node to move.
|
||||
@@ -155,25 +216,6 @@
|
||||
@@ -155,25 +216,6 @@ void Session::moveItem(const std::string& path, const MovePosition move, const s
|
||||
throwIfError(res, "Session::moveItem: Can't move '"s + path + "'", m_sess.get());
|
||||
}
|
||||
|
||||
@@ -235,16 +247,16 @@ index 94a0fad..634e450 100644
|
||||
/**
|
||||
* @brief Returns a tree which contains all nodes that match the provided XPath.
|
||||
*
|
||||
@@ -263,13 +305,15 @@
|
||||
@@ -263,13 +305,15 @@ void Session::applyChanges(std::chrono::milliseconds timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
- * Discards changes made in this Session.
|
||||
+ * Discards changes made earlier in this Session, optionally only below a given XPath
|
||||
+ *
|
||||
+ * The changes are applied only after calling Session::applyChanges.
|
||||
*
|
||||
- * Wraps `sr_discard_changes`.
|
||||
+ * The changes are applied only after calling Session::applyChanges.
|
||||
+ *
|
||||
+ * Wraps `sr_discard_changes_xpath`.
|
||||
*/
|
||||
-void Session::discardChanges()
|
||||
@@ -259,7 +271,7 @@ diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index f762f10..2637f67 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -211,7 +211,7 @@
|
||||
@@ -211,7 +211,7 @@ TEST_CASE("session")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +280,7 @@ index f762f10..2637f67 100644
|
||||
{
|
||||
// Set some arbitrary leaf.
|
||||
sess.setItem(leaf, "123");
|
||||
@@ -230,15 +230,51 @@
|
||||
@@ -230,15 +230,51 @@ TEST_CASE("session")
|
||||
sess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123");
|
||||
|
||||
@@ -328,7 +340,7 @@ index f762f10..2637f67 100644
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("edit batch")
|
||||
@@ -321,6 +357,11 @@
|
||||
@@ -321,6 +357,11 @@ TEST_CASE("session")
|
||||
sess.discardChanges();
|
||||
}
|
||||
|
||||
@@ -340,3 +352,6 @@ index f762f10..2637f67 100644
|
||||
REQUIRE(sess.getPendingChanges() == std::nullopt);
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
From 27dc076cbdff3c9edf00abb05bdce59d1944988b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 31 Jan 2025 17:54:06 +0100
|
||||
Subject: [PATCH 08/20] upstream break: sr_rpc_send_tree's `output` might be
|
||||
NULL now
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
As of upstream commit 927fd5221bd28ff50cc1ee7c2cb5c5f30b34c2c9, sysrepo
|
||||
can now return a nullptr when the resulting tree is empty. This appears
|
||||
to have been done in response to our bugreport where we pointed out that
|
||||
`netopeer2-server` started crashing on empty RPCs. However, it's unclear
|
||||
why this necessitated a change in sysrepo, given that this feature has
|
||||
worked well for years.
|
||||
|
||||
Anyway, let's propagate this through the C++ layer; what else can I do,
|
||||
after all.
|
||||
|
||||
Bug: https://github.com/CESNET/netopeer2/issues/1695
|
||||
Change-Id: I646d19f7448d98f0d21e9eec006f28fbb5e479fc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
CMakeLists.txt | 4 ++--
|
||||
include/sysrepo-cpp/Session.hpp | 2 +-
|
||||
src/Session.cpp | 6 ++++--
|
||||
tests/subscriptions.cpp | 7 ++++---
|
||||
5 files changed, 13 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index 70c5e49..dc87c6f 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -6,7 +6,7 @@
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2025-01-30--00
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
@@ -17,7 +17,7 @@
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2025-01-30--00
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 9e09d3d..85ff84c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -20,7 +20,7 @@ add_custom_target(sysrepo-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-set(SYSREPO_CPP_PKG_VERSION "4")
|
||||
+set(SYSREPO_CPP_PKG_VERSION "5")
|
||||
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.7 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.8 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 238f480..6e31fa2 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
void discardOperationalChanges(const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
void dropForeignOperationalContent(const std::optional<std::string>& xpath);
|
||||
void copyConfig(const Datastore source, const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
- libyang::DataNode sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
+ std::optional<libyang::DataNode> sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
void sendNotification(libyang::DataNode notification, const Wait wait, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
void replaceConfig(std::optional<libyang::DataNode> config, const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 634e450..5df04bc 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -343,13 +343,15 @@ void Session::copyConfig(const Datastore source, const std::optional<std::string
|
||||
* @param input Libyang tree representing the RPC/action.
|
||||
* @param timeout Optional timeout.
|
||||
*/
|
||||
-libyang::DataNode Session::sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout)
|
||||
+std::optional<libyang::DataNode> Session::sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout)
|
||||
{
|
||||
sr_data_t* output;
|
||||
auto res = sr_rpc_send_tree(m_sess.get(), libyang::getRawNode(input), timeout.count(), &output);
|
||||
throwIfError(res, "Couldn't send RPC", m_sess.get());
|
||||
|
||||
- assert(output); // TODO: sysrepo always gives the RPC node? (even when it has not output or output nodes?)
|
||||
+ if (!output) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
return wrapSrData(m_sess, output);
|
||||
}
|
||||
|
||||
diff --git a/tests/subscriptions.cpp b/tests/subscriptions.cpp
|
||||
index 36fd61c..6fc9cbb 100644
|
||||
--- a/tests/subscriptions.cpp
|
||||
+++ b/tests/subscriptions.cpp
|
||||
@@ -420,9 +420,10 @@ TEST_CASE("subscriptions")
|
||||
if (ret == sysrepo::ErrorCode::Ok) {
|
||||
auto output = sess.sendRPC(sess.getContext().newPath(rpcPath));
|
||||
if (setFunction) {
|
||||
- REQUIRE(output.findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output));
|
||||
- } else {
|
||||
- REQUIRE(!output.findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output).has_value());
|
||||
+ REQUIRE(!!output);
|
||||
+ REQUIRE(output->findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output));
|
||||
+ } else if (output) {
|
||||
+ REQUIRE(!output->findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output).has_value());
|
||||
}
|
||||
} else {
|
||||
REQUIRE_THROWS(sess.sendRPC(sess.getContext().newPath(rpcPath)));
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
From dc8facb5befbd17f2848098bbc9267c9d9f58e2d 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 12:08:47 +0100
|
||||
Subject: [PATCH 09/20] tests: adapt to upstream breaking change in sr_get_data
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
It seems that since upstream commit ba8897ce3 [1] the max_depth
|
||||
parameter started behaving differently. It now behaves as the
|
||||
documentation [2] states, i.e:
|
||||
|
||||
Maximum depth of the selected subtrees. 0 is unlimited, 1 will not
|
||||
return any descendant nodes. If a list should be returned, its keys
|
||||
are always returned as well.
|
||||
|
||||
The previous behaviour was probably wrong. When reported via slack,
|
||||
another fix was issued [3], so let's depend on that as well.
|
||||
|
||||
[1] https://github.com/sysrepo/sysrepo/commit/ba8897ce3da0bc8203d5b964851fd60fb164760d
|
||||
[2] https://netopeer.liberouter.org/doc/sysrepo/devel/html/group__get__data__api.html
|
||||
[3] https://github.com/sysrepo/sysrepo/commit/309b34d28cc09771cf488d04478161f684f4b8a7
|
||||
|
||||
Change-Id: I337dc1f1f77b9501dda29931846116050123bcad
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
tests/session.cpp | 11 +++++++++--
|
||||
2 files changed, 10 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 85ff84c..fb132a2 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.8 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 2637f67..5be0d79 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -127,6 +127,13 @@ TEST_CASE("session")
|
||||
data = sess.getData("/test_module:popelnice", 1);
|
||||
REQUIRE(data);
|
||||
REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ "test_module:popelnice": {}
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ data = sess.getData("/test_module:popelnice", 2);
|
||||
+ REQUIRE(data);
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
"test_module:popelnice": {
|
||||
"content": {}
|
||||
}
|
||||
@@ -134,7 +141,7 @@ TEST_CASE("session")
|
||||
)");
|
||||
|
||||
// If a list should be returned, its keys are always returned as well.
|
||||
- data = sess.getData("/test_module:popelnice", 2);
|
||||
+ data = sess.getData("/test_module:popelnice", 3);
|
||||
REQUIRE(data);
|
||||
REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
"test_module:popelnice": {
|
||||
@@ -152,7 +159,7 @@ TEST_CASE("session")
|
||||
}
|
||||
)");
|
||||
|
||||
- data = sess.getData("/test_module:popelnice", 3);
|
||||
+ data = sess.getData("/test_module:popelnice", 4);
|
||||
REQUIRE(data);
|
||||
REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
"test_module:popelnice": {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
From 6ed5442db482a9a37128add02778aebd096f9207 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 16:20:09 +0100
|
||||
Subject: [PATCH 10/20] Utilities for working with existing
|
||||
sysrepo:discard-items nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
findMatchingDiscard() and findMatchingDiscardPrefixes() are useful for
|
||||
figuring out which nodes need to be prune when adding/modifying a
|
||||
discarding node.
|
||||
|
||||
unlinkFromForest() is useful for removing "a node" from a forest of data
|
||||
node trees. Sometimes the node to be deleted *could* be a root node
|
||||
which is also the first sibling among its collection of siblings, and in
|
||||
that case some extra care is needed to ensure that we still have a
|
||||
reference to something after we've done with the unlinking.
|
||||
|
||||
In the real world this doesn't happen that often because there usually
|
||||
is something like a top-level container node below which we're
|
||||
processing these discards. Still, let's put this utility function into a
|
||||
single place.
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8436
|
||||
Change-Id: Ib346be6887f75220a8a6435864c74aef8d5925bb
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/utils/utils.hpp | 3 ++
|
||||
src/Session.cpp | 3 ++
|
||||
src/utils/utils.cpp | 57 ++++++++++++++++++++++
|
||||
tests/session.cpp | 74 +++++++++++++++++++++++++++--
|
||||
4 files changed, 134 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp
|
||||
index 22d7d66..64b1c2e 100644
|
||||
--- a/include/sysrepo-cpp/utils/utils.hpp
|
||||
+++ b/include/sysrepo-cpp/utils/utils.hpp
|
||||
@@ -12,4 +12,7 @@
|
||||
namespace sysrepo {
|
||||
Session wrapUnmanagedSession(sr_session_ctx_s* session);
|
||||
void setLogLevelStderr(const LogLevel);
|
||||
+std::optional<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/Session.cpp b/src/Session.cpp
|
||||
index 5df04bc..3c21e84 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -153,6 +153,7 @@ void Session::deleteItem(const std::string& path, const EditOptions opts)
|
||||
* Wraps `sr_discard_items`.
|
||||
*
|
||||
* @param xpath Expression filtering the nodes to discard, nullopt for all nodes.
|
||||
+ * @see findMatchingDiscard()
|
||||
*/
|
||||
void Session::dropForeignOperationalContent(const std::optional<std::string>& xpath)
|
||||
{
|
||||
@@ -167,6 +168,8 @@ void Session::dropForeignOperationalContent(const std::optional<std::string>& xp
|
||||
* with the "replace" operation.
|
||||
*
|
||||
* Wraps `sr_get_oper_changes`.
|
||||
+ *
|
||||
+ * @see findMatchingDiscard()
|
||||
*/
|
||||
std::optional<libyang::DataNode> Session::operationalChanges(const std::optional<std::string>& moduleName) const
|
||||
{
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index 972a4fa..e339e88 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -72,4 +72,61 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional<FDHandli
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short If there's a sysrepo:discard-items node which matches the given XPath, return it
|
||||
+ *
|
||||
+ * @see Session::operationalChanges()
|
||||
+ * @see Session::dropForeignOperationalContent()
|
||||
+ */
|
||||
+std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
|
||||
+{
|
||||
+ auto discard = root.firstOpaqueSibling();
|
||||
+ while (discard) {
|
||||
+ if (discard->name().matches("sysrepo", "discard-items") && discard->value() == xpath) {
|
||||
+ return discard;
|
||||
+ }
|
||||
+ if (auto next = discard->nextSibling()) {
|
||||
+ discard = next->asOpaque();
|
||||
+ } else {
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendats of this XPath
|
||||
+ */
|
||||
+std::vector<libyang::DataNode> 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;
|
||||
+ auto discard = root.firstOpaqueSibling();
|
||||
+ while (discard) {
|
||||
+ if (discard->name().matches("sysrepo", "discard-items")) {
|
||||
+ if (auto text = discard->value(); text == xpathPrefix || text.starts_with(withSlash) || text.starts_with(withBracket)) {
|
||||
+ res.emplace_back(*discard);
|
||||
+ }
|
||||
+ }
|
||||
+ if (auto next = discard->nextSibling()) {
|
||||
+ discard = next->asOpaque();
|
||||
+ } else {
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ return res;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @short Remove a node from a forest of tree nodes while modifying the root in-place
|
||||
+ */
|
||||
+void unlinkFromForest(std::optional<libyang::DataNode>& root, libyang::DataNode node)
|
||||
+{
|
||||
+ if (node == root) {
|
||||
+ root = node.nextSibling();
|
||||
+ }
|
||||
+ node.unlink();
|
||||
+}
|
||||
+
|
||||
}
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 5be0d79..9a419a1 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -245,9 +245,77 @@ TEST_CASE("session")
|
||||
sess.applyChanges();
|
||||
REQUIRE(!sess.getData(leaf));
|
||||
|
||||
- // Using discardOperationalChanges makes the leaf visible again (in the operational datastore).
|
||||
- // Also, no need to applyChanges().
|
||||
- sess.discardOperationalChanges("test_module");
|
||||
+ // check that a magic sysrepo:discard-items node is in place
|
||||
+ REQUIRE(!!sess.operationalChanges());
|
||||
+ auto matchingDiscard = sysrepo::findMatchingDiscard(*sess.operationalChanges(), leaf);
|
||||
+ REQUIRE(!!matchingDiscard);
|
||||
+ REQUIRE(matchingDiscard->value() == leaf);
|
||||
+ REQUIRE(matchingDiscard->name().moduleOrNamespace == "sysrepo");
|
||||
+ REQUIRE(matchingDiscard->name().name == "discard-items");
|
||||
+ REQUIRE(!sysrepo::findMatchingDiscard(*sess.operationalChanges(), "something else"));
|
||||
+
|
||||
+ DOCTEST_SUBCASE("forget changes via discardOperationalChanges(module)")
|
||||
+ {
|
||||
+ // Using discardOperationalChanges makes the leaf visible again (in the operational datastore).
|
||||
+ // Also, no need to applyChanges().
|
||||
+ sess.discardOperationalChanges("test_module");
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("forget changes via a selective edit")
|
||||
+ {
|
||||
+ // this edit only has a single node, which means that we cannot really call unlink() and hope for a sane result
|
||||
+ REQUIRE(matchingDiscard->firstSibling() == *matchingDiscard);
|
||||
+
|
||||
+ // so, we add a dummy node instead...
|
||||
+ auto root = matchingDiscard->newPath("/test_module:popelnice/s", "foo");
|
||||
+ // ...and only then we nuke the eixtsing discard-items node
|
||||
+ matchingDiscard->unlink();
|
||||
+ sess.editBatch(*root, sysrepo::DefaultOperation::Replace);
|
||||
+ sess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("multiple sysrepo:discard-items nodes")
|
||||
+ {
|
||||
+ sess.dropForeignOperationalContent("/test_module:popelnice");
|
||||
+ sess.dropForeignOperationalContent("/test_module:popelnice/s");
|
||||
+ sess.dropForeignOperationalContent("/test_module:values");
|
||||
+ sess.dropForeignOperationalContent("/test_module:popelnice/content");
|
||||
+ sess.dropForeignOperationalContent("/test_module:denyAllLeaf");
|
||||
+ sess.dropForeignOperationalContent(leaf); // yup, once more, in addition to the one at the very beginning
|
||||
+ sess.applyChanges();
|
||||
+
|
||||
+ auto forPopelnice = sysrepo::findMatchingDiscard(*sess.operationalChanges(), "/test_module:popelnice");
|
||||
+ REQUIRE(!!forPopelnice);
|
||||
+ REQUIRE(forPopelnice->value() == "/test_module:popelnice");
|
||||
+ auto oneMatch = sysrepo::findMatchingDiscard(*sess.operationalChanges(), "/test_module:values");
|
||||
+ REQUIRE(!!oneMatch);
|
||||
+ REQUIRE(oneMatch->value() == "/test_module:values");
|
||||
+
|
||||
+ auto atOrBelowPopelnice = sysrepo::findMatchingDiscardPrefixes(*sess.operationalChanges(), "/test_module:popelnice");
|
||||
+ REQUIRE(atOrBelowPopelnice.size() == 3);
|
||||
+ // yup, these are apparently backwards compared to how I put them in. Never mind.
|
||||
+ REQUIRE(atOrBelowPopelnice[2].value() == "/test_module:popelnice");
|
||||
+ REQUIRE(atOrBelowPopelnice[1].value() == "/test_module:popelnice/s");
|
||||
+ REQUIRE(atOrBelowPopelnice[0].value() == "/test_module:popelnice/content");
|
||||
+
|
||||
+ auto belowPopelnice = sysrepo::findMatchingDiscardPrefixes(*sess.operationalChanges(), "/test_module:popelnice/");
|
||||
+ REQUIRE(belowPopelnice.size() == 2);
|
||||
+ // again, the order is reversed
|
||||
+ REQUIRE(belowPopelnice[1].value() == "/test_module:popelnice/s");
|
||||
+ REQUIRE(belowPopelnice[0].value() == "/test_module:popelnice/content");
|
||||
+
|
||||
+ auto newEdit = sess.operationalChanges();
|
||||
+ auto forLeaf = sysrepo::findMatchingDiscardPrefixes(*newEdit, leaf);
|
||||
+ REQUIRE(forLeaf.size() == 2);
|
||||
+ REQUIRE(forLeaf[0].value() == leaf);
|
||||
+ REQUIRE(forLeaf[1].value() == leaf);
|
||||
+ for (auto node : forLeaf) {
|
||||
+ sysrepo::unlinkFromForest(newEdit, node);
|
||||
+ }
|
||||
+ sess.editBatch(*newEdit, sysrepo::DefaultOperation::Replace);
|
||||
+ sess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123");
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
From aa78815b05090df63bef9ac1b6473f32aac5363d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 18 Mar 2025 11:25:05 +0100
|
||||
Subject: [PATCH 11/20] Fix a typo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...because I don't want to go through the CI again, hence a separate
|
||||
commit.
|
||||
|
||||
Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes
|
||||
Reported-by: Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
Change-Id: I31f3efc1006fe222b7853b24a116b3d8cc04dc8d
|
||||
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 e339e88..ff4a32c 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -95,7 +95,7 @@ std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, con
|
||||
}
|
||||
|
||||
/**
|
||||
- * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendats of this XPath
|
||||
+ * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath
|
||||
*/
|
||||
std::vector<libyang::DataNode> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,414 @@
|
||||
From 9d812203b3c0550995be8d60e7b11761bdbac04d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 1 Apr 2025 21:22:04 +0200
|
||||
Subject: [PATCH 13/20] add subtree filtering to notifications subscription
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Ie547b5f478cc8e3b09ea2f324b62854576787e1b
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
include/sysrepo-cpp/Session.hpp | 6 +-
|
||||
src/Session.cpp | 58 +++++++++--
|
||||
tests/subscriptions-dynamic.cpp | 173 ++++++++++++++++++++++++++++++--
|
||||
4 files changed, 213 insertions(+), 26 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 01ec967..47472d2 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -82,7 +82,7 @@ if(BUILD_TESTING)
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-interfaces@2018-02-20.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-ip@2018-02-22.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-network-instance@2019-01-21.yang
|
||||
- --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay -e subtree
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-yang-push@2019-09-09.yang -e on-change
|
||||
)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 255f861..0781890 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -137,17 +137,17 @@ public:
|
||||
const std::optional<FDHandling>& callbacks = std::nullopt);
|
||||
|
||||
[[nodiscard]] DynamicSubscription yangPushPeriodic(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
std::chrono::milliseconds periodTime,
|
||||
const std::optional<NotificationTimeStamp>& anchorTime = std::nullopt,
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
|
||||
[[nodiscard]] DynamicSubscription yangPushOnChange(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod = std::nullopt,
|
||||
SyncOnStart syncOnStart = SyncOnStart::No,
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
|
||||
[[nodiscard]] DynamicSubscription subscribeNotifications(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::string>& stream = std::nullopt,
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt,
|
||||
const std::optional<NotificationTimeStamp>& startTime = std::nullopt);
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 2c27c92..1d65d70 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -42,6 +42,37 @@ libyang::DataNode wrapSrData(std::shared_ptr<sr_session_ctx_s> sess, sr_data_t*
|
||||
sr_release_data(data);
|
||||
}));
|
||||
}
|
||||
+
|
||||
+std::optional<std::string> constructXPathFilter(const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter)
|
||||
+{
|
||||
+ if (!filter) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ if (std::holds_alternative<std::string>(*filter)) {
|
||||
+ return std::get<std::string>(*filter);
|
||||
+ }
|
||||
+
|
||||
+ auto node = std::get<libyang::DataNodeAny>(*filter);
|
||||
+ auto value = node.releaseValue();
|
||||
+
|
||||
+ if (!value) {
|
||||
+ return "/"; // select nothing, RFC 6241, 6.4.2
|
||||
+ }
|
||||
+
|
||||
+ if (std::holds_alternative<libyang::DataNode>(*value)) {
|
||||
+ char* str;
|
||||
+
|
||||
+ auto filterTree = std::get<libyang::DataNode>(*value);
|
||||
+ auto res = srsn_filter_subtree2xpath(libyang::getRawNode(filterTree), nullptr, &str);
|
||||
+ std::unique_ptr<char, decltype([](auto* p) constexpr { std::free(p); })> strDeleter(str); // pass ownership of c-string to the deleter
|
||||
+
|
||||
+ throwIfError(res, "Unable to convert subtree filter to xpath");
|
||||
+ return str;
|
||||
+ }
|
||||
+
|
||||
+ throw Error("Subtree filter anydata node must contain (possibly empty) libyang tree");
|
||||
+}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,9 +557,9 @@ Subscription Session::onNotification(
|
||||
/**
|
||||
* Subscribe for receiving notifications according to 'ietf-yang-push' YANG periodic subscriptions.
|
||||
*
|
||||
- * Wraps `srsn_yang_push_periodic`.
|
||||
+ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters.
|
||||
*
|
||||
- * @param xpathFilter Optional XPath that filters received notification.
|
||||
+ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny
|
||||
* @param periodTime Notification period.
|
||||
* @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
@@ -536,7 +567,7 @@ Subscription Session::onNotification(
|
||||
* @return A YangPushSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushPeriodic(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
std::chrono::milliseconds periodTime,
|
||||
const std::optional<NotificationTimeStamp>& anchorTime,
|
||||
const std::optional<NotificationTimeStamp>& stopTime)
|
||||
@@ -545,6 +576,8 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
uint32_t subId;
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
auto anchorSpec = anchorTime ? std::optional{toTimespec(*anchorTime)} : std::nullopt;
|
||||
+ auto xpathFilter = constructXPathFilter(filter);
|
||||
+
|
||||
auto res = srsn_yang_push_periodic(m_sess.get(),
|
||||
toDatastore(activeDatastore()),
|
||||
xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
@@ -561,9 +594,9 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
/**
|
||||
* Subscribe for receiving notifications according to 'ietf-yang-push' YANG on-change subscriptions.
|
||||
*
|
||||
- * Wraps `srsn_yang_push_on_change`.
|
||||
+ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters.
|
||||
*
|
||||
- * @param xpathFilter Optional XPath that filters received notification.
|
||||
+ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny
|
||||
* @param dampeningPeriod Optional dampening period.
|
||||
* @param syncOnStart Whether to start with a notification of the current state.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
@@ -571,7 +604,7 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
* @return A YangPushSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushOnChange(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod,
|
||||
SyncOnStart syncOnStart,
|
||||
const std::optional<NotificationTimeStamp>& stopTime)
|
||||
@@ -579,6 +612,8 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
int fd;
|
||||
uint32_t subId;
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
+ auto xpathFilter = constructXPathFilter(filter);
|
||||
+
|
||||
auto res = srsn_yang_push_on_change(m_sess.get(),
|
||||
toDatastore(activeDatastore()),
|
||||
xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
@@ -596,11 +631,11 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
}
|
||||
|
||||
/**
|
||||
- * Subscribe for receiving notifications according to 'ietf-subscribed-notifications'
|
||||
+ * Subscribe for receiving notifications according to 'ietf-subscribed-notifications'.
|
||||
*
|
||||
- * Wraps `srsn_subscribe.
|
||||
+ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters.
|
||||
*
|
||||
- * @param xpathFilter Optional XPath that filters received notification.
|
||||
+ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny
|
||||
* @param stream Optional stream to subscribe to.
|
||||
* @param stopTime Optional stop time ending the subscription.
|
||||
* @param startTime Optional start time of the subscription, used for replaying stored notifications.
|
||||
@@ -608,7 +643,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
* @return A YangPushSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::subscribeNotifications(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::string>& stream,
|
||||
const std::optional<NotificationTimeStamp>& stopTime,
|
||||
const std::optional<NotificationTimeStamp>& startTime)
|
||||
@@ -618,10 +653,11 @@ DynamicSubscription Session::subscribeNotifications(
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
auto startSpec = startTime ? std::optional{toTimespec(*startTime)} : std::nullopt;
|
||||
struct timespec replayStartSpec;
|
||||
+ auto xpathFilter = constructXPathFilter(filter);
|
||||
|
||||
auto res = srsn_subscribe(m_sess.get(),
|
||||
stream ? stream->c_str() : nullptr,
|
||||
- xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
+ xpathFilter ? xpathFilter->data() : nullptr,
|
||||
stopSpec ? &stopSpec.value() : nullptr,
|
||||
startSpec ? &startSpec.value() : nullptr,
|
||||
false,
|
||||
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||
index a44bfba..84a0880 100644
|
||||
--- a/tests/subscriptions-dynamic.cpp
|
||||
+++ b/tests/subscriptions-dynamic.cpp
|
||||
@@ -28,6 +28,9 @@
|
||||
#define REQUIRE_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \
|
||||
TROMPELOEIL_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq);
|
||||
|
||||
+#define REQUIRE_NAMED_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \
|
||||
+ expectations.emplace_back(TROMPELOEIL_NAMED_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq));
|
||||
+
|
||||
#define READ_NOTIFICATION(SUBSCRIPTION) \
|
||||
REQUIRE(pipeStatus((SUBSCRIPTION).fd()) == PipeStatus::DataReady); \
|
||||
(SUBSCRIPTION).processEvent(cbNotif);
|
||||
@@ -276,6 +279,89 @@ TEST_CASE("Dynamic subscriptions")
|
||||
const auto excMessage = "Couldn't terminate yang-push subscription with id " + std::to_string(sub->subscriptionId()) + ": SR_ERR_NOT_FOUND";
|
||||
REQUIRE_THROWS_WITH_AS(sub->terminate(), excMessage.c_str(), sysrepo::ErrorWithCode);
|
||||
}
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Filtering")
|
||||
+ {
|
||||
+ std::optional<sysrepo::DynamicSubscription> sub;
|
||||
+ std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("xpath filter")
|
||||
+ {
|
||||
+ sub = sess.subscribeNotifications("/test_module:ping");
|
||||
+
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("subtree filter")
|
||||
+ {
|
||||
+ libyang::CreatedNodes createdNodes;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("filter a node")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("XML")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::XML{"<ping xmlns='urn:ietf:params:xml:ns:yang:test_module' />"});
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("JSON")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::JSON{R"({"test_module:ping": {}})"});
|
||||
+ }
|
||||
+
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("filter more top level nodes")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("XML")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::XML{"<ping xmlns='urn:ietf:params:xml:ns:yang:test_module' />"
|
||||
+ "<silent-ping xmlns='urn:ietf:params:xml:ns:yang:test_module' />"});
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("JSON")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::JSON{R"({
|
||||
+ "test_module:ping": {},
|
||||
+ "test_module:silent-ping": {}
|
||||
+ })"});
|
||||
+ }
|
||||
+
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]);
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[1]);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("empty filter selects nothing")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ std::nullopt);
|
||||
+ }
|
||||
+
|
||||
+ sub = sess.subscribeNotifications(createdNodes.createdNode->asAny());
|
||||
+ }
|
||||
+
|
||||
+ CLIENT_SEND_NOTIFICATION(notifications[0]);
|
||||
+ CLIENT_SEND_NOTIFICATION(notifications[1]);
|
||||
+
|
||||
+ // read as many notifications as we expect
|
||||
+ for (size_t i = 0; i < expectations.size(); ++i) {
|
||||
+ READ_NOTIFICATION_BLOCKING(*sub);
|
||||
+ }
|
||||
+
|
||||
+ sub->terminate();
|
||||
+
|
||||
+ // ensure no more notifications were sent
|
||||
+ REQUIRE_PIPE_HANGUP(*sub);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("YANG Push on change")
|
||||
@@ -285,9 +371,73 @@ TEST_CASE("Dynamic subscriptions")
|
||||
* between writing to sysrepo and reading the notifications.
|
||||
*/
|
||||
|
||||
- auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes);
|
||||
+ DOCTEST_SUBCASE("Filters")
|
||||
+ {
|
||||
+ std::optional<sysrepo::DynamicSubscription> sub;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XPath filter")
|
||||
+ {
|
||||
+ sub = sess.yangPushOnChange("/test_module:leafInt32 | /test_module:popelnice/content/trash[name='asd']");
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Subtree filter")
|
||||
+ {
|
||||
+ auto createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/ietf-yang-push:datastore-subtree-filter",
|
||||
+ libyang::XML{"<leafInt32 xmlns='http://example.com/' />"
|
||||
+ "<popelnice xmlns='http://example.com/'><content><trash><name>asd</name></trash></content></popelnice>"});
|
||||
+ sub = sess.yangPushOnChange(createdNodes.createdNode->asAny());
|
||||
+ }
|
||||
+
|
||||
+ client.setItem("/test_module:leafInt32", "42");
|
||||
+ client.setItem("/test_module:popelnice/s", "asd");
|
||||
+ client.setItem("/test_module:popelnice/content/trash[name='asd']", std::nullopt);
|
||||
+ client.applyChanges();
|
||||
|
||||
- REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ client.deleteItem("/test_module:popelnice/s");
|
||||
+ client.applyChanges();
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(*sub, R"({
|
||||
+ "ietf-yang-push:push-change-update": {
|
||||
+ "datastore-changes": {
|
||||
+ "yang-patch": {
|
||||
+ "patch-id": "patch-1",
|
||||
+ "edit": [
|
||||
+ {
|
||||
+ "edit-id": "edit-1",
|
||||
+ "operation": "create",
|
||||
+ "target": "/test_module:leafInt32",
|
||||
+ "value": {
|
||||
+ "test_module:leafInt32": 42
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "edit-id": "edit-2",
|
||||
+ "operation": "create",
|
||||
+ "target": "/test_module:popelnice/content/trash[name='asd']",
|
||||
+ "value": {
|
||||
+ "test_module:trash": {
|
||||
+ "name": "asd"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+)");
|
||||
+ READ_YANG_PUSH_UPDATE(*sub);
|
||||
+
|
||||
+ sub->terminate();
|
||||
+ REQUIRE_PIPE_HANGUP(*sub);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Sync on start")
|
||||
+ {
|
||||
+ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes);
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
"ietf-yang-push:push-update": {
|
||||
"datastore-contents": {
|
||||
"test_module:values": [
|
||||
@@ -298,14 +448,14 @@ TEST_CASE("Dynamic subscriptions")
|
||||
}
|
||||
}
|
||||
)");
|
||||
- READ_YANG_PUSH_UPDATE(sub);
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
|
||||
- client.setItem("/test_module:leafInt32", "123");
|
||||
- client.setItem("/test_module:values[.='5']", std::nullopt);
|
||||
- client.deleteItem("/test_module:values[.='3']");
|
||||
- client.applyChanges();
|
||||
+ client.setItem("/test_module:leafInt32", "123");
|
||||
+ client.setItem("/test_module:values[.='5']", std::nullopt);
|
||||
+ client.deleteItem("/test_module:values[.='3']");
|
||||
+ client.applyChanges();
|
||||
|
||||
- REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
"ietf-yang-push:push-change-update": {
|
||||
"datastore-changes": {
|
||||
"yang-patch": {
|
||||
@@ -342,10 +492,11 @@ TEST_CASE("Dynamic subscriptions")
|
||||
}
|
||||
}
|
||||
)");
|
||||
- READ_YANG_PUSH_UPDATE(sub);
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
|
||||
- sub.terminate();
|
||||
- REQUIRE_PIPE_HANGUP(sub);
|
||||
+ sub.terminate();
|
||||
+ REQUIRE_PIPE_HANGUP(sub);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("YANG Push periodic")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
From b960058e37c471084538bb5db8b3b5e9e992c46c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 2 Apr 2025 21:47:21 +0200
|
||||
Subject: [PATCH 14/20] add excluded changes to YANG push on-change wrapper
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Idb7bb48a3c60ff70286441cd4d0a5c1c28acee15
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Enum.hpp | 12 +++++++
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 12 ++++++-
|
||||
src/utils/enum.hpp | 8 +++++
|
||||
tests/subscriptions-dynamic.cpp | 60 +++++++++++++++++++++++++++++++++
|
||||
5 files changed, 92 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp
|
||||
index cac1f2e..48467c6 100644
|
||||
--- a/include/sysrepo-cpp/Enum.hpp
|
||||
+++ b/include/sysrepo-cpp/Enum.hpp
|
||||
@@ -209,6 +209,18 @@ constexpr GetOptions operator|(const GetOptions a, const GetOptions b)
|
||||
return implEnumBitOr(a, b);
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Wraps `srsn_yang_push_change_t`
|
||||
+ */
|
||||
+enum class YangPushChange : uint32_t {
|
||||
+ Create = 0, /**< SRSN_YP_CHANGE_CREATE */
|
||||
+ Delete = 1, /**< SRSN_YP_CHANGE_DELETE */
|
||||
+ Insert = 2, /**< SRSN_YP_CHANGE_INSERT */
|
||||
+ Move = 3, /**< SRSN_YP_CHANGE_MOVE */
|
||||
+ Replace = 4, /**< SRSN_YP_CHANGE_REPLACE */
|
||||
+ EnumCount = 5, /**< SRSN_COUNT_YP_CHANGE */
|
||||
+};
|
||||
+
|
||||
std::ostream& operator<<(std::ostream& os, const NotificationType& type);
|
||||
std::ostream& operator<<(std::ostream& os, const Event& event);
|
||||
std::ostream& operator<<(std::ostream& os, const ChangeOperation& changeOp);
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 0781890..b7cad72 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -145,6 +145,7 @@ public:
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod = std::nullopt,
|
||||
SyncOnStart syncOnStart = SyncOnStart::No,
|
||||
+ const std::set<YangPushChange>& excludedChanges = {},
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
|
||||
[[nodiscard]] DynamicSubscription subscribeNotifications(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 1d65d70..40c3df3 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -607,6 +607,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod,
|
||||
SyncOnStart syncOnStart,
|
||||
+ const std::set<YangPushChange>& excludedChanges,
|
||||
const std::optional<NotificationTimeStamp>& stopTime)
|
||||
{
|
||||
int fd;
|
||||
@@ -614,12 +615,21 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
auto xpathFilter = constructXPathFilter(filter);
|
||||
|
||||
+ /* The enum values are not used as the other enum flags in sysrepo-cpp.
|
||||
+ * srsn_yang_push_on_change expects an integer array of size EnumCount with 0 or 1 values.
|
||||
+ */
|
||||
+ using YangPushChangeUnderlying = std::underlying_type_t<YangPushChange>;
|
||||
+ std::array<int, static_cast<YangPushChangeUnderlying>(YangPushChange::EnumCount)> excludedChangesArray{};
|
||||
+ for (const auto& change: excludedChanges) {
|
||||
+ excludedChangesArray[static_cast<YangPushChangeUnderlying>(change)] = 1;
|
||||
+ }
|
||||
+
|
||||
auto res = srsn_yang_push_on_change(m_sess.get(),
|
||||
toDatastore(activeDatastore()),
|
||||
xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
dampeningPeriod ? dampeningPeriod->count() : 0,
|
||||
syncOnStart == SyncOnStart::Yes,
|
||||
- nullptr,
|
||||
+ excludedChangesArray.data(),
|
||||
stopSpec ? &stopSpec.value() : nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
|
||||
index a2dcf7a..2d3a298 100644
|
||||
--- a/src/utils/enum.hpp
|
||||
+++ b/src/utils/enum.hpp
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <type_traits>
|
||||
extern "C" {
|
||||
#include <sysrepo.h>
|
||||
+#include <sysrepo/subscribed_notifications.h>
|
||||
}
|
||||
#include <sysrepo-cpp/Enum.hpp>
|
||||
|
||||
@@ -171,4 +172,11 @@ constexpr sr_get_options_t toGetOptions(const GetOptions opts)
|
||||
{
|
||||
return static_cast<sr_get_options_t>(opts);
|
||||
}
|
||||
+
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_DELETE) == YangPushChange::Delete);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_INSERT) == YangPushChange::Insert);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_MOVE) == YangPushChange::Move);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_REPLACE) == YangPushChange::Replace);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_COUNT_YP_CHANGE) == YangPushChange::EnumCount);
|
||||
}
|
||||
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||
index 84a0880..5ed173b 100644
|
||||
--- a/tests/subscriptions-dynamic.cpp
|
||||
+++ b/tests/subscriptions-dynamic.cpp
|
||||
@@ -497,6 +497,66 @@ TEST_CASE("Dynamic subscriptions")
|
||||
sub.terminate();
|
||||
REQUIRE_PIPE_HANGUP(sub);
|
||||
}
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Excluded changes")
|
||||
+ {
|
||||
+ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::No, {sysrepo::YangPushChange::Create});
|
||||
+
|
||||
+ client.setItem("/test_module:leafInt32", "123");
|
||||
+ client.applyChanges(); // excluded (create)
|
||||
+ client.setItem("/test_module:leafInt32", "124");
|
||||
+ client.applyChanges();
|
||||
+ client.setItem("/test_module:leafInt32", "125");
|
||||
+ client.applyChanges();
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ "ietf-yang-push:push-change-update": {
|
||||
+ "datastore-changes": {
|
||||
+ "yang-patch": {
|
||||
+ "patch-id": "patch-1",
|
||||
+ "edit": [
|
||||
+ {
|
||||
+ "edit-id": "edit-1",
|
||||
+ "operation": "replace",
|
||||
+ "target": "/test_module:leafInt32",
|
||||
+ "value": {
|
||||
+ "test_module:leafInt32": 124
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ "ietf-yang-push:push-change-update": {
|
||||
+ "datastore-changes": {
|
||||
+ "yang-patch": {
|
||||
+ "patch-id": "patch-2",
|
||||
+ "edit": [
|
||||
+ {
|
||||
+ "edit-id": "edit-1",
|
||||
+ "operation": "replace",
|
||||
+ "target": "/test_module:leafInt32",
|
||||
+ "value": {
|
||||
+ "test_module:leafInt32": 125
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+)");
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
+
|
||||
+ sub.terminate();
|
||||
+ REQUIRE_PIPE_HANGUP(sub);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("YANG Push periodic")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
From 3407f98d147ffbf031725102181e1fe3cd874363 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 12:52:00 +0200
|
||||
Subject: [PATCH 15/20] wrap sr_nacm_get_user
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This adds a counterpart to setting NACM user. Sometimes, it might be
|
||||
useful to get it as well.
|
||||
|
||||
Change-Id: I534a229eb536d63091904ab4f6268cb9da6dd3db
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 9 +++++++++
|
||||
tests/session.cpp | 7 +++++++
|
||||
3 files changed, 17 insertions(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index b7cad72..1c409b8 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
void replaceConfig(std::optional<libyang::DataNode> config, const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
|
||||
void setNacmUser(const std::string& user);
|
||||
+ std::optional<std::string> getNacmUser() const;
|
||||
[[nodiscard]] Subscription initNacm(
|
||||
SubscribeOptions opts = SubscribeOptions::Default,
|
||||
ExceptionHandler handler = nullptr,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 40c3df3..2273c79 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -706,6 +706,15 @@ void Session::setNacmUser(const std::string& user)
|
||||
throwIfError(res, "Couldn't set NACM user", m_sess.get());
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Get the NACM user for this session.
|
||||
+ */
|
||||
+std::optional<std::string> Session::getNacmUser() const
|
||||
+{
|
||||
+ auto* username = sr_nacm_get_user(m_sess.get());
|
||||
+ return username ? std::make_optional<std::string>(username) : std::nullopt;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Initializes NACM callbacks.
|
||||
*
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 9a419a1..548b371 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -390,10 +390,14 @@ TEST_CASE("session")
|
||||
auto data = sess.getData("/test_module:denyAllLeaf");
|
||||
REQUIRE(data.value().findPath("/test_module:denyAllLeaf").value().asTerm().valueStr() == "AHOJ");
|
||||
|
||||
+ REQUIRE(!sess.getNacmUser());
|
||||
+
|
||||
// check that repeated NACM initialization still works
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto nacmSub = sess.initNacm();
|
||||
sess.setNacmUser("nobody");
|
||||
+ REQUIRE(sess.getNacmUser() == "nobody");
|
||||
+
|
||||
data = sess.getData("/test_module:denyAllLeaf");
|
||||
// After turning on NACM, we can't access the leaf.
|
||||
REQUIRE(!data);
|
||||
@@ -408,6 +412,9 @@ TEST_CASE("session")
|
||||
sysrepo::ErrorWithCode);
|
||||
}
|
||||
|
||||
+ REQUIRE(!!sess.getNacmUser());
|
||||
+ REQUIRE(sess.getNacmUser() == "nobody");
|
||||
+
|
||||
// duplicate NACM initialization should throw
|
||||
auto nacm = sess.initNacm();
|
||||
REQUIRE_THROWS_WITH_AS(auto x = sess.initNacm(),
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
From afc4b8e04b4bf61d6bf0e0a66c1115b0111eaebf Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:36:30 +0200
|
||||
Subject: [PATCH 16/20] CI: renamed project upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8570
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
README.md | 2 +-
|
||||
ci/build.sh | 2 +-
|
||||
3 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index dc87c6f..7ada6f7 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -7,7 +7,7 @@
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
override-checkout: v44
|
||||
@@ -18,7 +18,7 @@
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
override-checkout: v44
|
||||
diff --git a/README.md b/README.md
|
||||
index 27dc748..dd9570c 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -12,7 +12,7 @@ It uses RAII for automatic memory management.
|
||||
- [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang*
|
||||
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||
- CMake 3.19+
|
||||
-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for built-in tests, [trompeloeil](https://github.com/rollbear/trompeloeil) for mock objects in C++
|
||||
- optionally for the docs, Doxygen
|
||||
|
||||
diff --git a/ci/build.sh b/ci/build.sh
|
||||
index d886b79..785a811 100755
|
||||
--- a/ci/build.sh
|
||||
+++ b/ci/build.sh
|
||||
@@ -78,7 +78,7 @@ build_n_test() {
|
||||
|
||||
build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF
|
||||
build_n_test github/sysrepo/sysrepo -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF -DREPO_PATH=${PREFIX}/etc-sysrepo
|
||||
-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
+build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
# non-release builds download Catch2
|
||||
CMAKE_BUILD_TYPE=Release build_n_test github/rollbear/trompeloeil
|
||||
build_n_test CzechLight/libyang-cpp -DBUILD_TESTING=ON
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
From ad070266f9347f159874e6b2fa57302385f354e3 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 14:51:31 +0200
|
||||
Subject: [PATCH 17/20] wrap sr_nacm_check_operation
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Sysrepo provides API function for explicit checking if an operation can
|
||||
be authorized on a node. This might come handy if one decides to do NACM
|
||||
authorization himself, like we will do in our RESTCONF server.
|
||||
|
||||
Change-Id: Ida41514a7f03ab120a331363b7f9ed8b69918d88
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 13 ++++++++++
|
||||
tests/session.cpp | 46 +++++++++++++++++++++++++++++++++
|
||||
tests/test_module.yang | 4 +++
|
||||
4 files changed, 64 insertions(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 1c409b8..2112d21 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
|
||||
void setNacmUser(const std::string& user);
|
||||
std::optional<std::string> getNacmUser() const;
|
||||
+ bool checkNacmOperation(const libyang::DataNode& node) const;
|
||||
[[nodiscard]] Subscription initNacm(
|
||||
SubscribeOptions opts = SubscribeOptions::Default,
|
||||
ExceptionHandler handler = nullptr,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 2273c79..ce0dec2 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -715,6 +715,19 @@ std::optional<std::string> Session::getNacmUser() const
|
||||
return username ? std::make_optional<std::string>(username) : std::nullopt;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`.
|
||||
+ * @return true if the current user is authorized to perform operation on given @p node.
|
||||
+ *
|
||||
+ * Details on unsuccessfull authorizations can be retrieved via Session::getErrors.
|
||||
+ * Note that if the NACM user is not set, `sr_nacm_check_operation` and this function both return true.
|
||||
+ */
|
||||
+bool Session::checkNacmOperation(const libyang::DataNode& node) const
|
||||
+{
|
||||
+ auto res = sr_nacm_check_operation(m_sess.get(), libyang::getRawNode(node));
|
||||
+ return res == SR_ERR_OK;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Initializes NACM callbacks.
|
||||
*
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 548b371..35dd545 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -423,6 +423,52 @@ TEST_CASE("session")
|
||||
sysrepo::ErrorWithCode);
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("Session::checkNacmOperation")
|
||||
+ {
|
||||
+ auto nacmSub = sess.initNacm();
|
||||
+
|
||||
+ // check NACM access for RPCs
|
||||
+ auto shutdownRPC = sess.getContext().newPath("/test_module:shutdown", std::nullopt);
|
||||
+ auto denyAllRPC = sess.getContext().newPath("/test_module:deny-all-rpc", std::nullopt);
|
||||
+
|
||||
+ // user not set, everything is permitted
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true);
|
||||
+ REQUIRE(sess.getErrors().size() == 0);
|
||||
+
|
||||
+ sess.setNacmUser("root");
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true);
|
||||
+ REQUIRE(sess.getErrors().size() == 0);
|
||||
+
|
||||
+ sess.setNacmUser("nobody");
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == false);
|
||||
+ REQUIRE(sess.getErrors().size() == 1);
|
||||
+ REQUIRE(sess.getErrors().at(0) == sysrepo::ErrorInfo{
|
||||
+ .code = sysrepo::ErrorCode::Unauthorized,
|
||||
+ .errorMessage = "Executing the operation is denied because \"nobody\" NACM authorization failed.",
|
||||
+ });
|
||||
+
|
||||
+ sess.setNacmUser("root"); // 'nobody' is not authorized to write into this subtree
|
||||
+ sess.switchDatastore(sysrepo::Datastore::Running);
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/enable-external-groups", "false");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/groups/group[name='grp']/user-name[.='nobody']", "");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/group[.='grp']", "");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/module-name", "test_module");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/access-operations", "*");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/action", "deny");
|
||||
+ sess.applyChanges();
|
||||
+
|
||||
+ sess.setNacmUser("root");
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+
|
||||
+ sess.setNacmUser("nobody");
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == false);
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == false);
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Session::getPendingChanges")
|
||||
{
|
||||
REQUIRE(sess.getPendingChanges() == std::nullopt);
|
||||
diff --git a/tests/test_module.yang b/tests/test_module.yang
|
||||
index 02c467b..3d6c26d 100644
|
||||
--- a/tests/test_module.yang
|
||||
+++ b/tests/test_module.yang
|
||||
@@ -55,6 +55,10 @@ module test_module {
|
||||
rpc noop {
|
||||
}
|
||||
|
||||
+ rpc deny-all-rpc {
|
||||
+ nacm:default-deny-all;
|
||||
+ }
|
||||
+
|
||||
rpc shutdown {
|
||||
output {
|
||||
leaf success {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
From cecf636eecea53a597bcbf75eb4dda0f916e9967 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 15 Apr 2025 10:54:11 +0200
|
||||
Subject: [PATCH 18/20] Fix return value type in doxygen
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Fixes: 415c401 ("wrap dynamic subscription functions")
|
||||
Change-Id: I66912530d8aa16bb4dde3d35611be1e6e4247b36
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/Session.cpp | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index ce0dec2..8258295 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -564,7 +564,7 @@ Subscription Session::onNotification(
|
||||
* @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
*
|
||||
- * @return A YangPushSubscription handle.
|
||||
+ * @return A DynamicSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushPeriodic(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
@@ -601,7 +601,7 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
* @param syncOnStart Whether to start with a notification of the current state.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
*
|
||||
- * @return A YangPushSubscription handle.
|
||||
+ * @return A DynamicSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushOnChange(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
@@ -650,7 +650,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
* @param stopTime Optional stop time ending the subscription.
|
||||
* @param startTime Optional start time of the subscription, used for replaying stored notifications.
|
||||
*
|
||||
- * @return A YangPushSubscription handle.
|
||||
+ * @return A DynamicSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::subscribeNotifications(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
From ee53d86929296c4824e9860f6425850f7d795302 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 15 Apr 2025 11:06:50 +0200
|
||||
Subject: [PATCH 19/20] add a session getter to dynamic subscriptions
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Sometimes it might be useful to get the session which created the
|
||||
subscription (e.g. to do some work in processEvent callback). There was
|
||||
no easy way to do it up until now because the session was not stored as
|
||||
sysrepo::Session.
|
||||
However, it seems quite easy to change that and add the getter.
|
||||
|
||||
Change-Id: Ia943a47debba6f84c3faad5ea48a69da68dac874
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Subscription.hpp | 3 ++-
|
||||
src/Session.cpp | 6 +++---
|
||||
src/Subscription.cpp | 17 +++++++++++------
|
||||
3 files changed, 16 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp
|
||||
index 7fdc08b..4c58e78 100644
|
||||
--- a/include/sysrepo-cpp/Subscription.hpp
|
||||
+++ b/include/sysrepo-cpp/Subscription.hpp
|
||||
@@ -303,6 +303,7 @@ public:
|
||||
DynamicSubscription& operator=(DynamicSubscription&&) noexcept;
|
||||
~DynamicSubscription();
|
||||
|
||||
+ sysrepo::Session getSession() const;
|
||||
int fd() const;
|
||||
uint64_t subscriptionId() const;
|
||||
std::optional<NotificationTimeStamp> replayStartTime() const;
|
||||
@@ -310,7 +311,7 @@ public:
|
||||
void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||
|
||||
private:
|
||||
- DynamicSubscription(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime = std::nullopt);
|
||||
+ DynamicSubscription(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime = std::nullopt);
|
||||
|
||||
struct Data;
|
||||
std::unique_ptr<Data> m_data;
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 8258295..7d44662 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -588,7 +588,7 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
&subId);
|
||||
throwIfError(res, "Couldn't create yang-push periodic subscription", m_sess.get());
|
||||
|
||||
- return {m_sess, fd, subId};
|
||||
+ return {*this, fd, subId};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -637,7 +637,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
&subId);
|
||||
throwIfError(res, "Couldn't create yang-push on-change subscription", m_sess.get());
|
||||
|
||||
- return {m_sess, fd, subId};
|
||||
+ return {*this, fd, subId};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -683,7 +683,7 @@ DynamicSubscription Session::subscribeNotifications(
|
||||
replayStart = toTimePoint(replayStartSpec);
|
||||
}
|
||||
|
||||
- return {m_sess, fd, subId, replayStart};
|
||||
+ return {*this, fd, subId, replayStart};
|
||||
}
|
||||
|
||||
/**
|
||||
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
|
||||
index ab27b16..6dccaee 100644
|
||||
--- a/src/Subscription.cpp
|
||||
+++ b/src/Subscription.cpp
|
||||
@@ -414,18 +414,18 @@ bool ChangeIterator::operator==(const ChangeIterator& other) const
|
||||
|
||||
|
||||
struct DynamicSubscription::Data {
|
||||
- std::shared_ptr<sr_session_ctx_s> sess;
|
||||
+ sysrepo::Session sess;
|
||||
int fd;
|
||||
uint64_t subId;
|
||||
std::optional<NotificationTimeStamp> m_replayStartTime;
|
||||
bool m_terminated;
|
||||
|
||||
- Data(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated);
|
||||
+ Data(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated);
|
||||
~Data();
|
||||
void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||
};
|
||||
|
||||
-DynamicSubscription::DynamicSubscription(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime)
|
||||
+DynamicSubscription::DynamicSubscription(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime)
|
||||
: m_data(std::make_unique<Data>(std::move(sess), fd, subId, replayStartTime, false))
|
||||
{
|
||||
}
|
||||
@@ -434,6 +434,12 @@ DynamicSubscription::DynamicSubscription(DynamicSubscription&&) noexcept = defau
|
||||
DynamicSubscription& DynamicSubscription::operator=(DynamicSubscription&&) noexcept = default;
|
||||
DynamicSubscription::~DynamicSubscription() = default;
|
||||
|
||||
+/** @brief Returns sysrepo Session associated with this subscription */
|
||||
+sysrepo::Session DynamicSubscription::getSession() const
|
||||
+{
|
||||
+ return m_data->sess;
|
||||
+}
|
||||
+
|
||||
/** @brief Returns the file descriptor associated with this subscription. */
|
||||
int DynamicSubscription::fd() const
|
||||
{
|
||||
@@ -472,9 +478,8 @@ void DynamicSubscription::processEvent(YangPushNotifCb cb) const
|
||||
{
|
||||
struct timespec timestamp;
|
||||
struct lyd_node* tree;
|
||||
- auto ctx = std::unique_ptr<const ly_ctx, std::function<void(const ly_ctx*)>>(sr_session_acquire_context(m_data->sess.get()), [&](const ly_ctx*) { sr_session_release_context(m_data->sess.get()); });
|
||||
|
||||
- auto err = srsn_read_notif(fd(), ctx.get(), ×tamp, &tree);
|
||||
+ auto err = srsn_read_notif(fd(), libyang::retrieveContext(m_data->sess.getContext()), ×tamp, &tree);
|
||||
throwIfError(err, "Couldn't read yang-push notification");
|
||||
|
||||
const auto wrappedNotification = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt;
|
||||
@@ -488,7 +493,7 @@ void DynamicSubscription::processEvent(YangPushNotifCb cb) const
|
||||
cb(wrappedNotification, toTimePoint(timestamp));
|
||||
}
|
||||
|
||||
-DynamicSubscription::Data::Data(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated)
|
||||
+DynamicSubscription::Data::Data(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated)
|
||||
: sess(std::move(sess))
|
||||
, fd(fd)
|
||||
, subId(subId)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
From 67eed65414cb9ccd031e16ec76149cc09c341186 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 15 Apr 2025 15:26:30 +0200
|
||||
Subject: [PATCH 20/20] wrap sr_nacm_get_recovery_user
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The function does not actually need a session, I am only adding it to
|
||||
Session so it is in the same place as other NACM user methods.
|
||||
|
||||
Change-Id: I3ee015b2bdc180ab1a9115ffc1a3a53f8e6f677c
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 10 ++++++++++
|
||||
2 files changed, 11 insertions(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 2112d21..2612e86 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
|
||||
void setNacmUser(const std::string& user);
|
||||
std::optional<std::string> getNacmUser() const;
|
||||
+ static std::string getNacmRecoveryUser();
|
||||
bool checkNacmOperation(const libyang::DataNode& node) const;
|
||||
[[nodiscard]] Subscription initNacm(
|
||||
SubscribeOptions opts = SubscribeOptions::Default,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 7d44662..e96e6ec 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -715,6 +715,16 @@ std::optional<std::string> Session::getNacmUser() const
|
||||
return username ? std::make_optional<std::string>(username) : std::nullopt;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Get the sysrepo NACM recovery user.
|
||||
+ *
|
||||
+ * wraps `sr_nacm_get_recovery_user`.
|
||||
+ */
|
||||
+std::string Session::getNacmRecoveryUser()
|
||||
+{
|
||||
+ return sr_nacm_get_recovery_user();
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`.
|
||||
* @return true if the current user is authorized to perform operation on given @p node.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
From 5059085e17b7967a8ed10f10c0cfbe1413a6955c Mon Sep 17 00:00:00 2001
|
||||
From: Liu Ying <victor.liu@nxp.com>
|
||||
Date: Mon, 4 Nov 2024 11:27:58 +0800
|
||||
Subject: [PATCH 28/35] drm: of: Get MEDIA_BUS_FMT_RGB101010_1X7X5_{JEIDA,
|
||||
SPWG} LVDS data mappings
|
||||
Organization: Wires
|
||||
|
||||
Add MEDIA_BUS_FMT_RGB101010_1X7X5_{JEIDA,SPWG} support in
|
||||
drm_of_lvds_get_data_mapping() function implementation so that function
|
||||
callers may get the two LVDS data mappings.
|
||||
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241104032806.611890-6-victor.liu@nxp.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
drivers/gpu/drm/drm_of.c | 6 ++++++
|
||||
1 file changed, 6 insertions(+)
|
||||
|
||||
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
|
||||
index 177b600895d3..89863a35c731 100644
|
||||
--- a/drivers/gpu/drm/drm_of.c
|
||||
+++ b/drivers/gpu/drm/drm_of.c
|
||||
@@ -410,7 +410,9 @@ EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order);
|
||||
* Return:
|
||||
* * MEDIA_BUS_FMT_RGB666_1X7X3_SPWG - data-mapping is "jeida-18"
|
||||
* * MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA - data-mapping is "jeida-24"
|
||||
+ * * MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA - data-mapping is "jeida-30"
|
||||
* * MEDIA_BUS_FMT_RGB888_1X7X4_SPWG - data-mapping is "vesa-24"
|
||||
+ * * MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG - data-mapping is "vesa-30"
|
||||
* * -EINVAL - the "data-mapping" property is unsupported
|
||||
* * -ENODEV - the "data-mapping" property is missing
|
||||
*/
|
||||
@@ -427,8 +429,12 @@ int drm_of_lvds_get_data_mapping(const struct device_node *port)
|
||||
return MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
|
||||
if (!strcmp(mapping, "jeida-24"))
|
||||
return MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
|
||||
+ if (!strcmp(mapping, "jeida-30"))
|
||||
+ return MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA;
|
||||
if (!strcmp(mapping, "vesa-24"))
|
||||
return MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
|
||||
+ if (!strcmp(mapping, "vesa-30"))
|
||||
+ return MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
From 0f483d196dab387a4ad31342ab1ad5d4c75d5bf8 Mon Sep 17 00:00:00 2001
|
||||
From: Liu Ying <victor.liu@nxp.com>
|
||||
Date: Mon, 4 Nov 2024 11:27:59 +0800
|
||||
Subject: [PATCH 29/35] drm: of: Add
|
||||
drm_of_lvds_get_dual_link_pixel_order_sink()
|
||||
Organization: Wires
|
||||
|
||||
drm_of_lvds_get_dual_link_pixel_order() gets LVDS dual-link source pixel
|
||||
order. Similar to it, add it's counterpart function
|
||||
drm_of_lvds_get_dual_link_pixel_order_sink() to get LVDS dual-link sink
|
||||
pixel order.
|
||||
|
||||
Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241104032806.611890-7-victor.liu@nxp.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
drivers/gpu/drm/drm_of.c | 76 ++++++++++++++++++++++++++++++++++------
|
||||
include/drm/drm_of.h | 9 +++++
|
||||
2 files changed, 74 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
|
||||
index 89863a35c731..5c2abc9eca9c 100644
|
||||
--- a/drivers/gpu/drm/drm_of.c
|
||||
+++ b/drivers/gpu/drm/drm_of.c
|
||||
@@ -341,8 +341,23 @@ static int drm_of_lvds_get_remote_pixels_type(
|
||||
return pixels_type;
|
||||
}
|
||||
|
||||
+static int __drm_of_lvds_get_dual_link_pixel_order(int p1_pt, int p2_pt)
|
||||
+{
|
||||
+ /*
|
||||
+ * A valid dual-lVDS bus is found when one port is marked with
|
||||
+ * "dual-lvds-even-pixels", and the other port is marked with
|
||||
+ * "dual-lvds-odd-pixels", bail out if the markers are not right.
|
||||
+ */
|
||||
+ if (p1_pt + p2_pt != DRM_OF_LVDS_EVEN + DRM_OF_LVDS_ODD)
|
||||
+ return -EINVAL;
|
||||
+
|
||||
+ return p1_pt == DRM_OF_LVDS_EVEN ?
|
||||
+ DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS :
|
||||
+ DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
- * drm_of_lvds_get_dual_link_pixel_order - Get LVDS dual-link pixel order
|
||||
+ * drm_of_lvds_get_dual_link_pixel_order - Get LVDS dual-link source pixel order
|
||||
* @port1: First DT port node of the Dual-link LVDS source
|
||||
* @port2: Second DT port node of the Dual-link LVDS source
|
||||
*
|
||||
@@ -387,19 +402,58 @@ int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
|
||||
if (remote_p2_pt < 0)
|
||||
return remote_p2_pt;
|
||||
|
||||
- /*
|
||||
- * A valid dual-lVDS bus is found when one remote port is marked with
|
||||
- * "dual-lvds-even-pixels", and the other remote port is marked with
|
||||
- * "dual-lvds-odd-pixels", bail out if the markers are not right.
|
||||
- */
|
||||
- if (remote_p1_pt + remote_p2_pt != DRM_OF_LVDS_EVEN + DRM_OF_LVDS_ODD)
|
||||
+ return __drm_of_lvds_get_dual_link_pixel_order(remote_p1_pt, remote_p2_pt);
|
||||
+}
|
||||
+EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order);
|
||||
+
|
||||
+/**
|
||||
+ * drm_of_lvds_get_dual_link_pixel_order_sink - Get LVDS dual-link sink pixel order
|
||||
+ * @port1: First DT port node of the Dual-link LVDS sink
|
||||
+ * @port2: Second DT port node of the Dual-link LVDS sink
|
||||
+ *
|
||||
+ * An LVDS dual-link connection is made of two links, with even pixels
|
||||
+ * transitting on one link, and odd pixels on the other link. This function
|
||||
+ * returns, for two ports of an LVDS dual-link sink, which port shall transmit
|
||||
+ * the even and odd pixels, based on the requirements of the sink.
|
||||
+ *
|
||||
+ * The pixel order is determined from the dual-lvds-even-pixels and
|
||||
+ * dual-lvds-odd-pixels properties in the sink's DT port nodes. If those
|
||||
+ * properties are not present, or if their usage is not valid, this function
|
||||
+ * returns -EINVAL.
|
||||
+ *
|
||||
+ * If either port is not connected, this function returns -EPIPE.
|
||||
+ *
|
||||
+ * @port1 and @port2 are typically DT sibling nodes, but may have different
|
||||
+ * parents when, for instance, two separate LVDS decoders receive the even and
|
||||
+ * odd pixels.
|
||||
+ *
|
||||
+ * Return:
|
||||
+ * * DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS - @port1 receives even pixels and @port2
|
||||
+ * receives odd pixels
|
||||
+ * * DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS - @port1 receives odd pixels and @port2
|
||||
+ * receives even pixels
|
||||
+ * * -EINVAL - @port1 or @port2 are NULL
|
||||
+ * * -EPIPE - when @port1 or @port2 are not connected
|
||||
+ */
|
||||
+int drm_of_lvds_get_dual_link_pixel_order_sink(struct device_node *port1,
|
||||
+ struct device_node *port2)
|
||||
+{
|
||||
+ int sink_p1_pt, sink_p2_pt;
|
||||
+
|
||||
+ if (!port1 || !port2)
|
||||
return -EINVAL;
|
||||
|
||||
- return remote_p1_pt == DRM_OF_LVDS_EVEN ?
|
||||
- DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS :
|
||||
- DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
|
||||
+ sink_p1_pt = drm_of_lvds_get_port_pixels_type(port1);
|
||||
+ if (!sink_p1_pt)
|
||||
+ return -EPIPE;
|
||||
+
|
||||
+ sink_p2_pt = drm_of_lvds_get_port_pixels_type(port2);
|
||||
+ if (!sink_p2_pt)
|
||||
+ return -EPIPE;
|
||||
+
|
||||
+ return __drm_of_lvds_get_dual_link_pixel_order(sink_p1_pt, sink_p2_pt);
|
||||
}
|
||||
-EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order);
|
||||
+EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order_sink);
|
||||
|
||||
/**
|
||||
* drm_of_lvds_get_data_mapping - Get LVDS data mapping
|
||||
diff --git a/include/drm/drm_of.h b/include/drm/drm_of.h
|
||||
index 02d1cdd7f798..7f0256dae3f1 100644
|
||||
--- a/include/drm/drm_of.h
|
||||
+++ b/include/drm/drm_of.h
|
||||
@@ -52,6 +52,8 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
|
||||
struct drm_bridge **bridge);
|
||||
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
|
||||
const struct device_node *port2);
|
||||
+int drm_of_lvds_get_dual_link_pixel_order_sink(struct device_node *port1,
|
||||
+ struct device_node *port2);
|
||||
int drm_of_lvds_get_data_mapping(const struct device_node *port);
|
||||
int drm_of_get_data_lanes_count(const struct device_node *endpoint,
|
||||
const unsigned int min, const unsigned int max);
|
||||
@@ -109,6 +111,13 @@ drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
+static inline int
|
||||
+drm_of_lvds_get_dual_link_pixel_order_sink(struct device_node *port1,
|
||||
+ struct device_node *port2)
|
||||
+{
|
||||
+ return -EINVAL;
|
||||
+}
|
||||
+
|
||||
static inline int
|
||||
drm_of_lvds_get_data_mapping(const struct device_node *port)
|
||||
{
|
||||
@@ -1,80 +0,0 @@
|
||||
From 901dd3cc12ff3e8ac7f361892ee04cfb44a4df76 Mon Sep 17 00:00:00 2001
|
||||
From: Liu Ying <victor.liu@nxp.com>
|
||||
Date: Mon, 4 Nov 2024 11:28:00 +0800
|
||||
Subject: [PATCH 30/35] dt-bindings: display: lvds-data-mapping: Add 30-bit RGB
|
||||
pixel data mappings
|
||||
Organization: Wires
|
||||
|
||||
Add "jeida-30" and "vesa-30" data mappings that are compatible with JEIDA
|
||||
and VESA respectively.
|
||||
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241104032806.611890-8-victor.liu@nxp.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
.../bindings/display/lvds-data-mapping.yaml | 31 +++++++++++++++++++
|
||||
1 file changed, 31 insertions(+)
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/display/lvds-data-mapping.yaml b/Documentation/devicetree/bindings/display/lvds-data-mapping.yaml
|
||||
index d68982fe2e9b..ab842594feb9 100644
|
||||
--- a/Documentation/devicetree/bindings/display/lvds-data-mapping.yaml
|
||||
+++ b/Documentation/devicetree/bindings/display/lvds-data-mapping.yaml
|
||||
@@ -26,12 +26,17 @@ description: |
|
||||
Device compatible with those specifications have been marketed under the
|
||||
FPD-Link and FlatLink brands.
|
||||
|
||||
+ This bindings also supports 30-bit data mapping compatible with JEIDA and
|
||||
+ VESA.
|
||||
+
|
||||
properties:
|
||||
data-mapping:
|
||||
enum:
|
||||
- jeida-18
|
||||
- jeida-24
|
||||
+ - jeida-30
|
||||
- vesa-24
|
||||
+ - vesa-30
|
||||
description: |
|
||||
The color signals mapping order.
|
||||
|
||||
@@ -60,6 +65,19 @@ properties:
|
||||
DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B7__><__B6__><__B5__><__B4__><
|
||||
DATA3 ><_CTL3_><__B1__><__B0__><__G1__><__G0__><__R1__><__R0__><
|
||||
|
||||
+ - "jeida-30" - 30-bit data mapping compatible with JEIDA and VESA. Data
|
||||
+ are transferred as follows on 5 LVDS lanes.
|
||||
+
|
||||
+ Slot 0 1 2 3 4 5 6
|
||||
+ ________________ _________________
|
||||
+ Clock \_______________________/
|
||||
+ ______ ______ ______ ______ ______ ______ ______
|
||||
+ DATA0 ><__G4__><__R9__><__R8__><__R7__><__R6__><__R5__><__R4__><
|
||||
+ DATA1 ><__B5__><__B4__><__G9__><__G8__><__G7__><__G6__><__G5__><
|
||||
+ DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B9__><__B8__><__B7__><__B6__><
|
||||
+ DATA3 ><_CTL3_><__B3__><__B2__><__G3__><__G2__><__R3__><__R2__><
|
||||
+ DATA4 ><_CTL3_><__B1__><__B0__><__G1__><__G0__><__R1__><__R0__><
|
||||
+
|
||||
- "vesa-24" - 24-bit data mapping compatible with the [VESA] specification.
|
||||
Data are transferred as follows on 4 LVDS lanes.
|
||||
|
||||
@@ -72,6 +90,19 @@ properties:
|
||||
DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B5__><__B4__><__B3__><__B2__><
|
||||
DATA3 ><_CTL3_><__B7__><__B6__><__G7__><__G6__><__R7__><__R6__><
|
||||
|
||||
+ - "vesa-30" - 30-bit data mapping compatible with VESA. Data are
|
||||
+ transferred as follows on 5 LVDS lanes.
|
||||
+
|
||||
+ Slot 0 1 2 3 4 5 6
|
||||
+ ________________ _________________
|
||||
+ Clock \_______________________/
|
||||
+ ______ ______ ______ ______ ______ ______ ______
|
||||
+ DATA0 ><__G0__><__R5__><__R4__><__R3__><__R2__><__R1__><__R0__><
|
||||
+ DATA1 ><__B1__><__B0__><__G5__><__G4__><__G3__><__G2__><__G1__><
|
||||
+ DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B5__><__B4__><__B3__><__B2__><
|
||||
+ DATA3 ><_CTL3_><__B7__><__B6__><__G7__><__G6__><__R7__><__R6__><
|
||||
+ DATA4 ><_CTL3_><__B9__><__B8__><__G9__><__G8__><__R9__><__R8__><
|
||||
+
|
||||
Control signals are mapped as follows.
|
||||
|
||||
CTL0: HSync
|
||||
-187
@@ -1,187 +0,0 @@
|
||||
From 34bc3735912c7af9b7c1f600bac60482d1879c3f Mon Sep 17 00:00:00 2001
|
||||
From: Liu Ying <victor.liu@nxp.com>
|
||||
Date: Mon, 4 Nov 2024 11:28:01 +0800
|
||||
Subject: [PATCH 31/35] dt-bindings: display: Document dual-link LVDS display
|
||||
common properties
|
||||
Organization: Wires
|
||||
|
||||
Dual-link LVDS displays receive odd pixels and even pixels separately from
|
||||
dual LVDS links. One link receives odd pixels and the other receives even
|
||||
pixels. Some of those displays may also use only one LVDS link to receive
|
||||
all pixels, being odd and even agnostic. Document common properties for
|
||||
those displays by extending LVDS display common properties defined in
|
||||
lvds.yaml.
|
||||
|
||||
Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241104032806.611890-9-victor.liu@nxp.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
.../bindings/display/lvds-dual-ports.yaml | 63 +++++++++++++++++++
|
||||
.../display/panel/advantech,idk-2121wr.yaml | 14 +----
|
||||
.../panel/panel-simple-lvds-dual-ports.yaml | 20 +-----
|
||||
3 files changed, 65 insertions(+), 32 deletions(-)
|
||||
create mode 100644 Documentation/devicetree/bindings/display/lvds-dual-ports.yaml
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/display/lvds-dual-ports.yaml b/Documentation/devicetree/bindings/display/lvds-dual-ports.yaml
|
||||
new file mode 100644
|
||||
index 000000000000..785701fe1590
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/display/lvds-dual-ports.yaml
|
||||
@@ -0,0 +1,63 @@
|
||||
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
+%YAML 1.2
|
||||
+---
|
||||
+$id: http://devicetree.org/schemas/display/lvds-dual-ports.yaml#
|
||||
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
+
|
||||
+title: Dual-link LVDS Display Common Properties
|
||||
+
|
||||
+maintainers:
|
||||
+ - Liu Ying <victor.liu@nxp.com>
|
||||
+
|
||||
+description: |
|
||||
+ Common properties for LVDS displays with dual LVDS links. Extend LVDS display
|
||||
+ common properties defined in lvds.yaml.
|
||||
+
|
||||
+ Dual-link LVDS displays receive odd pixels and even pixels separately from
|
||||
+ the dual LVDS links. One link receives odd pixels and the other receives
|
||||
+ even pixels. Some of those displays may also use only one LVDS link to
|
||||
+ receive all pixels, being odd and even agnostic.
|
||||
+
|
||||
+allOf:
|
||||
+ - $ref: lvds.yaml#
|
||||
+
|
||||
+properties:
|
||||
+ ports:
|
||||
+ $ref: /schemas/graph.yaml#/properties/ports
|
||||
+
|
||||
+ patternProperties:
|
||||
+ '^port@[01]$':
|
||||
+ $ref: /schemas/graph.yaml#/$defs/port-base
|
||||
+ unevaluatedProperties: false
|
||||
+ description: |
|
||||
+ port@0 represents the first LVDS input link.
|
||||
+ port@1 represents the second LVDS input link.
|
||||
+
|
||||
+ properties:
|
||||
+ dual-lvds-odd-pixels:
|
||||
+ type: boolean
|
||||
+ description: LVDS input link for odd pixels
|
||||
+
|
||||
+ dual-lvds-even-pixels:
|
||||
+ type: boolean
|
||||
+ description: LVDS input link for even pixels
|
||||
+
|
||||
+ oneOf:
|
||||
+ - required: [dual-lvds-odd-pixels]
|
||||
+ - required: [dual-lvds-even-pixels]
|
||||
+ - properties:
|
||||
+ dual-lvds-odd-pixels: false
|
||||
+ dual-lvds-even-pixels: false
|
||||
+
|
||||
+ anyOf:
|
||||
+ - required:
|
||||
+ - port@0
|
||||
+ - required:
|
||||
+ - port@1
|
||||
+
|
||||
+required:
|
||||
+ - ports
|
||||
+
|
||||
+additionalProperties: true
|
||||
+
|
||||
+...
|
||||
diff --git a/Documentation/devicetree/bindings/display/panel/advantech,idk-2121wr.yaml b/Documentation/devicetree/bindings/display/panel/advantech,idk-2121wr.yaml
|
||||
index 2e8dbdb5a3d5..05ca3b2385f8 100644
|
||||
--- a/Documentation/devicetree/bindings/display/panel/advantech,idk-2121wr.yaml
|
||||
+++ b/Documentation/devicetree/bindings/display/panel/advantech,idk-2121wr.yaml
|
||||
@@ -20,6 +20,7 @@ description: |
|
||||
dual-lvds-odd-pixels or dual-lvds-even-pixels).
|
||||
|
||||
allOf:
|
||||
+ - $ref: /schemas/display/lvds-dual-ports.yaml#
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
@@ -44,22 +45,10 @@ properties:
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
- $ref: /schemas/graph.yaml#/$defs/port-base
|
||||
- unevaluatedProperties: false
|
||||
- description: The sink for odd pixels.
|
||||
- properties:
|
||||
- dual-lvds-odd-pixels: true
|
||||
-
|
||||
required:
|
||||
- dual-lvds-odd-pixels
|
||||
|
||||
port@1:
|
||||
- $ref: /schemas/graph.yaml#/$defs/port-base
|
||||
- unevaluatedProperties: false
|
||||
- description: The sink for even pixels.
|
||||
- properties:
|
||||
- dual-lvds-even-pixels: true
|
||||
-
|
||||
required:
|
||||
- dual-lvds-even-pixels
|
||||
|
||||
@@ -75,7 +64,6 @@ required:
|
||||
- height-mm
|
||||
- data-mapping
|
||||
- panel-timing
|
||||
- - ports
|
||||
|
||||
examples:
|
||||
- |+
|
||||
diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple-lvds-dual-ports.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple-lvds-dual-ports.yaml
|
||||
index 10ed4b57232b..e80fc7006984 100644
|
||||
--- a/Documentation/devicetree/bindings/display/panel/panel-simple-lvds-dual-ports.yaml
|
||||
+++ b/Documentation/devicetree/bindings/display/panel/panel-simple-lvds-dual-ports.yaml
|
||||
@@ -22,6 +22,7 @@ description: |
|
||||
If the panel is more advanced a dedicated binding file is required.
|
||||
|
||||
allOf:
|
||||
+ - $ref: /schemas/display/lvds-dual-ports.yaml#
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
@@ -55,28 +56,10 @@ properties:
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
- $ref: /schemas/graph.yaml#/$defs/port-base
|
||||
- unevaluatedProperties: false
|
||||
- description: The first sink port.
|
||||
-
|
||||
- properties:
|
||||
- dual-lvds-odd-pixels:
|
||||
- type: boolean
|
||||
- description: The first sink port for odd pixels.
|
||||
-
|
||||
required:
|
||||
- dual-lvds-odd-pixels
|
||||
|
||||
port@1:
|
||||
- $ref: /schemas/graph.yaml#/$defs/port-base
|
||||
- unevaluatedProperties: false
|
||||
- description: The second sink port.
|
||||
-
|
||||
- properties:
|
||||
- dual-lvds-even-pixels:
|
||||
- type: boolean
|
||||
- description: The second sink port for even pixels.
|
||||
-
|
||||
required:
|
||||
- dual-lvds-even-pixels
|
||||
|
||||
@@ -88,7 +71,6 @@ unevaluatedProperties: false
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- - ports
|
||||
- power-supply
|
||||
|
||||
examples:
|
||||
-278
@@ -1,278 +0,0 @@
|
||||
From 40d2762755d7f6b404edb1e2baa28ef62e8b4973 Mon Sep 17 00:00:00 2001
|
||||
From: Liu Ying <victor.liu@nxp.com>
|
||||
Date: Mon, 4 Nov 2024 11:28:02 +0800
|
||||
Subject: [PATCH 32/35] dt-bindings: display: bridge: Add ITE IT6263 LVDS to
|
||||
HDMI converter
|
||||
Organization: Wires
|
||||
|
||||
Document ITE IT6263 LVDS to HDMI converter.
|
||||
|
||||
Product link:
|
||||
https://www.ite.com.tw/en/product/cate1/IT6263
|
||||
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Reviewed-by: Biju Das <biju.das.jz@bp.renesas.com>
|
||||
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241104032806.611890-10-victor.liu@nxp.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
.../bindings/display/bridge/ite,it6263.yaml | 250 ++++++++++++++++++
|
||||
1 file changed, 250 insertions(+)
|
||||
create mode 100644 Documentation/devicetree/bindings/display/bridge/ite,it6263.yaml
|
||||
|
||||
diff --git a/Documentation/devicetree/bindings/display/bridge/ite,it6263.yaml b/Documentation/devicetree/bindings/display/bridge/ite,it6263.yaml
|
||||
new file mode 100644
|
||||
index 000000000000..0a10e10d80ff
|
||||
--- /dev/null
|
||||
+++ b/Documentation/devicetree/bindings/display/bridge/ite,it6263.yaml
|
||||
@@ -0,0 +1,250 @@
|
||||
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
+%YAML 1.2
|
||||
+---
|
||||
+$id: http://devicetree.org/schemas/display/bridge/ite,it6263.yaml#
|
||||
+$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
+
|
||||
+title: ITE IT6263 LVDS to HDMI converter
|
||||
+
|
||||
+maintainers:
|
||||
+ - Liu Ying <victor.liu@nxp.com>
|
||||
+
|
||||
+description: |
|
||||
+ The IT6263 is a high-performance single-chip De-SSC(De-Spread Spectrum) LVDS
|
||||
+ to HDMI converter. Combined with LVDS receiver and HDMI 1.4a transmitter,
|
||||
+ the IT6263 supports LVDS input and HDMI 1.4 output by conversion function.
|
||||
+ The built-in LVDS receiver can support single-link and dual-link LVDS inputs,
|
||||
+ and the built-in HDMI transmitter is fully compliant with HDMI 1.4a/3D, HDCP
|
||||
+ 1.2 and backward compatible with DVI 1.0 specification.
|
||||
+
|
||||
+ The IT6263 also encodes and transmits up to 8 channels of I2S digital audio,
|
||||
+ with sampling rate up to 192KHz and sample size up to 24 bits. In addition,
|
||||
+ an S/PDIF input port takes in compressed audio of up to 192KHz frame rate.
|
||||
+
|
||||
+ The newly supported High-Bit Rate(HBR) audio by HDMI specifications v1.3 is
|
||||
+ provided by the IT6263 in two interfaces: the four I2S input ports or the
|
||||
+ S/PDIF input port. With both interfaces the highest possible HBR frame rate
|
||||
+ is supported at up to 768KHz.
|
||||
+
|
||||
+allOf:
|
||||
+ - $ref: /schemas/display/lvds-dual-ports.yaml#
|
||||
+
|
||||
+properties:
|
||||
+ compatible:
|
||||
+ const: ite,it6263
|
||||
+
|
||||
+ reg:
|
||||
+ maxItems: 1
|
||||
+
|
||||
+ clocks:
|
||||
+ maxItems: 1
|
||||
+ description: audio master clock
|
||||
+
|
||||
+ clock-names:
|
||||
+ const: mclk
|
||||
+
|
||||
+ data-mapping:
|
||||
+ enum:
|
||||
+ - jeida-18
|
||||
+ - jeida-24
|
||||
+ - jeida-30
|
||||
+ - vesa-24
|
||||
+ - vesa-30
|
||||
+
|
||||
+ reset-gpios:
|
||||
+ maxItems: 1
|
||||
+
|
||||
+ ivdd-supply:
|
||||
+ description: 1.8V digital logic power
|
||||
+
|
||||
+ ovdd-supply:
|
||||
+ description: 3.3V I/O pin power
|
||||
+
|
||||
+ txavcc18-supply:
|
||||
+ description: 1.8V HDMI analog frontend power
|
||||
+
|
||||
+ txavcc33-supply:
|
||||
+ description: 3.3V HDMI analog frontend power
|
||||
+
|
||||
+ pvcc1-supply:
|
||||
+ description: 1.8V HDMI frontend core PLL power
|
||||
+
|
||||
+ pvcc2-supply:
|
||||
+ description: 1.8V HDMI frontend filter PLL power
|
||||
+
|
||||
+ avcc-supply:
|
||||
+ description: 3.3V LVDS frontend power
|
||||
+
|
||||
+ anvdd-supply:
|
||||
+ description: 1.8V LVDS frontend analog power
|
||||
+
|
||||
+ apvdd-supply:
|
||||
+ description: 1.8V LVDS frontend PLL power
|
||||
+
|
||||
+ "#sound-dai-cells":
|
||||
+ const: 0
|
||||
+
|
||||
+ ite,i2s-audio-fifo-sources:
|
||||
+ $ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
+ minItems: 1
|
||||
+ maxItems: 4
|
||||
+ items:
|
||||
+ enum: [0, 1, 2, 3]
|
||||
+ description:
|
||||
+ Each array element indicates the pin number of an I2S serial data input
|
||||
+ line which is connected to an audio FIFO, from audio FIFO0 to FIFO3.
|
||||
+
|
||||
+ ite,rl-channel-swap-audio-sources:
|
||||
+ $ref: /schemas/types.yaml#/definitions/uint32-array
|
||||
+ minItems: 1
|
||||
+ maxItems: 4
|
||||
+ uniqueItems: true
|
||||
+ items:
|
||||
+ enum: [0, 1, 2, 3]
|
||||
+ description:
|
||||
+ Each array element indicates an audio source whose right channel and left
|
||||
+ channel are swapped by this converter. For I2S, the element is the pin
|
||||
+ number of an I2S serial data input line. For S/PDIF, the element is always
|
||||
+ 0.
|
||||
+
|
||||
+ ports:
|
||||
+ $ref: /schemas/graph.yaml#/properties/ports
|
||||
+
|
||||
+ properties:
|
||||
+ port@0: true
|
||||
+
|
||||
+ port@1:
|
||||
+ oneOf:
|
||||
+ - required: [dual-lvds-odd-pixels]
|
||||
+ - required: [dual-lvds-even-pixels]
|
||||
+
|
||||
+ port@2:
|
||||
+ $ref: /schemas/graph.yaml#/properties/port
|
||||
+ description: video port for the HDMI output
|
||||
+
|
||||
+ port@3:
|
||||
+ $ref: /schemas/graph.yaml#/properties/port
|
||||
+ description: sound input port
|
||||
+
|
||||
+ required:
|
||||
+ - port@0
|
||||
+ - port@2
|
||||
+
|
||||
+required:
|
||||
+ - compatible
|
||||
+ - reg
|
||||
+ - data-mapping
|
||||
+ - ivdd-supply
|
||||
+ - ovdd-supply
|
||||
+ - txavcc18-supply
|
||||
+ - txavcc33-supply
|
||||
+ - pvcc1-supply
|
||||
+ - pvcc2-supply
|
||||
+ - avcc-supply
|
||||
+ - anvdd-supply
|
||||
+ - apvdd-supply
|
||||
+
|
||||
+unevaluatedProperties: false
|
||||
+
|
||||
+examples:
|
||||
+ - |
|
||||
+ /* single-link LVDS input */
|
||||
+ #include <dt-bindings/gpio/gpio.h>
|
||||
+
|
||||
+ i2c {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ hdmi@4c {
|
||||
+ compatible = "ite,it6263";
|
||||
+ reg = <0x4c>;
|
||||
+ data-mapping = "jeida-24";
|
||||
+ reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
|
||||
+ ivdd-supply = <®_buck5>;
|
||||
+ ovdd-supply = <®_vext_3v3>;
|
||||
+ txavcc18-supply = <®_buck5>;
|
||||
+ txavcc33-supply = <®_vext_3v3>;
|
||||
+ pvcc1-supply = <®_buck5>;
|
||||
+ pvcc2-supply = <®_buck5>;
|
||||
+ avcc-supply = <®_vext_3v3>;
|
||||
+ anvdd-supply = <®_buck5>;
|
||||
+ apvdd-supply = <®_buck5>;
|
||||
+
|
||||
+ ports {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ port@0 {
|
||||
+ reg = <0>;
|
||||
+
|
||||
+ it6263_lvds_link1: endpoint {
|
||||
+ remote-endpoint = <&ldb_lvds_ch0>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ port@2 {
|
||||
+ reg = <2>;
|
||||
+
|
||||
+ it6263_out: endpoint {
|
||||
+ remote-endpoint = <&hdmi_in>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ - |
|
||||
+ /* dual-link LVDS input */
|
||||
+ #include <dt-bindings/gpio/gpio.h>
|
||||
+
|
||||
+ i2c {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ hdmi@4c {
|
||||
+ compatible = "ite,it6263";
|
||||
+ reg = <0x4c>;
|
||||
+ data-mapping = "jeida-24";
|
||||
+ reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
|
||||
+ ivdd-supply = <®_buck5>;
|
||||
+ ovdd-supply = <®_vext_3v3>;
|
||||
+ txavcc18-supply = <®_buck5>;
|
||||
+ txavcc33-supply = <®_vext_3v3>;
|
||||
+ pvcc1-supply = <®_buck5>;
|
||||
+ pvcc2-supply = <®_buck5>;
|
||||
+ avcc-supply = <®_vext_3v3>;
|
||||
+ anvdd-supply = <®_buck5>;
|
||||
+ apvdd-supply = <®_buck5>;
|
||||
+
|
||||
+ ports {
|
||||
+ #address-cells = <1>;
|
||||
+ #size-cells = <0>;
|
||||
+
|
||||
+ port@0 {
|
||||
+ reg = <0>;
|
||||
+ dual-lvds-odd-pixels;
|
||||
+
|
||||
+ it6263_lvds_link1_dual: endpoint {
|
||||
+ remote-endpoint = <&ldb_lvds_ch0>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ port@1 {
|
||||
+ reg = <1>;
|
||||
+ dual-lvds-even-pixels;
|
||||
+
|
||||
+ it6263_lvds_link2_dual: endpoint {
|
||||
+ remote-endpoint = <&ldb_lvds_ch1>;
|
||||
+ };
|
||||
+ };
|
||||
+
|
||||
+ port@2 {
|
||||
+ reg = <2>;
|
||||
+
|
||||
+ it6263_out_dual: endpoint {
|
||||
+ remote-endpoint = <&hdmi_in>;
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
+ };
|
||||
@@ -1,966 +0,0 @@
|
||||
From aa3f6b62a24979e579323e77c8900064e7c725f1 Mon Sep 17 00:00:00 2001
|
||||
From: Liu Ying <victor.liu@nxp.com>
|
||||
Date: Mon, 4 Nov 2024 11:28:03 +0800
|
||||
Subject: [PATCH 33/35] drm/bridge: Add ITE IT6263 LVDS to HDMI converter
|
||||
Organization: Wires
|
||||
|
||||
Add basic HDMI video output support. Currently, only RGB888 output
|
||||
pixel format is supported. At the LVDS input side, the driver
|
||||
supports single LVDS link and dual LVDS links with "jeida-24" LVDS
|
||||
mapping.
|
||||
|
||||
Product link:
|
||||
https://www.ite.com.tw/en/product/cate1/IT6263
|
||||
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Reviewed-by: Biju Das <biju.das.jz@bp.renesas.com>
|
||||
Acked-by: Maxime Ripard <mripard@kernel.org>
|
||||
Tested-by: Biju Das <biju.das.jz@bp.renesas.com>
|
||||
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241104032806.611890-11-victor.liu@nxp.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
drivers/gpu/drm/bridge/Kconfig | 11 +
|
||||
drivers/gpu/drm/bridge/Makefile | 1 +
|
||||
drivers/gpu/drm/bridge/ite-it6263.c | 898 ++++++++++++++++++++++++++++
|
||||
3 files changed, 910 insertions(+)
|
||||
create mode 100644 drivers/gpu/drm/bridge/ite-it6263.c
|
||||
|
||||
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
|
||||
index 3eb955333c80..676286542706 100644
|
||||
--- a/drivers/gpu/drm/bridge/Kconfig
|
||||
+++ b/drivers/gpu/drm/bridge/Kconfig
|
||||
@@ -90,6 +90,17 @@ config DRM_FSL_LDB
|
||||
help
|
||||
Support for i.MX8MP DPI-to-LVDS on-SoC encoder.
|
||||
|
||||
+config DRM_ITE_IT6263
|
||||
+ tristate "ITE IT6263 LVDS/HDMI bridge"
|
||||
+ depends on OF
|
||||
+ select DRM_DISPLAY_HDMI_STATE_HELPER
|
||||
+ select DRM_DISPLAY_HELPER
|
||||
+ select DRM_BRIDGE_CONNECTOR
|
||||
+ select DRM_KMS_HELPER
|
||||
+ select REGMAP_I2C
|
||||
+ help
|
||||
+ ITE IT6263 LVDS to HDMI bridge chip driver.
|
||||
+
|
||||
config DRM_ITE_IT6505
|
||||
tristate "ITE IT6505 DisplayPort bridge"
|
||||
depends on OF
|
||||
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
|
||||
index 7df87b582dca..f3776dd473fd 100644
|
||||
--- a/drivers/gpu/drm/bridge/Makefile
|
||||
+++ b/drivers/gpu/drm/bridge/Makefile
|
||||
@@ -6,6 +6,7 @@ obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
|
||||
obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
|
||||
obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
|
||||
obj-$(CONFIG_DRM_FSL_LDB) += fsl-ldb.o
|
||||
+obj-$(CONFIG_DRM_ITE_IT6263) += ite-it6263.o
|
||||
obj-$(CONFIG_DRM_ITE_IT6505) += ite-it6505.o
|
||||
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
|
||||
obj-$(CONFIG_DRM_LONTIUM_LT9211) += lontium-lt9211.o
|
||||
diff --git a/drivers/gpu/drm/bridge/ite-it6263.c b/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
new file mode 100644
|
||||
index 000000000000..cbabd4e20d3e
|
||||
--- /dev/null
|
||||
+++ b/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
@@ -0,0 +1,898 @@
|
||||
+// SPDX-License-Identifier: GPL-2.0
|
||||
+/*
|
||||
+ * Copyright 2024 NXP
|
||||
+ */
|
||||
+
|
||||
+#include <linux/bitfield.h>
|
||||
+#include <linux/bits.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/gpio/consumer.h>
|
||||
+#include <linux/hdmi.h>
|
||||
+#include <linux/i2c.h>
|
||||
+#include <linux/media-bus-format.h>
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/of.h>
|
||||
+#include <linux/regmap.h>
|
||||
+#include <linux/regulator/consumer.h>
|
||||
+
|
||||
+#include <drm/display/drm_hdmi_helper.h>
|
||||
+#include <drm/display/drm_hdmi_state_helper.h>
|
||||
+#include <drm/drm_atomic.h>
|
||||
+#include <drm/drm_atomic_helper.h>
|
||||
+#include <drm/drm_atomic_state_helper.h>
|
||||
+#include <drm/drm_bridge.h>
|
||||
+#include <drm/drm_bridge_connector.h>
|
||||
+#include <drm/drm_connector.h>
|
||||
+#include <drm/drm_crtc.h>
|
||||
+#include <drm/drm_edid.h>
|
||||
+#include <drm/drm_of.h>
|
||||
+#include <drm/drm_probe_helper.h>
|
||||
+
|
||||
+/* -----------------------------------------------------------------------------
|
||||
+ * LVDS registers
|
||||
+ */
|
||||
+
|
||||
+/* LVDS software reset registers */
|
||||
+#define LVDS_REG_05 0x05
|
||||
+#define REG_SOFT_P_RST BIT(1)
|
||||
+
|
||||
+/* LVDS system configuration registers */
|
||||
+/* 0x0b */
|
||||
+#define LVDS_REG_0B 0x0b
|
||||
+#define REG_SSC_PCLK_RF BIT(0)
|
||||
+#define REG_LVDS_IN_SWAP BIT(1)
|
||||
+
|
||||
+/* LVDS test pattern gen control registers */
|
||||
+/* 0x2c */
|
||||
+#define LVDS_REG_2C 0x2c
|
||||
+#define REG_COL_DEP GENMASK(1, 0)
|
||||
+#define BIT8 FIELD_PREP(REG_COL_DEP, 1)
|
||||
+#define OUT_MAP BIT(4)
|
||||
+#define JEIDA 0
|
||||
+#define REG_DESSC_ENB BIT(6)
|
||||
+#define DMODE BIT(7)
|
||||
+#define DISO BIT(7)
|
||||
+#define SISO 0
|
||||
+
|
||||
+#define LVDS_REG_3C 0x3c
|
||||
+#define LVDS_REG_3F 0x3f
|
||||
+#define LVDS_REG_47 0x47
|
||||
+#define LVDS_REG_48 0x48
|
||||
+#define LVDS_REG_4F 0x4f
|
||||
+#define LVDS_REG_52 0x52
|
||||
+
|
||||
+/* -----------------------------------------------------------------------------
|
||||
+ * HDMI registers are separated into three banks:
|
||||
+ * 1) HDMI register common bank: 0x00 ~ 0x2f
|
||||
+ */
|
||||
+
|
||||
+/* HDMI genernal registers */
|
||||
+#define HDMI_REG_SW_RST 0x04
|
||||
+#define SOFTREF_RST BIT(5)
|
||||
+#define SOFTA_RST BIT(4)
|
||||
+#define SOFTV_RST BIT(3)
|
||||
+#define AUD_RST BIT(2)
|
||||
+#define HDCP_RST BIT(0)
|
||||
+#define HDMI_RST_ALL (SOFTREF_RST | SOFTA_RST | SOFTV_RST | \
|
||||
+ AUD_RST | HDCP_RST)
|
||||
+
|
||||
+#define HDMI_REG_SYS_STATUS 0x0e
|
||||
+#define HPDETECT BIT(6)
|
||||
+#define TXVIDSTABLE BIT(4)
|
||||
+
|
||||
+#define HDMI_REG_BANK_CTRL 0x0f
|
||||
+#define REG_BANK_SEL BIT(0)
|
||||
+
|
||||
+/* HDMI System DDC control registers */
|
||||
+#define HDMI_REG_DDC_MASTER_CTRL 0x10
|
||||
+#define MASTER_SEL_HOST BIT(0)
|
||||
+
|
||||
+#define HDMI_REG_DDC_HEADER 0x11
|
||||
+
|
||||
+#define HDMI_REG_DDC_REQOFF 0x12
|
||||
+#define HDMI_REG_DDC_REQCOUNT 0x13
|
||||
+#define HDMI_REG_DDC_EDIDSEG 0x14
|
||||
+
|
||||
+#define HDMI_REG_DDC_CMD 0x15
|
||||
+#define DDC_CMD_EDID_READ 0x3
|
||||
+#define DDC_CMD_FIFO_CLR 0x9
|
||||
+
|
||||
+#define HDMI_REG_DDC_STATUS 0x16
|
||||
+#define DDC_DONE BIT(7)
|
||||
+#define DDC_NOACK BIT(5)
|
||||
+#define DDC_WAITBUS BIT(4)
|
||||
+#define DDC_ARBILOSE BIT(3)
|
||||
+#define DDC_ERROR (DDC_NOACK | DDC_WAITBUS | DDC_ARBILOSE)
|
||||
+
|
||||
+#define HDMI_DDC_FIFO_BYTES 32
|
||||
+#define HDMI_REG_DDC_READFIFO 0x17
|
||||
+#define HDMI_REG_LVDS_PORT 0x1d /* LVDS input control I2C addr */
|
||||
+#define HDMI_REG_LVDS_PORT_EN 0x1e
|
||||
+#define LVDS_INPUT_CTRL_I2C_ADDR 0x33
|
||||
+
|
||||
+/* -----------------------------------------------------------------------------
|
||||
+ * 2) HDMI register bank0: 0x30 ~ 0xff
|
||||
+ */
|
||||
+
|
||||
+/* HDMI AFE registers */
|
||||
+#define HDMI_REG_AFE_DRV_CTRL 0x61
|
||||
+#define AFE_DRV_PWD BIT(5)
|
||||
+#define AFE_DRV_RST BIT(4)
|
||||
+
|
||||
+#define HDMI_REG_AFE_XP_CTRL 0x62
|
||||
+#define AFE_XP_GAINBIT BIT(7)
|
||||
+#define AFE_XP_ER0 BIT(4)
|
||||
+#define AFE_XP_RESETB BIT(3)
|
||||
+
|
||||
+#define HDMI_REG_AFE_ISW_CTRL 0x63
|
||||
+
|
||||
+#define HDMI_REG_AFE_IP_CTRL 0x64
|
||||
+#define AFE_IP_GAINBIT BIT(7)
|
||||
+#define AFE_IP_ER0 BIT(3)
|
||||
+#define AFE_IP_RESETB BIT(2)
|
||||
+
|
||||
+/* HDMI input data format registers */
|
||||
+#define HDMI_REG_INPUT_MODE 0x70
|
||||
+#define IN_RGB 0x00
|
||||
+
|
||||
+/* HDMI general control registers */
|
||||
+#define HDMI_REG_HDMI_MODE 0xc0
|
||||
+#define TX_HDMI_MODE BIT(0)
|
||||
+
|
||||
+#define HDMI_REG_GCP 0xc1
|
||||
+#define AVMUTE BIT(0)
|
||||
+#define HDMI_COLOR_DEPTH GENMASK(6, 4)
|
||||
+#define HDMI_COLOR_DEPTH_24 FIELD_PREP(HDMI_COLOR_DEPTH, 4)
|
||||
+
|
||||
+#define HDMI_REG_PKT_GENERAL_CTRL 0xc6
|
||||
+#define HDMI_REG_AVI_INFOFRM_CTRL 0xcd
|
||||
+#define ENABLE_PKT BIT(0)
|
||||
+#define REPEAT_PKT BIT(1)
|
||||
+
|
||||
+/* -----------------------------------------------------------------------------
|
||||
+ * 3) HDMI register bank1: 0x130 ~ 0x1ff (HDMI packet registers)
|
||||
+ */
|
||||
+
|
||||
+/* AVI packet registers */
|
||||
+#define HDMI_REG_AVI_DB1 0x158
|
||||
+#define HDMI_REG_AVI_DB2 0x159
|
||||
+#define HDMI_REG_AVI_DB3 0x15a
|
||||
+#define HDMI_REG_AVI_DB4 0x15b
|
||||
+#define HDMI_REG_AVI_DB5 0x15c
|
||||
+#define HDMI_REG_AVI_CSUM 0x15d
|
||||
+#define HDMI_REG_AVI_DB6 0x15e
|
||||
+#define HDMI_REG_AVI_DB7 0x15f
|
||||
+#define HDMI_REG_AVI_DB8 0x160
|
||||
+#define HDMI_REG_AVI_DB9 0x161
|
||||
+#define HDMI_REG_AVI_DB10 0x162
|
||||
+#define HDMI_REG_AVI_DB11 0x163
|
||||
+#define HDMI_REG_AVI_DB12 0x164
|
||||
+#define HDMI_REG_AVI_DB13 0x165
|
||||
+
|
||||
+#define HDMI_AVI_DB_CHUNK1_SIZE (HDMI_REG_AVI_DB5 - HDMI_REG_AVI_DB1 + 1)
|
||||
+#define HDMI_AVI_DB_CHUNK2_SIZE (HDMI_REG_AVI_DB13 - HDMI_REG_AVI_DB6 + 1)
|
||||
+
|
||||
+/* IT6263 data sheet Rev0.8: LVDS RX supports input clock rate up to 150MHz. */
|
||||
+#define MAX_PIXEL_CLOCK_KHZ 150000
|
||||
+
|
||||
+/* IT6263 programming guide Ver0.90: PCLK_HIGH for TMDS clock over 80MHz. */
|
||||
+#define HIGH_PIXEL_CLOCK_KHZ 80000
|
||||
+
|
||||
+/*
|
||||
+ * IT6263 data sheet Rev0.8: HDMI TX supports link speeds of up to 2.25Gbps
|
||||
+ * (link clock rate of 225MHz).
|
||||
+ */
|
||||
+#define MAX_HDMI_TMDS_CHAR_RATE_HZ 225000000
|
||||
+
|
||||
+struct it6263 {
|
||||
+ struct device *dev;
|
||||
+ struct i2c_client *hdmi_i2c;
|
||||
+ struct i2c_client *lvds_i2c;
|
||||
+ struct regmap *hdmi_regmap;
|
||||
+ struct regmap *lvds_regmap;
|
||||
+ struct drm_bridge bridge;
|
||||
+ struct drm_bridge *next_bridge;
|
||||
+ int lvds_data_mapping;
|
||||
+ bool lvds_dual_link;
|
||||
+ bool lvds_link12_swap;
|
||||
+};
|
||||
+
|
||||
+static inline struct it6263 *bridge_to_it6263(struct drm_bridge *bridge)
|
||||
+{
|
||||
+ return container_of(bridge, struct it6263, bridge);
|
||||
+}
|
||||
+
|
||||
+static bool it6263_hdmi_writeable_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ switch (reg) {
|
||||
+ case HDMI_REG_SW_RST:
|
||||
+ case HDMI_REG_BANK_CTRL:
|
||||
+ case HDMI_REG_DDC_MASTER_CTRL:
|
||||
+ case HDMI_REG_DDC_HEADER:
|
||||
+ case HDMI_REG_DDC_REQOFF:
|
||||
+ case HDMI_REG_DDC_REQCOUNT:
|
||||
+ case HDMI_REG_DDC_EDIDSEG:
|
||||
+ case HDMI_REG_DDC_CMD:
|
||||
+ case HDMI_REG_LVDS_PORT:
|
||||
+ case HDMI_REG_LVDS_PORT_EN:
|
||||
+ case HDMI_REG_AFE_DRV_CTRL:
|
||||
+ case HDMI_REG_AFE_XP_CTRL:
|
||||
+ case HDMI_REG_AFE_ISW_CTRL:
|
||||
+ case HDMI_REG_AFE_IP_CTRL:
|
||||
+ case HDMI_REG_INPUT_MODE:
|
||||
+ case HDMI_REG_HDMI_MODE:
|
||||
+ case HDMI_REG_GCP:
|
||||
+ case HDMI_REG_PKT_GENERAL_CTRL:
|
||||
+ case HDMI_REG_AVI_INFOFRM_CTRL:
|
||||
+ case HDMI_REG_AVI_DB1:
|
||||
+ case HDMI_REG_AVI_DB2:
|
||||
+ case HDMI_REG_AVI_DB3:
|
||||
+ case HDMI_REG_AVI_DB4:
|
||||
+ case HDMI_REG_AVI_DB5:
|
||||
+ case HDMI_REG_AVI_CSUM:
|
||||
+ case HDMI_REG_AVI_DB6:
|
||||
+ case HDMI_REG_AVI_DB7:
|
||||
+ case HDMI_REG_AVI_DB8:
|
||||
+ case HDMI_REG_AVI_DB9:
|
||||
+ case HDMI_REG_AVI_DB10:
|
||||
+ case HDMI_REG_AVI_DB11:
|
||||
+ case HDMI_REG_AVI_DB12:
|
||||
+ case HDMI_REG_AVI_DB13:
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static bool it6263_hdmi_readable_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ if (it6263_hdmi_writeable_reg(dev, reg))
|
||||
+ return true;
|
||||
+
|
||||
+ switch (reg) {
|
||||
+ case HDMI_REG_SYS_STATUS:
|
||||
+ case HDMI_REG_DDC_STATUS:
|
||||
+ case HDMI_REG_DDC_READFIFO:
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static bool it6263_hdmi_volatile_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ switch (reg) {
|
||||
+ case HDMI_REG_SW_RST:
|
||||
+ case HDMI_REG_SYS_STATUS:
|
||||
+ case HDMI_REG_DDC_STATUS:
|
||||
+ case HDMI_REG_DDC_READFIFO:
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static const struct regmap_range_cfg it6263_hdmi_range_cfg = {
|
||||
+ .range_min = 0x00,
|
||||
+ .range_max = HDMI_REG_AVI_DB13,
|
||||
+ .selector_reg = HDMI_REG_BANK_CTRL,
|
||||
+ .selector_mask = REG_BANK_SEL,
|
||||
+ .selector_shift = 0,
|
||||
+ .window_start = 0x00,
|
||||
+ .window_len = 0x100,
|
||||
+};
|
||||
+
|
||||
+static const struct regmap_config it6263_hdmi_regmap_config = {
|
||||
+ .name = "it6263-hdmi",
|
||||
+ .reg_bits = 8,
|
||||
+ .val_bits = 8,
|
||||
+ .writeable_reg = it6263_hdmi_writeable_reg,
|
||||
+ .readable_reg = it6263_hdmi_readable_reg,
|
||||
+ .volatile_reg = it6263_hdmi_volatile_reg,
|
||||
+ .max_register = HDMI_REG_AVI_DB13,
|
||||
+ .ranges = &it6263_hdmi_range_cfg,
|
||||
+ .num_ranges = 1,
|
||||
+ .cache_type = REGCACHE_MAPLE,
|
||||
+};
|
||||
+
|
||||
+static bool it6263_lvds_writeable_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ switch (reg) {
|
||||
+ case LVDS_REG_05:
|
||||
+ case LVDS_REG_0B:
|
||||
+ case LVDS_REG_2C:
|
||||
+ case LVDS_REG_3C:
|
||||
+ case LVDS_REG_3F:
|
||||
+ case LVDS_REG_47:
|
||||
+ case LVDS_REG_48:
|
||||
+ case LVDS_REG_4F:
|
||||
+ case LVDS_REG_52:
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static bool it6263_lvds_readable_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ return it6263_lvds_writeable_reg(dev, reg);
|
||||
+}
|
||||
+
|
||||
+static bool it6263_lvds_volatile_reg(struct device *dev, unsigned int reg)
|
||||
+{
|
||||
+ return reg == LVDS_REG_05;
|
||||
+}
|
||||
+
|
||||
+static const struct regmap_config it6263_lvds_regmap_config = {
|
||||
+ .name = "it6263-lvds",
|
||||
+ .reg_bits = 8,
|
||||
+ .val_bits = 8,
|
||||
+ .writeable_reg = it6263_lvds_writeable_reg,
|
||||
+ .readable_reg = it6263_lvds_readable_reg,
|
||||
+ .volatile_reg = it6263_lvds_volatile_reg,
|
||||
+ .max_register = LVDS_REG_52,
|
||||
+ .cache_type = REGCACHE_MAPLE,
|
||||
+};
|
||||
+
|
||||
+static const char * const it6263_supplies[] = {
|
||||
+ "ivdd", "ovdd", "txavcc18", "txavcc33", "pvcc1", "pvcc2",
|
||||
+ "avcc", "anvdd", "apvdd"
|
||||
+};
|
||||
+
|
||||
+static int it6263_parse_dt(struct it6263 *it)
|
||||
+{
|
||||
+ struct device *dev = it->dev;
|
||||
+ struct device_node *port0, *port1;
|
||||
+ int ret = 0;
|
||||
+
|
||||
+ it->lvds_data_mapping = drm_of_lvds_get_data_mapping(dev->of_node);
|
||||
+ if (it->lvds_data_mapping < 0) {
|
||||
+ dev_err(dev, "%pOF: invalid or missing %s DT property: %d\n",
|
||||
+ dev->of_node, "data-mapping", it->lvds_data_mapping);
|
||||
+ return it->lvds_data_mapping;
|
||||
+ }
|
||||
+
|
||||
+ it->next_bridge = devm_drm_of_get_bridge(dev, dev->of_node, 2, 0);
|
||||
+ if (IS_ERR(it->next_bridge))
|
||||
+ return dev_err_probe(dev, PTR_ERR(it->next_bridge),
|
||||
+ "failed to get next bridge\n");
|
||||
+
|
||||
+ port0 = of_graph_get_port_by_id(dev->of_node, 0);
|
||||
+ port1 = of_graph_get_port_by_id(dev->of_node, 1);
|
||||
+ if (port0 && port1) {
|
||||
+ int order;
|
||||
+
|
||||
+ it->lvds_dual_link = true;
|
||||
+ order = drm_of_lvds_get_dual_link_pixel_order_sink(port0, port1);
|
||||
+ if (order < 0) {
|
||||
+ dev_err(dev,
|
||||
+ "failed to get dual link pixel order: %d\n",
|
||||
+ order);
|
||||
+ ret = order;
|
||||
+ } else if (order == DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS) {
|
||||
+ it->lvds_link12_swap = true;
|
||||
+ }
|
||||
+ } else if (port1) {
|
||||
+ ret = -EINVAL;
|
||||
+ dev_err(dev, "single input LVDS port1 is not supported\n");
|
||||
+ } else if (!port0) {
|
||||
+ ret = -EINVAL;
|
||||
+ dev_err(dev, "no input LVDS port\n");
|
||||
+ }
|
||||
+
|
||||
+ of_node_put(port0);
|
||||
+ of_node_put(port1);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_hw_reset(struct gpio_desc *reset_gpio)
|
||||
+{
|
||||
+ if (!reset_gpio)
|
||||
+ return;
|
||||
+
|
||||
+ gpiod_set_value_cansleep(reset_gpio, 0);
|
||||
+ fsleep(1000);
|
||||
+ gpiod_set_value_cansleep(reset_gpio, 1);
|
||||
+ /* The chip maker says the low pulse should be at least 40ms. */
|
||||
+ fsleep(40000);
|
||||
+ gpiod_set_value_cansleep(reset_gpio, 0);
|
||||
+ /* addtional time to wait the high voltage to be stable */
|
||||
+ fsleep(5000);
|
||||
+}
|
||||
+
|
||||
+static inline int it6263_lvds_set_i2c_addr(struct it6263 *it)
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = regmap_write(it->hdmi_regmap, HDMI_REG_LVDS_PORT,
|
||||
+ LVDS_INPUT_CTRL_I2C_ADDR << 1);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ return regmap_write(it->hdmi_regmap, HDMI_REG_LVDS_PORT_EN, BIT(0));
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_lvds_reset(struct it6263 *it)
|
||||
+{
|
||||
+ /* AFE PLL reset */
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_3C, BIT(0), 0x0);
|
||||
+ fsleep(1000);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_3C, BIT(0), BIT(0));
|
||||
+
|
||||
+ /* software pixel clock domain reset */
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_05, REG_SOFT_P_RST,
|
||||
+ REG_SOFT_P_RST);
|
||||
+ fsleep(1000);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_05, REG_SOFT_P_RST, 0x0);
|
||||
+ fsleep(10000);
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_lvds_set_interface(struct it6263 *it)
|
||||
+{
|
||||
+ /* color depth */
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, REG_COL_DEP, BIT8);
|
||||
+ /* output mapping */
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, OUT_MAP, JEIDA);
|
||||
+
|
||||
+ if (it->lvds_dual_link) {
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, DMODE, DISO);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_52, BIT(1), BIT(1));
|
||||
+ } else {
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, DMODE, SISO);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_52, BIT(1), 0);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_lvds_set_afe(struct it6263 *it)
|
||||
+{
|
||||
+ regmap_write(it->lvds_regmap, LVDS_REG_3C, 0xaa);
|
||||
+ regmap_write(it->lvds_regmap, LVDS_REG_3F, 0x02);
|
||||
+ regmap_write(it->lvds_regmap, LVDS_REG_47, 0xaa);
|
||||
+ regmap_write(it->lvds_regmap, LVDS_REG_48, 0x02);
|
||||
+ regmap_write(it->lvds_regmap, LVDS_REG_4F, 0x11);
|
||||
+
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_0B, REG_SSC_PCLK_RF,
|
||||
+ REG_SSC_PCLK_RF);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_3C, 0x07, 0);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, REG_DESSC_ENB,
|
||||
+ REG_DESSC_ENB);
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_lvds_sys_cfg(struct it6263 *it)
|
||||
+{
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_0B, REG_LVDS_IN_SWAP,
|
||||
+ it->lvds_link12_swap ? REG_LVDS_IN_SWAP : 0);
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_lvds_config(struct it6263 *it)
|
||||
+{
|
||||
+ it6263_lvds_reset(it);
|
||||
+ it6263_lvds_set_interface(it);
|
||||
+ it6263_lvds_set_afe(it);
|
||||
+ it6263_lvds_sys_cfg(it);
|
||||
+}
|
||||
+
|
||||
+static inline void it6263_hdmi_config(struct it6263 *it)
|
||||
+{
|
||||
+ regmap_write(it->hdmi_regmap, HDMI_REG_SW_RST, HDMI_RST_ALL);
|
||||
+ regmap_write(it->hdmi_regmap, HDMI_REG_INPUT_MODE, IN_RGB);
|
||||
+ regmap_write_bits(it->hdmi_regmap, HDMI_REG_GCP, HDMI_COLOR_DEPTH,
|
||||
+ HDMI_COLOR_DEPTH_24);
|
||||
+}
|
||||
+
|
||||
+static enum drm_connector_status it6263_detect(struct it6263 *it)
|
||||
+{
|
||||
+ unsigned int val;
|
||||
+
|
||||
+ regmap_read(it->hdmi_regmap, HDMI_REG_SYS_STATUS, &val);
|
||||
+ if (val & HPDETECT)
|
||||
+ return connector_status_connected;
|
||||
+ else
|
||||
+ return connector_status_disconnected;
|
||||
+}
|
||||
+
|
||||
+static int it6263_read_edid(void *data, u8 *buf, unsigned int block, size_t len)
|
||||
+{
|
||||
+ struct it6263 *it = data;
|
||||
+ struct regmap *regmap = it->hdmi_regmap;
|
||||
+ unsigned int start = (block % 2) * EDID_LENGTH;
|
||||
+ unsigned int segment = block >> 1;
|
||||
+ unsigned int count, val;
|
||||
+ int ret;
|
||||
+
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_MASTER_CTRL, MASTER_SEL_HOST);
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_HEADER, DDC_ADDR << 1);
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_EDIDSEG, segment);
|
||||
+
|
||||
+ while (len) {
|
||||
+ /* clear DDC FIFO */
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_FIFO_CLR);
|
||||
+
|
||||
+ ret = regmap_read_poll_timeout(regmap, HDMI_REG_DDC_STATUS,
|
||||
+ val, val & DDC_DONE,
|
||||
+ 2000, 10000);
|
||||
+ if (ret) {
|
||||
+ dev_err(it->dev, "failed to clear DDC FIFO:%d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ count = len > HDMI_DDC_FIFO_BYTES ? HDMI_DDC_FIFO_BYTES : len;
|
||||
+
|
||||
+ /* fire the read command */
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_REQOFF, start);
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_REQCOUNT, count);
|
||||
+ regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_EDID_READ);
|
||||
+
|
||||
+ start += count;
|
||||
+ len -= count;
|
||||
+
|
||||
+ ret = regmap_read_poll_timeout(regmap, HDMI_REG_DDC_STATUS, val,
|
||||
+ val & (DDC_DONE | DDC_ERROR),
|
||||
+ 20000, 250000);
|
||||
+ if (ret && !(val & DDC_ERROR)) {
|
||||
+ dev_err(it->dev, "failed to read EDID:%d\n", ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ if (val & DDC_ERROR) {
|
||||
+ dev_err(it->dev, "DDC error\n");
|
||||
+ return -EIO;
|
||||
+ }
|
||||
+
|
||||
+ /* cache to buffer */
|
||||
+ for (; count > 0; count--) {
|
||||
+ regmap_read(regmap, HDMI_REG_DDC_READFIFO, &val);
|
||||
+ *(buf++) = val;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int it6263_bridge_atomic_check(struct drm_bridge *bridge,
|
||||
+ struct drm_bridge_state *bridge_state,
|
||||
+ struct drm_crtc_state *crtc_state,
|
||||
+ struct drm_connector_state *conn_state)
|
||||
+{
|
||||
+ return drm_atomic_helper_connector_hdmi_check(conn_state->connector,
|
||||
+ conn_state->state);
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+it6263_bridge_atomic_disable(struct drm_bridge *bridge,
|
||||
+ struct drm_bridge_state *old_bridge_state)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+
|
||||
+ regmap_write_bits(it->hdmi_regmap, HDMI_REG_GCP, AVMUTE, AVMUTE);
|
||||
+ regmap_write(it->hdmi_regmap, HDMI_REG_PKT_GENERAL_CTRL, 0);
|
||||
+ regmap_write(it->hdmi_regmap, HDMI_REG_AFE_DRV_CTRL,
|
||||
+ AFE_DRV_RST | AFE_DRV_PWD);
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
+it6263_bridge_atomic_enable(struct drm_bridge *bridge,
|
||||
+ struct drm_bridge_state *old_bridge_state)
|
||||
+{
|
||||
+ struct drm_atomic_state *state = old_bridge_state->base.state;
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+ const struct drm_crtc_state *crtc_state;
|
||||
+ struct regmap *regmap = it->hdmi_regmap;
|
||||
+ const struct drm_display_mode *mode;
|
||||
+ struct drm_connector *connector;
|
||||
+ bool is_stable = false;
|
||||
+ struct drm_crtc *crtc;
|
||||
+ unsigned int val;
|
||||
+ bool pclk_high;
|
||||
+ int i, ret;
|
||||
+
|
||||
+ connector = drm_atomic_get_new_connector_for_encoder(state,
|
||||
+ bridge->encoder);
|
||||
+ crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
|
||||
+ crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
||||
+ mode = &crtc_state->adjusted_mode;
|
||||
+
|
||||
+ regmap_write(regmap, HDMI_REG_HDMI_MODE, TX_HDMI_MODE);
|
||||
+
|
||||
+ drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
|
||||
+
|
||||
+ /* HDMI AFE setup */
|
||||
+ pclk_high = mode->clock > HIGH_PIXEL_CLOCK_KHZ;
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST);
|
||||
+ if (pclk_high)
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_XP_CTRL,
|
||||
+ AFE_XP_GAINBIT | AFE_XP_RESETB);
|
||||
+ else
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_XP_CTRL,
|
||||
+ AFE_XP_ER0 | AFE_XP_RESETB);
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_ISW_CTRL, 0x10);
|
||||
+ if (pclk_high)
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_IP_CTRL,
|
||||
+ AFE_IP_GAINBIT | AFE_IP_RESETB);
|
||||
+ else
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_IP_CTRL,
|
||||
+ AFE_IP_ER0 | AFE_IP_RESETB);
|
||||
+
|
||||
+ /* HDMI software video reset */
|
||||
+ regmap_write_bits(regmap, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST);
|
||||
+ fsleep(1000);
|
||||
+ regmap_write_bits(regmap, HDMI_REG_SW_RST, SOFTV_RST, 0);
|
||||
+
|
||||
+ /* reconfigure LVDS and retry several times in case video is instable */
|
||||
+ for (i = 0; i < 3; i++) {
|
||||
+ ret = regmap_read_poll_timeout(regmap, HDMI_REG_SYS_STATUS, val,
|
||||
+ val & TXVIDSTABLE,
|
||||
+ 20000, 500000);
|
||||
+ if (!ret) {
|
||||
+ is_stable = true;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ it6263_lvds_config(it);
|
||||
+ }
|
||||
+
|
||||
+ if (!is_stable)
|
||||
+ dev_warn(it->dev, "failed to wait for video stable\n");
|
||||
+
|
||||
+ /* HDMI AFE reset release and power up */
|
||||
+ regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, 0);
|
||||
+
|
||||
+ regmap_write_bits(regmap, HDMI_REG_GCP, AVMUTE, 0);
|
||||
+
|
||||
+ regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL, ENABLE_PKT | REPEAT_PKT);
|
||||
+}
|
||||
+
|
||||
+static enum drm_mode_status
|
||||
+it6263_bridge_mode_valid(struct drm_bridge *bridge,
|
||||
+ const struct drm_display_info *info,
|
||||
+ const struct drm_display_mode *mode)
|
||||
+{
|
||||
+ unsigned long long rate;
|
||||
+
|
||||
+ rate = drm_hdmi_compute_mode_clock(mode, 8, HDMI_COLORSPACE_RGB);
|
||||
+ if (rate == 0)
|
||||
+ return MODE_NOCLOCK;
|
||||
+
|
||||
+ return bridge->funcs->hdmi_tmds_char_rate_valid(bridge, mode, rate);
|
||||
+}
|
||||
+
|
||||
+static int it6263_bridge_attach(struct drm_bridge *bridge,
|
||||
+ enum drm_bridge_attach_flags flags)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+ struct drm_connector *connector;
|
||||
+ int ret;
|
||||
+
|
||||
+ ret = drm_bridge_attach(bridge->encoder, it->next_bridge, bridge,
|
||||
+ flags | DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+
|
||||
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
|
||||
+ return 0;
|
||||
+
|
||||
+ connector = drm_bridge_connector_init(bridge->dev, bridge->encoder);
|
||||
+ if (IS_ERR(connector)) {
|
||||
+ ret = PTR_ERR(connector);
|
||||
+ dev_err(it->dev, "failed to initialize bridge connector: %d\n",
|
||||
+ ret);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ drm_connector_attach_encoder(connector, bridge->encoder);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static enum drm_connector_status it6263_bridge_detect(struct drm_bridge *bridge)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+
|
||||
+ return it6263_detect(it);
|
||||
+}
|
||||
+
|
||||
+static const struct drm_edid *
|
||||
+it6263_bridge_edid_read(struct drm_bridge *bridge,
|
||||
+ struct drm_connector *connector)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+
|
||||
+ return drm_edid_read_custom(connector, it6263_read_edid, it);
|
||||
+}
|
||||
+
|
||||
+static u32 *
|
||||
+it6263_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
|
||||
+ struct drm_bridge_state *bridge_state,
|
||||
+ struct drm_crtc_state *crtc_state,
|
||||
+ struct drm_connector_state *conn_state,
|
||||
+ u32 output_fmt,
|
||||
+ unsigned int *num_input_fmts)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+ u32 *input_fmts;
|
||||
+
|
||||
+ *num_input_fmts = 0;
|
||||
+
|
||||
+ if (it->lvds_data_mapping != MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA)
|
||||
+ return NULL;
|
||||
+
|
||||
+ input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
|
||||
+ if (!input_fmts)
|
||||
+ return NULL;
|
||||
+
|
||||
+ input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
|
||||
+ *num_input_fmts = 1;
|
||||
+
|
||||
+ return input_fmts;
|
||||
+}
|
||||
+
|
||||
+static enum drm_mode_status
|
||||
+it6263_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge,
|
||||
+ const struct drm_display_mode *mode,
|
||||
+ unsigned long long tmds_rate)
|
||||
+{
|
||||
+ if (mode->clock > MAX_PIXEL_CLOCK_KHZ)
|
||||
+ return MODE_CLOCK_HIGH;
|
||||
+
|
||||
+ if (tmds_rate > MAX_HDMI_TMDS_CHAR_RATE_HZ)
|
||||
+ return MODE_CLOCK_HIGH;
|
||||
+
|
||||
+ return MODE_OK;
|
||||
+}
|
||||
+
|
||||
+static int it6263_hdmi_clear_infoframe(struct drm_bridge *bridge,
|
||||
+ enum hdmi_infoframe_type type)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+
|
||||
+ if (type == HDMI_INFOFRAME_TYPE_AVI)
|
||||
+ regmap_write(it->hdmi_regmap, HDMI_REG_AVI_INFOFRM_CTRL, 0);
|
||||
+ else
|
||||
+ dev_dbg(it->dev, "unsupported HDMI infoframe 0x%x\n", type);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int it6263_hdmi_write_infoframe(struct drm_bridge *bridge,
|
||||
+ enum hdmi_infoframe_type type,
|
||||
+ const u8 *buffer, size_t len)
|
||||
+{
|
||||
+ struct it6263 *it = bridge_to_it6263(bridge);
|
||||
+ struct regmap *regmap = it->hdmi_regmap;
|
||||
+
|
||||
+ if (type != HDMI_INFOFRAME_TYPE_AVI) {
|
||||
+ dev_dbg(it->dev, "unsupported HDMI infoframe 0x%x\n", type);
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ /* write the first AVI infoframe data byte chunk(DB1-DB5) */
|
||||
+ regmap_bulk_write(regmap, HDMI_REG_AVI_DB1,
|
||||
+ &buffer[HDMI_INFOFRAME_HEADER_SIZE],
|
||||
+ HDMI_AVI_DB_CHUNK1_SIZE);
|
||||
+
|
||||
+ /* write the second AVI infoframe data byte chunk(DB6-DB13) */
|
||||
+ regmap_bulk_write(regmap, HDMI_REG_AVI_DB6,
|
||||
+ &buffer[HDMI_INFOFRAME_HEADER_SIZE +
|
||||
+ HDMI_AVI_DB_CHUNK1_SIZE],
|
||||
+ HDMI_AVI_DB_CHUNK2_SIZE);
|
||||
+
|
||||
+ /* write checksum */
|
||||
+ regmap_write(regmap, HDMI_REG_AVI_CSUM, buffer[3]);
|
||||
+
|
||||
+ regmap_write(regmap, HDMI_REG_AVI_INFOFRM_CTRL, ENABLE_PKT | REPEAT_PKT);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static const struct drm_bridge_funcs it6263_bridge_funcs = {
|
||||
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
+ .atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
+ .attach = it6263_bridge_attach,
|
||||
+ .mode_valid = it6263_bridge_mode_valid,
|
||||
+ .atomic_disable = it6263_bridge_atomic_disable,
|
||||
+ .atomic_enable = it6263_bridge_atomic_enable,
|
||||
+ .atomic_check = it6263_bridge_atomic_check,
|
||||
+ .detect = it6263_bridge_detect,
|
||||
+ .edid_read = it6263_bridge_edid_read,
|
||||
+ .atomic_get_input_bus_fmts = it6263_bridge_atomic_get_input_bus_fmts,
|
||||
+ .hdmi_tmds_char_rate_valid = it6263_hdmi_tmds_char_rate_valid,
|
||||
+ .hdmi_clear_infoframe = it6263_hdmi_clear_infoframe,
|
||||
+ .hdmi_write_infoframe = it6263_hdmi_write_infoframe,
|
||||
+};
|
||||
+
|
||||
+static int it6263_probe(struct i2c_client *client)
|
||||
+{
|
||||
+ struct device *dev = &client->dev;
|
||||
+ struct gpio_desc *reset_gpio;
|
||||
+ struct it6263 *it;
|
||||
+ int ret;
|
||||
+
|
||||
+ it = devm_kzalloc(dev, sizeof(*it), GFP_KERNEL);
|
||||
+ if (!it)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ it->dev = dev;
|
||||
+ it->hdmi_i2c = client;
|
||||
+
|
||||
+ it->hdmi_regmap = devm_regmap_init_i2c(client,
|
||||
+ &it6263_hdmi_regmap_config);
|
||||
+ if (IS_ERR(it->hdmi_regmap))
|
||||
+ return dev_err_probe(dev, PTR_ERR(it->hdmi_regmap),
|
||||
+ "failed to init I2C regmap for HDMI\n");
|
||||
+
|
||||
+ reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
|
||||
+ if (IS_ERR(reset_gpio))
|
||||
+ return dev_err_probe(dev, PTR_ERR(reset_gpio),
|
||||
+ "failed to get reset gpio\n");
|
||||
+
|
||||
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(it6263_supplies),
|
||||
+ it6263_supplies);
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "failed to get power supplies\n");
|
||||
+
|
||||
+ ret = it6263_parse_dt(it);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
+
|
||||
+ it6263_hw_reset(reset_gpio);
|
||||
+
|
||||
+ ret = it6263_lvds_set_i2c_addr(it);
|
||||
+ if (ret)
|
||||
+ return dev_err_probe(dev, ret, "failed to set I2C addr\n");
|
||||
+
|
||||
+ it->lvds_i2c = devm_i2c_new_dummy_device(dev, client->adapter,
|
||||
+ LVDS_INPUT_CTRL_I2C_ADDR);
|
||||
+ if (IS_ERR(it->lvds_i2c))
|
||||
+ dev_err_probe(it->dev, PTR_ERR(it->lvds_i2c),
|
||||
+ "failed to allocate I2C device for LVDS\n");
|
||||
+
|
||||
+ it->lvds_regmap = devm_regmap_init_i2c(it->lvds_i2c,
|
||||
+ &it6263_lvds_regmap_config);
|
||||
+ if (IS_ERR(it->lvds_regmap))
|
||||
+ return dev_err_probe(dev, PTR_ERR(it->lvds_regmap),
|
||||
+ "failed to init I2C regmap for LVDS\n");
|
||||
+
|
||||
+ it6263_lvds_config(it);
|
||||
+ it6263_hdmi_config(it);
|
||||
+
|
||||
+ i2c_set_clientdata(client, it);
|
||||
+
|
||||
+ it->bridge.funcs = &it6263_bridge_funcs;
|
||||
+ it->bridge.of_node = dev->of_node;
|
||||
+ /* IT6263 chip doesn't support HPD interrupt. */
|
||||
+ it->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
|
||||
+ DRM_BRIDGE_OP_HDMI;
|
||||
+ it->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
|
||||
+ it->bridge.vendor = "ITE";
|
||||
+ it->bridge.product = "IT6263";
|
||||
+
|
||||
+ return devm_drm_bridge_add(dev, &it->bridge);
|
||||
+}
|
||||
+
|
||||
+static const struct of_device_id it6263_of_match[] = {
|
||||
+ { .compatible = "ite,it6263", },
|
||||
+ { }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(of, it6263_of_match);
|
||||
+
|
||||
+static const struct i2c_device_id it6263_i2c_ids[] = {
|
||||
+ { "it6263", 0 },
|
||||
+ { }
|
||||
+};
|
||||
+MODULE_DEVICE_TABLE(i2c, it6263_i2c_ids);
|
||||
+
|
||||
+static struct i2c_driver it6263_driver = {
|
||||
+ .probe = it6263_probe,
|
||||
+ .driver = {
|
||||
+ .name = "it6263",
|
||||
+ .of_match_table = it6263_of_match,
|
||||
+ },
|
||||
+ .id_table = it6263_i2c_ids,
|
||||
+};
|
||||
+module_i2c_driver(it6263_driver);
|
||||
+
|
||||
+MODULE_DESCRIPTION("ITE Tech. Inc. IT6263 LVDS/HDMI bridge");
|
||||
+MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
|
||||
+MODULE_LICENSE("GPL");
|
||||
@@ -1,82 +0,0 @@
|
||||
From 40dbdab340dfc6c4392479d726999db6b4ed2713 Mon Sep 17 00:00:00 2001
|
||||
From: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
|
||||
Date: Thu, 5 Dec 2024 09:02:10 +0100
|
||||
Subject: [PATCH 34/35] drm/bridge: ite-it6263: Support VESA-24 input format
|
||||
Organization: Wires
|
||||
|
||||
Introduce it6263_is_input_bus_fmt_valid() and refactor the
|
||||
it6263_bridge_atomic_get_input_bus_fmts() function to support VESA-24
|
||||
format by selecting the LVDS input format based on the LVDS data mapping
|
||||
and thereby support both JEIDA-24 and VESA-24 input formats.
|
||||
|
||||
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
Signed-off-by: Tommaso Merciai <tommaso.merciai.xr@bp.renesas.com>
|
||||
Reviewed-by: Liu Ying <victor.liu@nxp.com>
|
||||
Reviewed-by: Biju Das <biju.das.jz@bp.renesas.com>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/20241205080210.1285385-1-tommaso.merciai.xr@bp.renesas.com
|
||||
Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
|
||||
---
|
||||
drivers/gpu/drm/bridge/ite-it6263.c | 25 ++++++++++++++++++++++---
|
||||
1 file changed, 22 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/drivers/gpu/drm/bridge/ite-it6263.c b/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
index cbabd4e20d3e..3fc5c6795487 100644
|
||||
--- a/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
+++ b/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
@@ -48,6 +48,7 @@
|
||||
#define REG_COL_DEP GENMASK(1, 0)
|
||||
#define BIT8 FIELD_PREP(REG_COL_DEP, 1)
|
||||
#define OUT_MAP BIT(4)
|
||||
+#define VESA BIT(4)
|
||||
#define JEIDA 0
|
||||
#define REG_DESSC_ENB BIT(6)
|
||||
#define DMODE BIT(7)
|
||||
@@ -428,12 +429,30 @@ static inline void it6263_lvds_reset(struct it6263 *it)
|
||||
fsleep(10000);
|
||||
}
|
||||
|
||||
+static inline bool it6263_is_input_bus_fmt_valid(int input_fmt)
|
||||
+{
|
||||
+ switch (input_fmt) {
|
||||
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
|
||||
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
|
||||
+ return true;
|
||||
+ }
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
static inline void it6263_lvds_set_interface(struct it6263 *it)
|
||||
{
|
||||
+ u8 fmt;
|
||||
+
|
||||
/* color depth */
|
||||
regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, REG_COL_DEP, BIT8);
|
||||
+
|
||||
+ if (it->lvds_data_mapping == MEDIA_BUS_FMT_RGB888_1X7X4_SPWG)
|
||||
+ fmt = VESA;
|
||||
+ else
|
||||
+ fmt = JEIDA;
|
||||
+
|
||||
/* output mapping */
|
||||
- regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, OUT_MAP, JEIDA);
|
||||
+ regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, OUT_MAP, fmt);
|
||||
|
||||
if (it->lvds_dual_link) {
|
||||
regmap_write_bits(it->lvds_regmap, LVDS_REG_2C, DMODE, DISO);
|
||||
@@ -714,14 +733,14 @@ it6263_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
|
||||
|
||||
*num_input_fmts = 0;
|
||||
|
||||
- if (it->lvds_data_mapping != MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA)
|
||||
+ if (!it6263_is_input_bus_fmt_valid(it->lvds_data_mapping))
|
||||
return NULL;
|
||||
|
||||
input_fmts = kmalloc(sizeof(*input_fmts), GFP_KERNEL);
|
||||
if (!input_fmts)
|
||||
return NULL;
|
||||
|
||||
- input_fmts[0] = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
|
||||
+ input_fmts[0] = it->lvds_data_mapping;
|
||||
*num_input_fmts = 1;
|
||||
|
||||
return input_fmts;
|
||||
@@ -1,36 +0,0 @@
|
||||
From ee2b394cbd2efd3a38151bfb5b244012ab0f05b8 Mon Sep 17 00:00:00 2001
|
||||
From: Dan Carpenter <dan.carpenter@linaro.org>
|
||||
Date: Tue, 12 Nov 2024 13:23:03 +0300
|
||||
Subject: [PATCH 35/35] drm/bridge: ite-it6263: Prevent error pointer
|
||||
dereference in probe()
|
||||
Organization: Wires
|
||||
|
||||
If devm_i2c_new_dummy_device() fails then we were supposed to return an
|
||||
error code, but instead the function continues and will crash on the next
|
||||
line. Add the missing return statement.
|
||||
|
||||
Fixes: 049723628716 ("drm/bridge: Add ITE IT6263 LVDS to HDMI converter")
|
||||
Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
|
||||
Reviewed-by: Biju Das <biju.das.jz@bp.renesas.com>
|
||||
Reviewed-by: Liu Ying <victor.liu@nxp.com>
|
||||
Signed-off-by: Liu Ying <victor.liu@nxp.com>
|
||||
Link: https://patchwork.freedesktop.org/patch/msgid/804a758b-f2e7-4116-b72d-29bc8905beed@stanley.mountain
|
||||
---
|
||||
drivers/gpu/drm/bridge/ite-it6263.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/drivers/gpu/drm/bridge/ite-it6263.c b/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
index 3fc5c6795487..9608e83ca894 100644
|
||||
--- a/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
+++ b/drivers/gpu/drm/bridge/ite-it6263.c
|
||||
@@ -864,8 +864,8 @@ static int it6263_probe(struct i2c_client *client)
|
||||
it->lvds_i2c = devm_i2c_new_dummy_device(dev, client->adapter,
|
||||
LVDS_INPUT_CTRL_I2C_ADDR);
|
||||
if (IS_ERR(it->lvds_i2c))
|
||||
- dev_err_probe(it->dev, PTR_ERR(it->lvds_i2c),
|
||||
- "failed to allocate I2C device for LVDS\n");
|
||||
+ return dev_err_probe(it->dev, PTR_ERR(it->lvds_i2c),
|
||||
+ "failed to allocate I2C device for LVDS\n");
|
||||
|
||||
it->lvds_regmap = devm_regmap_init_i2c(it->lvds_i2c,
|
||||
&it6263_lvds_regmap_config);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user