mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 17:23:00 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb7c031270 | ||
|
|
1d23f73bfd | ||
|
|
119b92f94e | ||
|
|
96fad208cb | ||
|
|
0a3879f521 | ||
|
|
6f6bb412f9 | ||
|
|
b04bae1ad6 | ||
|
|
73edf1bbd6 | ||
|
|
2557eb4900 | ||
|
|
f71722c111 | ||
|
|
5ea99680c8 | ||
|
|
55b038d999 | ||
|
|
83b55ee4d8 | ||
|
|
68e7167902 | ||
|
|
25a26fe7c6 | ||
|
|
6c8bd25cee | ||
|
|
ea300137ef | ||
|
|
44e9c87376 | ||
|
|
e708f21444 | ||
|
|
3508b8d0bb | ||
|
|
eec867bd5f | ||
|
|
daa0bd81e8 | ||
|
|
4ccc70aeee | ||
|
|
923443b620 | ||
|
|
9ad51c31bf | ||
|
|
6272c9c46c | ||
|
|
4750f96774 | ||
|
|
81bc143883 | ||
|
|
58b3fb30be | ||
|
|
6dfc305f0b | ||
|
|
8ca3d3a3ff | ||
|
|
7ae4de3a17 |
@@ -20,7 +20,6 @@ jobs:
|
||||
- cn9130_crb_boot
|
||||
- aarch64_qemu_boot
|
||||
- rpi4_boot
|
||||
- mt7986_sd_boot
|
||||
env:
|
||||
MAKEFLAGS: -j5
|
||||
steps:
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
name: Create SD Card Images
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
board:
|
||||
description: 'Board to create image for'
|
||||
type: choice
|
||||
required: true
|
||||
options:
|
||||
- raspberry-pi-4
|
||||
- banana-pi-r3
|
||||
default: 'raspberry-pi-4'
|
||||
use_latest_release:
|
||||
description: 'Use latest release artifacts instead of workflow artifacts'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
create-image:
|
||||
name: Create SD Card Image for ${{ inputs.board }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
clean: true
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
genimage \
|
||||
u-boot-tools \
|
||||
parted \
|
||||
gdisk \
|
||||
qemu-utils \
|
||||
dosfstools \
|
||||
e2fsprogs \
|
||||
genext2fs \
|
||||
mtools \
|
||||
jq
|
||||
|
||||
- name: Prepare build environment
|
||||
run: |
|
||||
# Set up directory structure similar to buildroot build
|
||||
mkdir -p output/images
|
||||
mkdir -p build
|
||||
|
||||
- name: Set bootloader and target based on board
|
||||
run: |
|
||||
case "${{ inputs.board }}" in
|
||||
raspberry-pi-4)
|
||||
echo "BOOTLOADER=rpi4_boot" >> $GITHUB_ENV
|
||||
echo "TARGET=aarch64" >> $GITHUB_ENV
|
||||
;;
|
||||
banana-pi-r3)
|
||||
echo "BOOTLOADER=mt7986_sd_boot" >> $GITHUB_ENV
|
||||
echo "TARGET=aarch64" >> $GITHUB_ENV
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown board ${{ inputs.board }}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "Using bootloader: $BOOTLOADER and target: $TARGET for board: ${{ inputs.board }}"
|
||||
|
||||
- name: Download bootloader artifacts
|
||||
if: ${{ !inputs.use_latest_release }}
|
||||
run: |
|
||||
# Download from latest bootloader build workflow on main branch
|
||||
gh run list --workflow=build-boot.yml --branch=main --limit=1 --status=success --json databaseId --jq '.[0].databaseId' > latest_boot_run_id
|
||||
BOOT_RUN_ID=$(cat latest_boot_run_id)
|
||||
|
||||
gh run download ${BOOT_RUN_ID} --name artifact-${BOOTLOADER} --dir temp_bootloader/
|
||||
|
||||
# Extract bootloader directly to output/images
|
||||
cd temp_bootloader/
|
||||
tar -xzf *.tar.gz --strip-components=1 -C ../output/images/
|
||||
cd ../
|
||||
rm -rf temp_bootloader/
|
||||
|
||||
echo "Bootloader files extracted to output/images:"
|
||||
ls -la output/images/
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download Infix artifacts
|
||||
if: ${{ !inputs.use_latest_release }}
|
||||
run: |
|
||||
# Download from latest Kernelkit Trigger workflow for main branch
|
||||
gh run list --workflow=164295764 --branch=main --limit=1 --status=success --json databaseId --jq '.[0].databaseId' > latest_infix_run_id
|
||||
INFIX_RUN_ID=$(cat latest_infix_run_id)
|
||||
|
||||
gh run download ${INFIX_RUN_ID} --name artifact-${TARGET} --dir temp_infix/
|
||||
|
||||
# Extract Infix directly to output/images
|
||||
cd temp_infix/
|
||||
tar -xzf *.tar.gz --strip-components=1 -C ../output/images/
|
||||
cd ../
|
||||
rm -rf temp_infix/
|
||||
|
||||
echo "Infix files extracted to output/images:"
|
||||
ls -la output/images/
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Download from latest releases
|
||||
if: ${{ inputs.use_latest_release }}
|
||||
run: |
|
||||
# Download latest bootloader release
|
||||
gh release download latest-boot --pattern "*${BOOTLOADER}*" --dir temp_bootloader/
|
||||
cd temp_bootloader/
|
||||
tar -xzf *.tar.gz --strip-components=1 -C ../output/images/
|
||||
cd ../
|
||||
rm -rf temp_bootloader/
|
||||
|
||||
# Download latest Infix release
|
||||
gh release download latest --pattern "*${TARGET}*" --dir temp_infix/
|
||||
cd temp_infix/
|
||||
tar -xzf *.tar.gz --strip-components=1 -C ../output/images/
|
||||
cd ../
|
||||
rm -rf temp_infix/
|
||||
|
||||
echo "All files extracted to output/images:"
|
||||
ls -la output/images/
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Verify extracted files
|
||||
run: |
|
||||
echo "Files available for mkimage.sh:"
|
||||
ls -la output/images/
|
||||
echo ""
|
||||
echo "File types:"
|
||||
file output/images/* || true
|
||||
|
||||
- name: Create SD card image
|
||||
run: |
|
||||
export BINARIES_DIR=$PWD/output/images
|
||||
export BUILD_DIR=$PWD/build
|
||||
export BR2_EXTERNAL_INFIX_PATH=$PWD
|
||||
export RELEASE=""
|
||||
export INFIX_ID="infix"
|
||||
|
||||
# Use the standardized mkimage.sh path for the selected board
|
||||
# BOARD_SCRIPT="src/board/${{ inputs.board }}/mkimage.sh"
|
||||
BOARD_SCRIPT="src/board/${{ inputs.board }}/mkimage.sh"
|
||||
|
||||
if [ -f "$BOARD_SCRIPT" ]; then
|
||||
echo "Using board-specific image creation script: $BOARD_SCRIPT"
|
||||
chmod +x "$BOARD_SCRIPT"
|
||||
"$BOARD_SCRIPT"
|
||||
else
|
||||
echo "Error: Board script $BOARD_SCRIPT not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify created image
|
||||
run: |
|
||||
echo "Contents of output/images after mkimage.sh:"
|
||||
ls -lh output/images/
|
||||
|
||||
# Look for SD card image with pattern: *-sdcard.img
|
||||
if ls output/images/*-sdcard.img 1> /dev/null 2>&1; then
|
||||
echo "Found SD card image(s):"
|
||||
for img in output/images/*-sdcard.img; do
|
||||
echo "- $(basename $img)"
|
||||
file "$img"
|
||||
fdisk -l "$img" 2>/dev/null || true
|
||||
done
|
||||
else
|
||||
echo "No SD card image found matching pattern: *-sdcard.img"
|
||||
echo "Available files:"
|
||||
ls -la output/images/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload SD card image
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdcard-image-${{ inputs.board }}-${{ env.BOOTLOADER }}
|
||||
path: |
|
||||
output/images/*-sdcard.img
|
||||
retention-days: 30
|
||||
|
||||
- name: Create checksums
|
||||
run: |
|
||||
cd output/images/
|
||||
for file in *-sdcard.img; do
|
||||
if [ -f "$file" ]; then
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload to release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
omitName: true
|
||||
omitBody: true
|
||||
omitBodyDuringUpdate: true
|
||||
prerelease: true
|
||||
tag: "latest-boot"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
artifacts: "output/images/*-sdcard.img*"
|
||||
|
||||
- name: Generate summary
|
||||
run: |
|
||||
cat <<EOF >> $GITHUB_STEP_SUMMARY
|
||||
# SD Card Image Build Complete! 🚀
|
||||
|
||||
**Board:** ${{ inputs.board }}
|
||||
**Target:** ${{ env.TARGET }}
|
||||
**Bootloader:** ${{ env.BOOTLOADER }}
|
||||
**Artifact Source:** ${{ inputs.use_latest_release && 'Latest Release' || 'Latest Workflow Run' }}
|
||||
|
||||
## Created Images
|
||||
$(find output/images/ -name "*.img" -o -name "*.qcow2" -o -name "*.raw" | xargs ls -lh 2>/dev/null | sed 's/^/- /' || echo "- No images found")
|
||||
|
||||
## Download
|
||||
The SD card image is available as a workflow artifact above.
|
||||
EOF
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
if echo $ver | grep -qE 'v[0-9.]+(-alpha|-beta|-rc)[0-9]*'; then
|
||||
echo "pre=true" >> $GITHUB_OUTPUT
|
||||
echo "latest=false" >> $GITHUB_OUTPUT
|
||||
elif echo $ver | grep -qE '^v[0-9.]+\.[0-9.]+(\.[0-9]+)?$'; then
|
||||
elif echo $ver | grep -qE '^v[0-9]+\.[0-9]+(\.0)?$'; then
|
||||
echo "pre=false" >> $GITHUB_OUTPUT
|
||||
echo "latest=true" >> $GITHUB_OUTPUT
|
||||
echo "cat=Releases" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -37,6 +37,6 @@ jobs:
|
||||
name: "infix"
|
||||
|
||||
test-publish-x86_64:
|
||||
if: startsWith(github.repository, 'kernelkit/')
|
||||
if: ${{ github.repository_owner == 'kernelkit' && github.ref_name == 'main' }}
|
||||
needs: test-run-x86_64
|
||||
uses: ./.github/workflows/publish.yml
|
||||
|
||||
@@ -112,12 +112,10 @@ containers for any custom functionality you need.
|
||||
### Supported Platforms
|
||||
|
||||
- **Raspberry Pi 4B** - Perfect for home labs, learning, and prototyping
|
||||
- **Banana Pi-R3** - Your next home router and gateway
|
||||
- **NanoPi R2S** - Compact dual-port router in a tiny package
|
||||
- **NanoPi R2S** - Compact dual-port router in a tiny package
|
||||
- **x86_64** - Run in VMs or on mini PCs for development and testing
|
||||
- **Marvell CN9130 CRB, EspressoBIN** - High-performance ARM64 platforms
|
||||
- **Microchip SparX-5i** - Enterprise switching capabilities
|
||||
- **NXP i.MX8MP EVK** - Highly capable ARM64 SoC
|
||||
- **Marvell CN9130 CRB, EspressoBIN** - High-performance ARM platforms
|
||||
- **Microchip SparX-5i, NXP i.MX8MP EVK** - Enterprise switching capabilities
|
||||
- **StarFive VisionFive2** - RISC-V architecture support
|
||||
|
||||
*Why start with Raspberry Pi?* It's affordable, widely available, has
|
||||
@@ -150,8 +148,6 @@ high-throughput network appliances.
|
||||
<br />Infix development is sponsored by <a href="https://wires.se">Wires</a>
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
[1]: https://buildroot.org/ "Buildroot Homepage"
|
||||
[2]: https://www.sysrepo.org/ "Sysrepo Homepage"
|
||||
[3]: https://kernelkit.org/infix/latest/cli/introduction/
|
||||
|
||||
+45
-2
@@ -4,8 +4,51 @@ aarch64
|
||||
Board Specific Documentation
|
||||
----------------------------
|
||||
|
||||
- [Banana Pi R3](banana-pi-r3/)
|
||||
- [Marvell CN9130-CRB](cn9130-crb/)
|
||||
- [Microchip SparX-5i PCB135 (eMMC)](sparx5-pcb135/)
|
||||
- [NanoPi R2S](r2s/)
|
||||
- [Raspberry Pi 4 b](raspberry-pi-4/)
|
||||
- [Raspberry Pi 4 b](#raspberry-pi-4-b)
|
||||
|
||||
# Raspberry Pi 4 b
|
||||
|
||||
## Support level
|
||||
Full support for base board but not any extension board on the
|
||||
GPIOs.
|
||||
|
||||
### Touch screen
|
||||
The [Raspberry Pi touch display v1][RPI-TOUCH] is supported, including
|
||||
touch functionality. There are multiple touchscreens on the market for
|
||||
Raspberry Pi, but only the official (first version with 800x480
|
||||
resolution) is currently supported. Infix supplies all drivers
|
||||
required to utilize the hardware, but you need to add the actual
|
||||
graphical application in a container.
|
||||
|
||||
There are some important considerations you need to know about when
|
||||
using Infix for graphical applications. The container needs access to
|
||||
/dev/dri/ to be able to access the graphics card, and it also needs
|
||||
access to /run/udev to be able to find the input devices.
|
||||
|
||||
Example of running Doom in Infix:
|
||||
|
||||
```cli
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit container doom
|
||||
admin@example:/config/container/doom/> set image docker://mattiaswal/alpine-doom:latest
|
||||
admin@example:/config/container/doom/> set privileged
|
||||
admin@example:/config/container/doom/> edit mount udev
|
||||
admin@example:/config/container/doom/mount/udev/> set type bind
|
||||
admin@example:/config/container/doom/mount/udev/> set target /run/udev/
|
||||
admin@example:/config/container/doom/mount/udev/> set source /run/udev/
|
||||
admin@example:/config/container/doom/mount/udev/> end
|
||||
admin@example:/config/container/doom/mount/xorg.conf/> set content U2VjdGlvbiAiT3V0cHV0Q2xhc3MiCiAgSWRlbnRpZmllciAidmM0IgogIE1hdGNoRHJpdmVyICJ2YzQiCiAgRHJpdmVyICJtb2Rlc2V0dGluZyIKICBPcHRpb24gIlByaW1hcnlHUFUiICJ0cnVlIgpFbmRTZWN0aW9uCg==
|
||||
admin@example:/config/container/doom/mount/xorg.conf/> set target /etc/X11/xorg.conf
|
||||
admin@example:/config/container/doom/mount/xorg.conf/> end
|
||||
admin@example:/config/container/doom/> edit volume var
|
||||
admin@example:/config/container/doom/volume/var/> set target /var
|
||||
admin@example:/config/container/doom/volume/var/> leave
|
||||
admin@example:/>
|
||||
|
||||
```
|
||||
|
||||
|
||||
[RPI-TOUCH]: https://www.raspberrypi.com/products/raspberry-pi-touch-display/
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# Banana Pi R3
|
||||
|
||||
## Support level
|
||||
Full support for all Infix enabled features including switched ethernet ports, WiFi,
|
||||
and SFP interfaces. The board includes comprehensive hardware support for
|
||||
MediaTek MT7986 SoC features.
|
||||
|
||||
### Hardware features
|
||||
The Banana Pi R3 is a high-performance networking board featuring:
|
||||
- MediaTek MT7986 ARM Cortex-A53 quad-core processor
|
||||
- 4x Gigabit LAN ports (lan1-lan4)
|
||||
- 1x Gigabit WAN port
|
||||
- 2x SFP ports (sfp1, sfp2) for fiber connectivity
|
||||
- Dual WiFi interfaces (wifi0 for 2.4GHz, wifi1 for 5GHz)
|
||||
- USB support
|
||||
- SD card boot support
|
||||
|
||||
### Network configuration
|
||||
The board comes preconfigured with:
|
||||
- 4 switched LAN ports for internal networking
|
||||
- Dedicated WAN port with DHCP client enabled
|
||||
- SFP ports for high-speed fiber connections
|
||||
- Dual WiFi interfaces for wireless connectivity
|
||||
|
||||
### Pre-built images
|
||||
SD card image: [infix-bpi-r3-sdcard.img](https://github.com/kernelkit/infix/releases/download/latest-boot/infix-bpi-r3-sdcard.img)
|
||||
|
||||
@@ -401,6 +401,7 @@ CONFIG_WATCHDOG=y
|
||||
CONFIG_WATCHDOG_SYSFS=y
|
||||
CONFIG_SOFT_WATCHDOG=y
|
||||
CONFIG_GPIO_WATCHDOG=y
|
||||
CONFIG_ARM_SBSA_WATCHDOG=y
|
||||
CONFIG_ARMADA_37XX_WATCHDOG=y
|
||||
CONFIG_I6300ESB_WDT=y
|
||||
CONFIG_MFD_MAX77620=y
|
||||
@@ -549,7 +550,14 @@ CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
CONFIG_PANIC_ON_OOPS=y
|
||||
CONFIG_PANIC_TIMEOUT=20
|
||||
CONFIG_DETECT_HUNG_TASK=y
|
||||
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR_PREFER_BUDDY=y
|
||||
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
|
||||
CONFIG_BOOTPARAM_HUNG_TASK_PANIC=y
|
||||
CONFIG_WQ_WATCHDOG=y
|
||||
CONFIG_WQ_CPU_INTENSIVE_REPORT=y
|
||||
CONFIG_TEST_LOCKUP=m
|
||||
# CONFIG_SCHED_DEBUG is not set
|
||||
# CONFIG_RCU_TRACE is not set
|
||||
CONFIG_FUNCTION_TRACER=y
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo="
|
||||
"infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCAtLSBhIE5ldHdvcmsgT3BlcmF0aW5nIFN5c3RlbQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQuZ2l0aHViLmlvCictJy0tLSctJwo="
|
||||
},
|
||||
"infix-dhcp-client:dhcp-client": {
|
||||
"client-if": [
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# Raspberry Pi 4 b
|
||||
|
||||
## Support level
|
||||
Full support for base board but not any extension board on the
|
||||
GPIOs.
|
||||
|
||||
### Touch screen
|
||||
The [Raspberry Pi touch display v1][RPI-TOUCH] is supported, including
|
||||
touch functionality. There are multiple touchscreens on the market for
|
||||
Raspberry Pi, but only the official (first version with 800x480
|
||||
resolution) is currently supported. Infix supplies all drivers
|
||||
required to utilize the hardware, but you need to add the actual
|
||||
graphical application in a container.
|
||||
|
||||
There are some important considerations you need to know about when
|
||||
using Infix for graphical applications. The container needs access to
|
||||
/dev/dri/ to be able to access the graphics card, and it also needs
|
||||
access to /run/udev to be able to find the input devices.
|
||||
|
||||
Example of running Doom in Infix:
|
||||
|
||||
```cli
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit container doom
|
||||
admin@example:/config/container/doom/> set image docker://mattiaswal/alpine-doom:latest
|
||||
admin@example:/config/container/doom/> set privileged
|
||||
admin@example:/config/container/doom/> edit mount udev
|
||||
admin@example:/config/container/doom/mount/udev/> set type bind
|
||||
admin@example:/config/container/doom/mount/udev/> set target /run/udev/
|
||||
admin@example:/config/container/doom/mount/udev/> set source /run/udev/
|
||||
admin@example:/config/container/doom/mount/udev/> end
|
||||
admin@example:/config/container/doom/mount/xorg.conf/> set content U2VjdGlvbiAiT3V0cHV0Q2xhc3MiCiAgSWRlbnRpZmllciAidmM0IgogIE1hdGNoRHJpdmVyICJ2YzQiCiAgRHJpdmVyICJtb2Rlc2V0dGluZyIKICBPcHRpb24gIlByaW1hcnlHUFUiICJ0cnVlIgpFbmRTZWN0aW9uCg==
|
||||
admin@example:/config/container/doom/mount/xorg.conf/> set target /etc/X11/xorg.conf
|
||||
admin@example:/config/container/doom/mount/xorg.conf/> end
|
||||
admin@example:/config/container/doom/> edit volume var
|
||||
admin@example:/config/container/doom/volume/var/> set target /var
|
||||
admin@example:/config/container/doom/volume/var/> leave
|
||||
admin@example:/>
|
||||
|
||||
```
|
||||
|
||||
### Pre-built images
|
||||
SD card image: [infix-rpi4-sdcard.img](https://github.com/kernelkit/infix/releases/download/latest-boot/infix-rpi4-sdcard.img)
|
||||
|
||||
|
||||
[RPI-TOUCH]: https://www.raspberrypi.com/products/raspberry-pi-touch-display/
|
||||
@@ -6,12 +6,6 @@ mountprefix=/var/lib/rauc/mnt
|
||||
bundle-formats=-plain
|
||||
max-bundle-download-size=1073741824
|
||||
|
||||
[log.event-log]
|
||||
filename=/var/log/upgrade-json.log
|
||||
format=json-pretty
|
||||
max-size=1M
|
||||
max-files=5
|
||||
|
||||
[keyring]
|
||||
directory=/etc/rauc/keys
|
||||
|
||||
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# Build a map of phandle -> device tree node path for all PHY nodes
|
||||
build_phandle_map()
|
||||
{
|
||||
for phy_node in /sys/firmware/devicetree/base/cp*/config-space*/mdio*/switch*/mdio/ethernet-phy@* \
|
||||
/sys/firmware/devicetree/base/cp*/config-space*/mdio*/ethernet-phy@*; do
|
||||
[ -f "$phy_node/phandle" ] || continue
|
||||
|
||||
phandle=$(od -An -t x4 -N 4 "$phy_node/phandle" 2>/dev/null | tr -d ' ')
|
||||
if [ -n "$phandle" ]; then
|
||||
echo "$phandle:$phy_node"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
build_phy_map()
|
||||
{
|
||||
phandle_map=$(build_phandle_map)
|
||||
|
||||
# Build a mapping of PHY of_node path -> interface name
|
||||
for iface in /sys/class/net/*; do
|
||||
[ -d "$iface" ] || continue
|
||||
|
||||
iface_name=$(basename "$iface")
|
||||
|
||||
# Try regular phydev approach first (for non-DSA interfaces)
|
||||
if [ -L "$iface/phydev/of_node" ]; then
|
||||
phy_of_node=$(readlink -f "$iface/phydev/of_node" 2>/dev/null)
|
||||
if [ -n "$phy_of_node" ]; then
|
||||
echo "$phy_of_node:$iface_name"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# For DSA interfaces, resolve via of_node's phy-handle
|
||||
if [ -L "$iface/of_node" ]; then
|
||||
iface_of_node=$(readlink -f "$iface/of_node" 2>/dev/null)
|
||||
[ -n "$iface_of_node" ] || continue
|
||||
|
||||
# Try to read phy-handle property (4-byte phandle)
|
||||
if [ -f "$iface_of_node/phy-handle" ]; then
|
||||
phy_phandle=$(od -An -t x4 -N 4 "$iface_of_node/phy-handle" 2>/dev/null | tr -d ' ')
|
||||
|
||||
if [ -n "$phy_phandle" ]; then
|
||||
# Look up the PHY node path from our phandle map
|
||||
phy_of_node=$(echo "$phandle_map" | grep "^$phy_phandle:" | cut -d: -f2)
|
||||
if [ -n "$phy_of_node" ]; then
|
||||
echo "$phy_of_node:$iface_name"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
zone_map()
|
||||
{
|
||||
type="$1"
|
||||
|
||||
case "$type" in
|
||||
ap-ic-thermal)
|
||||
echo "Application processor interconnect"
|
||||
;;
|
||||
ap-cpu[0-9]*-thermal)
|
||||
cpu=${type#ap-cpu}
|
||||
cpu=${cpu%-thermal}
|
||||
echo "Application processor core $cpu"
|
||||
;;
|
||||
cp[0-9]*-ic-thermal)
|
||||
cp=${type%%-*}
|
||||
cp=${cp#cp}
|
||||
echo "Communication processor $cp interconnect"
|
||||
;;
|
||||
*)
|
||||
echo "$type"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
thermal_zones()
|
||||
{
|
||||
echo "Thermal Zones"
|
||||
echo "============="
|
||||
echo
|
||||
|
||||
for zone in /sys/class/thermal/thermal_zone*; do
|
||||
[ -d "$zone" ] || continue
|
||||
|
||||
name=$(basename "$zone")
|
||||
type=$(cat "$zone/type" 2>/dev/null || echo "unknown")
|
||||
data=$(cat "$zone/temp" 2>/dev/null)
|
||||
desc=$(zone_map "$type")
|
||||
|
||||
if [ -n "$data" ] && [ "$data" != "N/A" ]; then
|
||||
# Convert millidegrees to degrees Celsius
|
||||
temp_c=$(awk "BEGIN {printf \"%.1f\", $data / 1000}")
|
||||
printf "%-20s %8s°C %s\n" "$name" "$temp_c" "$desc"
|
||||
else
|
||||
printf "%-20s %8s %s\n" "$name" "N/A" "$desc"
|
||||
fi
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
hwmon()
|
||||
{
|
||||
tmpfile=$(mktemp)
|
||||
|
||||
echo "Hardware Monitors"
|
||||
echo "================="
|
||||
echo
|
||||
|
||||
phy_map=$(build_phy_map)
|
||||
|
||||
for hwmon in /sys/class/hwmon/hwmon*; do
|
||||
[ -d "$hwmon" ] || continue
|
||||
|
||||
name=$(basename "$hwmon")
|
||||
data=$(cat "$hwmon/temp1_input" 2>/dev/null)
|
||||
|
||||
# Try to find the associated network interface
|
||||
iface=
|
||||
if [ -L "$hwmon/of_node" ]; then
|
||||
hwmon_of_node=$(readlink -f "$hwmon/of_node" 2>/dev/null)
|
||||
if [ -n "$hwmon_of_node" ]; then
|
||||
iface=$(echo "$phy_map" | grep "^$hwmon_of_node:" | cut -d: -f2)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$iface" ]; then
|
||||
description="Phy $iface temperature"
|
||||
else
|
||||
description="N/A"
|
||||
fi
|
||||
|
||||
if [ -n "$data" ] && [ "$data" != "N/A" ]; then
|
||||
# Convert millidegrees to degrees Celsius
|
||||
temp_c=$(awk "BEGIN {printf \"%.1f\", $data / 1000}")
|
||||
# Format: sortkey|hwmon|temp|description (sortkey for natural sort by interface)
|
||||
printf "%s|%-20s %8s°C %s\n" "$iface" "$name" "$temp_c" "$description" >> "$tmpfile"
|
||||
else
|
||||
printf "%s|%-20s %8s %s\n" "$iface" "$name" "N/A" "$description" >> "$tmpfile"
|
||||
fi
|
||||
done
|
||||
|
||||
# Sort by interface name naturally (e2 before e10), with N/A entries at the end
|
||||
# Then strip the sort key before displaying
|
||||
sort -V -t'|' -k1,1 "$tmpfile" | cut -d'|' -f2-
|
||||
rm -f "$tmpfile"
|
||||
echo
|
||||
}
|
||||
|
||||
[ -n "$1" ] || { echo "usage: $0 OUT-DIR"; exit 1; }
|
||||
work="$1"/system
|
||||
mkdir -p "${work}"
|
||||
|
||||
thermal_zones > "${work}"/temperature.txt
|
||||
hwmon >> "${work}"/temperature.txt
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
ecc_stat()
|
||||
{
|
||||
local chan=
|
||||
local base=
|
||||
|
||||
for chan in 0 1; do
|
||||
base=$((0xf0020360 + 0x200 * chan))
|
||||
|
||||
echo "DRAM Channel $chan ECC Status"
|
||||
echo -n " Log config: "; devmem $((base + 0x0)) 32
|
||||
echo -n " 1b errors: "; devmem $((base + 0x4)) 32
|
||||
echo -n " Info 0: "; devmem $((base + 0x8)) 32
|
||||
echo -n " Info 1: "; devmem $((base + 0xc)) 32
|
||||
echo
|
||||
done
|
||||
}
|
||||
|
||||
[ -n "$1" ] || { echo "usage: $0 OUT-DIR"; exit 1; }
|
||||
work="$1"/marvell-cn913x
|
||||
mkdir -p "${work}"
|
||||
|
||||
ecc_stat >"${work}"/ecc-stat
|
||||
@@ -15,34 +15,18 @@ export PROMPT_COMMAND=prompt_command
|
||||
# update the values of LINES and COLUMNS.
|
||||
shopt -s checkwinsize
|
||||
|
||||
# don't put duplicate lines or lines starting with space in the history.
|
||||
export HISTCONTROL=ignoreboth
|
||||
|
||||
# append to the history file, don't overwrite it
|
||||
shopt -s histappend
|
||||
|
||||
# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
|
||||
export HISTSIZE=1000
|
||||
export HISTFILESIZE=2000
|
||||
|
||||
# case-insensitive filename completion
|
||||
bind "set completion-ignore-case on"
|
||||
|
||||
# show all completions immediately instead of ringing bell
|
||||
bind "set show-all-if-ambiguous on"
|
||||
|
||||
log()
|
||||
{
|
||||
local fn="/var/log/syslog"
|
||||
[ -n "$1" ] && fn="/var/log/$1"
|
||||
less +G -r "$fn"
|
||||
less +G "$fn"
|
||||
}
|
||||
|
||||
follow()
|
||||
{
|
||||
local fn="/var/log/syslog"
|
||||
[ -n "$1" ] && fn="/var/log/$1"
|
||||
less +F -r "$fn"
|
||||
tail -F "$fn"
|
||||
}
|
||||
|
||||
_logfile_completions()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
service name:netopeer notify:none log env:/etc/default/confd \
|
||||
[12345] <pid/confd> netopeer2-server -F -t $CONFD_TIMEOUT -v 1 \
|
||||
-- NETCONF server
|
||||
@@ -1,5 +1,5 @@
|
||||
set G_MESSAGES_DEBUG=nocolor
|
||||
service [2345] <service/dbus/running> \
|
||||
env:-/etc/default/rauc \
|
||||
env:-/etc/default/rauc log:prio:user.notice \
|
||||
rauc service $RAUC_ARGS -- Software update service
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Use <pid/syslogd> as barrier for other system tasks and service that
|
||||
# rely on modules, firmware, and device nodes to be ready.
|
||||
service if:udevd nowarn env:-/etc/default/sysklogd <run/udevadm:post/success> \
|
||||
[S0123456789] syslogd -8 -F $SYSLOGD_ARGS -- System log daemon
|
||||
[S0123456789] syslogd -F $SYSLOGD_ARGS -- System log daemon
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../available/netconf.conf
|
||||
@@ -1,66 +0,0 @@
|
||||
# /etc/inputrc - global inputrc for libreadline
|
||||
|
||||
# Be 8 bit clean.
|
||||
set input-meta on
|
||||
set output-meta on
|
||||
|
||||
# To allow the use of 8bit-characters like the german umlauts, uncomment
|
||||
# the line below. However this makes the meta key not work as a meta key,
|
||||
# which is annoying to those which don't need to type in 8-bit characters.
|
||||
|
||||
# set convert-meta off
|
||||
|
||||
# try to enable the application keypad when it is called. Some systems
|
||||
# need this to enable the arrow keys.
|
||||
# set enable-keypad on
|
||||
|
||||
# see /usr/share/doc/bash/inputrc.arrows for other codes of arrow keys
|
||||
|
||||
# do not bell on tab-completion
|
||||
# set bell-style none
|
||||
# set bell-style visible
|
||||
|
||||
# some defaults / modifications for the emacs mode
|
||||
$if mode=emacs
|
||||
|
||||
# allow the use of the Home/End keys
|
||||
"\e[1~": beginning-of-line
|
||||
"\e[4~": end-of-line
|
||||
|
||||
# allow the use of the Delete/Insert keys
|
||||
"\e[3~": delete-char
|
||||
"\e[2~": quoted-insert
|
||||
|
||||
# mappings for "page up" and "page down" to step to the beginning/end
|
||||
# of the history
|
||||
# "\e[5~": beginning-of-history
|
||||
# "\e[6~": end-of-history
|
||||
|
||||
# alternate mappings for "page up" and "page down" to search the history
|
||||
"\e[5~": history-search-backward
|
||||
"\e[6~": history-search-forward
|
||||
|
||||
# mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving
|
||||
"\e[1;5C": forward-word
|
||||
"\e[1;5D": backward-word
|
||||
"\e[5C": forward-word
|
||||
"\e[5D": backward-word
|
||||
"\e\e[C": forward-word
|
||||
"\e\e[D": backward-word
|
||||
|
||||
$if term=rxvt
|
||||
"\e[7~": beginning-of-line
|
||||
"\e[8~": end-of-line
|
||||
"\eOc": forward-word
|
||||
"\eOd": backward-word
|
||||
$endif
|
||||
|
||||
# for non RH/Debian xterm, can't hurt for RH/Debian xterm
|
||||
# "\eOH": beginning-of-line
|
||||
# "\eOF": end-of-line
|
||||
|
||||
# for freebsd console
|
||||
# "\e[H": beginning-of-line
|
||||
# "\e[F": end-of-line
|
||||
|
||||
$endif
|
||||
@@ -90,9 +90,9 @@ reset-reason {
|
||||
|
||||
# Monitors file descriptor leaks based on /proc/sys/fs/file-nr
|
||||
filenr {
|
||||
# enabled = true
|
||||
interval = 300
|
||||
logmark = false
|
||||
enabled = true
|
||||
interval = 3600
|
||||
logmark = true
|
||||
warning = 0.9
|
||||
critical = 1.0
|
||||
# script = "/path/to/alt-reboot-action.sh"
|
||||
@@ -102,33 +102,42 @@ filenr {
|
||||
# The script is called with fsmon as the first argument and there
|
||||
# are two environment variables FSMON_NAME, for the monitored path,
|
||||
# and FSMON_TYPE indicating either 'blocks' or 'inodes'.
|
||||
#fsmon /var {
|
||||
# enabled = true
|
||||
# interval = 300
|
||||
# logmark = false
|
||||
# warning = 0.95
|
||||
# critical = 1.0
|
||||
# script = "/path/to/alt-reboot-action.sh"
|
||||
#}
|
||||
|
||||
# Monitors load average based on sysinfo() from /proc/loadavg
|
||||
# The level is composed from the average of the 1 and 5 min marks.
|
||||
loadavg {
|
||||
# enabled = true
|
||||
interval = 300
|
||||
logmark = false
|
||||
warning = 1.0
|
||||
critical = 2.0
|
||||
fsmon /var {
|
||||
enabled = true
|
||||
interval = 3600
|
||||
logmark = true
|
||||
warning = 0.95
|
||||
critical = 1.0
|
||||
# script = "/path/to/alt-reboot-action.sh"
|
||||
}
|
||||
|
||||
fsmon /tmp {
|
||||
enabled = true
|
||||
interval = 3600
|
||||
logmark = true
|
||||
warning = 0.95
|
||||
critical = 1.0
|
||||
# script = "/path/to/alt-reboot-action.sh"
|
||||
}
|
||||
|
||||
# Monitors load average based on sysinfo() from /proc/loadavg
|
||||
# The level is composed from the average of the 1 and 5 min marks.
|
||||
#loadavg {
|
||||
# enabled = true
|
||||
# interval = 300
|
||||
# logmark = true
|
||||
# warning = 1.0
|
||||
# critical = 2.0
|
||||
# script = "/path/to/alt-reboot-action.sh"
|
||||
#}
|
||||
|
||||
# Monitors free RAM based on data from /proc/meminfo
|
||||
meminfo {
|
||||
# enabled = true
|
||||
interval = 300
|
||||
logmark = false
|
||||
enabled = true
|
||||
interval = 3600
|
||||
logmark = true
|
||||
warning = 0.9
|
||||
critical = 0.95
|
||||
critical = 0.97
|
||||
# script = "/path/to/alt-reboot-action.sh"
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
# -K :: exit immediately when an interrupt character (usually ^C) is typed
|
||||
# -R :: Almost raw control charachters, only ANSI color escape sequences and
|
||||
# OSC 8 hyperlink sequences are output. Allows veritcal scrolling
|
||||
# -r :: Causes "raw" control characters to be displayed, including unicode.
|
||||
# -X :: No termcap initialization and deinitialization set to the terminal.
|
||||
# This is what leaves the contents of the output on screen.
|
||||
|
||||
export LESS="-P %f (press h for help or q to quit)"
|
||||
export LANG=en_US.UTF-8
|
||||
|
||||
less -rIKd -FX "$@"
|
||||
less -RIKd -FX "$@"
|
||||
|
||||
@@ -1,49 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
IFQUIRKSFILE=${IFQUIRKSFILE:-/etc/product/interface-quirks.json}
|
||||
|
||||
#!/bin/sh
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "usage: $0 <quirk-name> <ifname>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
quirk=$1
|
||||
ifname=$2
|
||||
|
||||
[ -f "$IFQUIRKSFILE" ] || { echo false && exit; }
|
||||
|
||||
match()
|
||||
{
|
||||
jq -e \
|
||||
--arg quirk "$quirk" --arg pattern "$1" \
|
||||
'.[$pattern][$quirk]' "$IFQUIRKSFILE" >/dev/null || return
|
||||
|
||||
echo true
|
||||
exit 0
|
||||
}
|
||||
|
||||
ethtoolmatch()
|
||||
{
|
||||
local pattern="${1#@ethtool:}"
|
||||
|
||||
grep -qFxvf \
|
||||
<(ethtool -i "$ifname") \
|
||||
<(echo -n "$pattern" | awk -v FS="=" -v RS=";" '{ printf("%s: %s\n", $1, $2); }') \
|
||||
&& return
|
||||
|
||||
match "@ethtool:$pattern"
|
||||
}
|
||||
|
||||
|
||||
for pattern in $(jq -r 'keys[]' "$IFQUIRKSFILE"); do
|
||||
case "$pattern" in
|
||||
@ethtool:*)
|
||||
ethtoolmatch "$pattern"
|
||||
;;
|
||||
"$ifname")
|
||||
match "$ifname"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "false"
|
||||
if [ -f "/etc/product/interface-quirks.json" ]; then
|
||||
echo "$(jq -r --arg iface "$ifname" --arg quirk "$quirk" '.[$iface][$quirk] // "false"' /etc/product/interface-quirks.json)"
|
||||
else
|
||||
echo "false"
|
||||
fi
|
||||
|
||||
@@ -34,12 +34,6 @@ ports=$(devlink -j port | jq -r '.port[]
|
||||
| select(.flavour == "physical")
|
||||
| .netdev')
|
||||
for iface in $ports; do
|
||||
# On systems with multiple switch trees, a port may be _both_
|
||||
# a physical port, registered with devlink, _and_ a DSA
|
||||
# port. In those cases, hold on to our initial "internal"
|
||||
# classification.
|
||||
[ $(ip -j link show dev "$iface" | jq -r '.[0].group') = internal ] && continue
|
||||
|
||||
ip link set "$iface" group port
|
||||
done
|
||||
|
||||
|
||||
@@ -23,22 +23,12 @@ fi
|
||||
|
||||
log()
|
||||
{
|
||||
logger -I $$ -t udhcpc -p user.notice "${interface}: $*"
|
||||
}
|
||||
|
||||
dbg()
|
||||
{
|
||||
logger -I $$ -t udhcpc -p user.debug "${interface}: $*"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
logger -I $$ -t udhcpc -p user.err "${interface}: $*"
|
||||
logger -I $$ -t udhcpc -p user.notice "$*"
|
||||
}
|
||||
|
||||
wait_for_ipv6_default_route()
|
||||
{
|
||||
dbg "waiting for IPv6 default route to be installed."
|
||||
log "waiting for IPv6 default route to be installed."
|
||||
while [ $IF_WAIT_DELAY -gt 0 ]; do
|
||||
if ip -6 route list proto dhcp dev $interface | grep -q default; then
|
||||
return
|
||||
@@ -47,7 +37,7 @@ wait_for_ipv6_default_route()
|
||||
printf "."
|
||||
: $((IF_WAIT_DELAY -= 1))
|
||||
done
|
||||
err "Timed out waiting for IPv6 default route!"
|
||||
log "Timed out witing for IPv6 default route!"
|
||||
}
|
||||
|
||||
# RFC3442: If the DHCP server returns both a Classless
|
||||
@@ -60,7 +50,7 @@ set_dhcp_routes()
|
||||
# format: dest1/mask gw1 ... destn/mask gwn
|
||||
set -- $staticroutes
|
||||
while [ -n "$1" -a -n "$2" ]; do
|
||||
dbg "adding route $1 via $2 metric $metric tag 100"
|
||||
log "adding route $1 via $2 metric $metric tag 100"
|
||||
echo "ip route $1 $2 $metric tag 100" >> "$NEXT"
|
||||
shift 2
|
||||
done
|
||||
@@ -79,7 +69,7 @@ set_dhcp_routes()
|
||||
|
||||
clr_dhcp_routes()
|
||||
{
|
||||
log "deleting DHCP routes"
|
||||
log "deleting DHCP routes from $interface"
|
||||
[ -f "$NAME" ] || return
|
||||
rm "$NAME"
|
||||
|
||||
@@ -94,7 +84,7 @@ clr_dhcp_addresses()
|
||||
for addr in $addrs; do
|
||||
ip="$(echo "$addr" | jq -r '."local"')"
|
||||
prefix="$(echo "$addr" | jq -r '."prefixlen"')"
|
||||
log "removing $ip/$prefix"
|
||||
log "removing $ip/$prefix from $interface"
|
||||
ip addr del "$ip/$prefix" dev "$interface"
|
||||
done
|
||||
}
|
||||
@@ -152,12 +142,12 @@ case "$ACTION" in
|
||||
fi
|
||||
|
||||
if [ -n "$search_list" ]; then
|
||||
dbg "adding search $search_list"
|
||||
log "adding search $search_list"
|
||||
echo "search $search_list # $interface" >> $RESOLV_CONF
|
||||
fi
|
||||
|
||||
for i in $dns ; do
|
||||
dbg "adding dns $i"
|
||||
log "adding dns $i"
|
||||
echo "nameserver $i # $interface" >> $RESOLV_CONF
|
||||
resolvconf -u
|
||||
done
|
||||
@@ -165,7 +155,7 @@ case "$ACTION" in
|
||||
if [ -n "$ntpsrv" ]; then
|
||||
truncate -s 0 "$NTPFILE"
|
||||
for srv in $ntpsrv; do
|
||||
dbg "got NTP server $srv"
|
||||
log "got NTP server $srv"
|
||||
echo "server $srv iburst" >> "$NTPFILE"
|
||||
done
|
||||
chronyc reload sources >/dev/null
|
||||
|
||||
@@ -33,7 +33,7 @@ for tgt in "${boot_targets}"; do
|
||||
setexpr ixmenu_n ${ixmenu_n} + 1
|
||||
|
||||
if load ${devtype} ${devnum}:${auxpart} ${loadaddr} /uboot.env; then
|
||||
env import -c ${loadaddr} ${filesize} BOOT_ORDER DEBUG ethact
|
||||
env import -b ${loadaddr} ${filesize} BOOT_ORDER DEBUG ethact
|
||||
fi
|
||||
|
||||
if test -n "${DEBUG}"; then
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -461,8 +461,16 @@ CONFIG_DEBUG_FS=y
|
||||
# CONFIG_SLUB_DEBUG is not set
|
||||
CONFIG_DEBUG_RODATA_TEST=y
|
||||
CONFIG_DEBUG_WX=y
|
||||
CONFIG_SOFTLOCKUP_DETECTOR=y
|
||||
CONFIG_PANIC_ON_OOPS=y
|
||||
CONFIG_PANIC_TIMEOUT=20
|
||||
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR_PREFER_BUDDY=y
|
||||
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
|
||||
CONFIG_BOOTPARAM_HUNG_TASK_PANIC=y
|
||||
CONFIG_WQ_WATCHDOG=y
|
||||
CONFIG_WQ_CPU_INTENSIVE_REPORT=y
|
||||
CONFIG_TEST_LOCKUP=m
|
||||
# CONFIG_SCHED_DEBUG is not set
|
||||
CONFIG_STACKTRACE=y
|
||||
CONFIG_RCU_CPU_STALL_TIMEOUT=60
|
||||
|
||||
@@ -6,12 +6,6 @@ mountprefix=/var/lib/rauc/mnt
|
||||
bundle-formats=-plain
|
||||
max-bundle-download-size=1073741824
|
||||
|
||||
[log.event-log]
|
||||
filename=/var/log/upgrade-json.log
|
||||
format=json-pretty
|
||||
max-size=1M
|
||||
max-files=5
|
||||
|
||||
[keyring]
|
||||
directory=/etc/rauc/keys
|
||||
|
||||
|
||||
@@ -266,7 +266,13 @@ CONFIG_MAGIC_SYSRQ=y
|
||||
CONFIG_DEBUG_FS=y
|
||||
CONFIG_PANIC_ON_OOPS=y
|
||||
CONFIG_PANIC_TIMEOUT=20
|
||||
CONFIG_DETECT_HUNG_TASK=y
|
||||
CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR=y
|
||||
CONFIG_HARDLOCKUP_DETECTOR_PREFER_BUDDY=y
|
||||
CONFIG_BOOTPARAM_HARDLOCKUP_PANIC=y
|
||||
CONFIG_BOOTPARAM_HUNG_TASK_PANIC=y
|
||||
CONFIG_WQ_WATCHDOG=y
|
||||
CONFIG_WQ_CPU_INTENSIVE_REPORT=y
|
||||
CONFIG_TEST_LOCKUP=m
|
||||
CONFIG_FUNCTION_TRACER=y
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
|
||||
@@ -7,12 +7,6 @@ mountprefix=/var/lib/rauc/mnt
|
||||
bundle-formats=-plain
|
||||
max-bundle-download-size=1073741824
|
||||
|
||||
[log.event-log]
|
||||
filename=/var/log/upgrade-json.log
|
||||
format=json-pretty
|
||||
max-size=1M
|
||||
max-files=5
|
||||
|
||||
[keyring]
|
||||
directory=/etc/rauc/keys
|
||||
|
||||
|
||||
+1
-1
Submodule buildroot updated: d1ff760036...ba5426091e
+16
-14
@@ -8,7 +8,6 @@ BR2_CCACHE=y
|
||||
BR2_CCACHE_DIR="${BR2_EXTERNAL_INFIX_PATH}/.ccache"
|
||||
BR2_ENABLE_DEBUG=y
|
||||
BR2_GLOBAL_PATCH_DIR="${BR2_EXTERNAL_INFIX_PATH}/patches"
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_TARGET_GENERIC_HOSTNAME="infix"
|
||||
BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
|
||||
BR2_INIT_FINIT=y
|
||||
@@ -28,16 +27,16 @@ 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.49"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.63"
|
||||
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
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
|
||||
BR2_PACKAGE_DBUS_CXX=y
|
||||
BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
@@ -62,7 +61,6 @@ BR2_PACKAGE_LIBINPUT=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_NETOPEER2_CLI=y
|
||||
BR2_PACKAGE_NSS_MDNS=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_LINUX_PAM=y
|
||||
BR2_PACKAGE_LIBPAM_RADIUS_AUTH=y
|
||||
BR2_PACKAGE_ONIGURUMA=y
|
||||
@@ -75,13 +73,11 @@ BR2_PACKAGE_ETHTOOL=y
|
||||
BR2_PACKAGE_FPING=y
|
||||
BR2_PACKAGE_FRR=y
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
BR2_PACKAGE_IPERF3=y
|
||||
BR2_PACKAGE_IPROUTE2=y
|
||||
BR2_PACKAGE_IPTABLES_NFTABLES=y
|
||||
BR2_PACKAGE_IPUTILS=y
|
||||
BR2_PACKAGE_LLDPD=y
|
||||
BR2_PACKAGE_MSTPD=y
|
||||
BR2_PACKAGE_MTR=y
|
||||
BR2_PACKAGE_NETCALC=y
|
||||
BR2_PACKAGE_NETCAT_OPENBSD=y
|
||||
BR2_PACKAGE_NETSNMP=y
|
||||
@@ -101,10 +97,8 @@ BR2_PACKAGE_TRACEROUTE=y
|
||||
BR2_PACKAGE_ULOGD=y
|
||||
BR2_PACKAGE_WHOIS=y
|
||||
BR2_PACKAGE_BASH_COMPLETION=y
|
||||
BR2_PACKAGE_NEOFETCH=y
|
||||
BR2_PACKAGE_SUDO=y
|
||||
BR2_PACKAGE_TTYD=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
BR2_PACKAGE_HTOP=y
|
||||
BR2_PACKAGE_IRQBALANCE=y
|
||||
BR2_PACKAGE_KMOD_TOOLS=y
|
||||
@@ -117,6 +111,12 @@ BR2_PACKAGE_RAUC_JSON=y
|
||||
BR2_PACKAGE_SYSKLOGD=y
|
||||
BR2_PACKAGE_SYSKLOGD_LOGGER=y
|
||||
BR2_PACKAGE_WATCHDOGD=y
|
||||
BR2_PACKAGE_WATCHDOGD_GENERIC=y
|
||||
BR2_PACKAGE_WATCHDOGD_LOADAVG=y
|
||||
BR2_PACKAGE_WATCHDOGD_FILENR=y
|
||||
BR2_PACKAGE_WATCHDOGD_MEMINFO=y
|
||||
BR2_PACKAGE_WATCHDOGD_FSMON=y
|
||||
BR2_PACKAGE_WATCHDOGD_TEMPMON
|
||||
BR2_PACKAGE_LESS=y
|
||||
BR2_PACKAGE_MG=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
@@ -131,24 +131,23 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
INFIX_VENDOR_HOME="https://kernelkit.org"
|
||||
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
|
||||
INFIX_VENDOR_HOME="https://github.com/kernelkit"
|
||||
INFIX_DESC="Infix is an operating system based on Linux and modeled with YANG. It can be set up both as a switch, with offloading using switchdev, a router with firewalling, or a secure end device. All while supporting advanced networking scenarios and running Docker containers."
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_ALDER_ALDER=y
|
||||
BR2_PACKAGE_MARVELL_CN9130_CRB=y
|
||||
BR2_PACKAGE_MARVELL_ESPRESSOBIN=y
|
||||
BR2_PACKAGE_STYX_DCP_SC_28P=y
|
||||
BR2_PACKAGE_RASPBERRY_PI_4=y
|
||||
BR2_PACKAGE_BANANA_PI_R3=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
BR2_PACKAGE_CURIOS_HTTPD=y
|
||||
BR2_PACKAGE_CURIOS_NFTABLES=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -170,11 +169,14 @@ BR2_PACKAGE_PODMAN=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
DISK_IMAGE_BOOT_BIN=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.49"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.63"
|
||||
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
|
||||
@@ -94,6 +94,12 @@ BR2_PACKAGE_RAUC_JSON=y
|
||||
BR2_PACKAGE_SYSKLOGD=y
|
||||
BR2_PACKAGE_SYSKLOGD_LOGGER=y
|
||||
BR2_PACKAGE_WATCHDOGD=y
|
||||
BR2_PACKAGE_WATCHDOGD_GENERIC=y
|
||||
BR2_PACKAGE_WATCHDOGD_LOADAVG=y
|
||||
BR2_PACKAGE_WATCHDOGD_FILENR=y
|
||||
BR2_PACKAGE_WATCHDOGD_MEMINFO=y
|
||||
BR2_PACKAGE_WATCHDOGD_FSMON=y
|
||||
BR2_PACKAGE_WATCHDOGD_TEMPMON
|
||||
BR2_PACKAGE_LESS=y
|
||||
BR2_PACKAGE_MG=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
@@ -107,10 +113,10 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
INFIX_VENDOR_HOME="https://kernelkit.org"
|
||||
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
|
||||
INFIX_VENDOR_HOME="https://github.com/kernelkit"
|
||||
INFIX_DESC="Infix is an operating system based on Linux and modeled with YANG. It can be set up both as a switch, with offloading using switchdev, a router with firewalling, or a secure end device. All while supporting advanced networking scenarios and running Docker containers."
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_ALDER_ALDER=y
|
||||
BR2_PACKAGE_MARVELL_CN9130_CRB=y
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
BR2_aarch64=y
|
||||
BR2_TOOLCHAIN_EXTERNAL=y
|
||||
BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y
|
||||
BR2_DL_DIR="$(BR2_EXTERNAL_INFIX_PATH)/dl"
|
||||
BR2_CCACHE=y
|
||||
BR2_CCACHE_DIR="$(BR2_EXTERNAL_INFIX_PATH)/.ccache"
|
||||
BR2_ENABLE_DEBUG=y
|
||||
BR2_PACKAGE_OVERRIDE_FILE="$(BR2_EXTERNAL_INFIX_PATH)/local.mk"
|
||||
BR2_GLOBAL_PATCH_DIR="$(BR2_EXTERNAL_INFIX_PATH)/patches"
|
||||
BR2_SSP_NONE=y
|
||||
BR2_INIT_NONE=y
|
||||
BR2_SYSTEM_BIN_SH_NONE=y
|
||||
# BR2_PACKAGE_BUSYBOX is not set
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
# BR2_TARGET_ROOTFS_TAR is not set
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE=y
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_GIT=y
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_REPO_URL="https://github.com/mtk-openwrt/arm-trusted-firmware-mtk.git"
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_CUSTOM_REPO_VERSION="78a0dfd927bb00ce973a1f8eb4079df0f755887a"
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_PLATFORM="mt7986"
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_FIP=y
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_UBOOT_AS_BL33=y
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_ADDITIONAL_VARIABLES="BOOT_DEVICE=sdmmc DRAM_USE_DDR4=1 USE_MKIMAGE=1 MKIMAGE=$(HOST_DIR)/bin/mkimage"
|
||||
BR2_TARGET_ARM_TRUSTED_FIRMWARE_IMAGES="*.img *.bin"
|
||||
BR2_TARGET_UBOOT=y
|
||||
BR2_TARGET_UBOOT_BUILD_SYSTEM_KCONFIG=y
|
||||
BR2_TARGET_UBOOT_CUSTOM_VERSION=y
|
||||
BR2_TARGET_UBOOT_CUSTOM_VERSION_VALUE="2025.01"
|
||||
BR2_TARGET_UBOOT_BOARD_DEFCONFIG="mt7986a_bpir3_sd"
|
||||
BR2_TARGET_UBOOT_CONFIG_FRAGMENT_FILES="${BR2_EXTERNAL_INFIX_PATH}/board/common/uboot/extras.config ${BR2_EXTERNAL_INFIX_PATH}/src/board/banana-pi-r3/uboot/extras.config ${BR2_EXTERNAL_INFIX_PATH}/src/board/banana-pi-r3/uboot/sd-extras.config"
|
||||
BR2_TARGET_UBOOT_NEEDS_DTC=y
|
||||
BR2_TARGET_UBOOT_FORMAT_DTB=y
|
||||
BR2_TARGET_UBOOT_CUSTOM_DTS_PATH="${BR2_EXTERNAL_INFIX_PATH}/src/board/banana-pi-r3/uboot/*.dtsi"
|
||||
BR2_PACKAGE_HOST_GENIMAGE=y
|
||||
BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
DISK_IMAGE_BOOT_BIN=y
|
||||
DISK_IMAGE_BOOT_DATA="${BINARIES_DIR}/flash-image.bin"
|
||||
DISK_IMAGE_BOOT_OFFSET=0x00200000
|
||||
@@ -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.49"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.44"
|
||||
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"
|
||||
@@ -44,7 +44,6 @@ BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT61=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT73=y
|
||||
@@ -66,7 +65,6 @@ BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_INPUT_EVENT_DAEMON=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
BR2_PACKAGE_RNG_TOOLS=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
|
||||
@@ -93,12 +91,10 @@ BR2_PACKAGE_ETHTOOL=y
|
||||
BR2_PACKAGE_FPING=y
|
||||
BR2_PACKAGE_FRR=y
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
BR2_PACKAGE_IPERF3=y
|
||||
BR2_PACKAGE_IPROUTE2=y
|
||||
BR2_PACKAGE_IPTABLES_NFTABLES=y
|
||||
BR2_PACKAGE_IPUTILS=y
|
||||
BR2_PACKAGE_LLDPD=y
|
||||
BR2_PACKAGE_MTR=y
|
||||
BR2_PACKAGE_NETCALC=y
|
||||
BR2_PACKAGE_NETCAT_OPENBSD=y
|
||||
BR2_PACKAGE_NETSNMP=y
|
||||
@@ -121,7 +117,6 @@ BR2_PACKAGE_WIRELESS_REGDB=y
|
||||
BR2_PACKAGE_WIRELESS_TOOLS=y
|
||||
BR2_PACKAGE_WPA_SUPPLICANT=y
|
||||
BR2_PACKAGE_BASH_COMPLETION=y
|
||||
BR2_PACKAGE_NEOFETCH=y
|
||||
BR2_PACKAGE_SUDO=y
|
||||
BR2_PACKAGE_TTYD=y
|
||||
BR2_PACKAGE_HTOP=y
|
||||
@@ -173,11 +168,11 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
INFIX_VENDOR_HOME="https://kernelkit.org"
|
||||
INFIX_VENDOR_HOME="https://github.com/kernelkit"
|
||||
INFIX_IMAGE_ID="${INFIX_ID}-r2s"
|
||||
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
|
||||
INFIX_DESC="Infix is an operating system based on Linux and modeled with YANG. It can be set up both as a switch, with offloading using switchdev, a router with firewalling, or a secure end device. All while supporting advanced networking scenarios and running Docker containers."
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
|
||||
+17
-15
@@ -11,8 +11,8 @@ BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
|
||||
BR2_INIT_FINIT=y
|
||||
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
|
||||
BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs"
|
||||
BR2_ROOTFS_MERGED_USR=y
|
||||
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
|
||||
BR2_ROOTFS_MERGED_USR=y
|
||||
BR2_SYSTEM_BIN_SH_BASH=y
|
||||
BR2_TARGET_GENERIC_GETTY_PORT="@console"
|
||||
BR2_TARGET_GENERIC_GETTY_TERM="xterm"
|
||||
@@ -39,9 +39,9 @@ BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_INFIX_PATH)/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT61=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE_RALINK_RT73=y
|
||||
@@ -60,22 +60,19 @@ BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
BR2_PACKAGE_RNG_TOOLS=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
|
||||
BR2_PACKAGE_PYTHON3=y
|
||||
BR2_PACKAGE_PYTHON_GUNICORN=y
|
||||
BR2_PACKAGE_LIBSSH_OPENSSL=y
|
||||
BR2_PACKAGE_LIBSSH2=y
|
||||
BR2_PACKAGE_LIBSSH2_OPENSSL=y
|
||||
BR2_PACKAGE_LIBXCRYPT=y
|
||||
BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBINPUT=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_NETOPEER2_CLI=y
|
||||
BR2_PACKAGE_NSS_MDNS=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_LINUX_PAM=y
|
||||
BR2_PACKAGE_LIBPAM_RADIUS_AUTH=y
|
||||
BR2_PACKAGE_ONIGURUMA=y
|
||||
@@ -88,13 +85,11 @@ BR2_PACKAGE_ETHTOOL=y
|
||||
BR2_PACKAGE_FPING=y
|
||||
BR2_PACKAGE_FRR=y
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
BR2_PACKAGE_IPERF3=y
|
||||
BR2_PACKAGE_IPROUTE2=y
|
||||
BR2_PACKAGE_IPTABLES_NFTABLES=y
|
||||
BR2_PACKAGE_IPUTILS=y
|
||||
BR2_PACKAGE_LLDPD=y
|
||||
BR2_PACKAGE_MSTPD=y
|
||||
BR2_PACKAGE_MTR=y
|
||||
BR2_PACKAGE_NETCALC=y
|
||||
BR2_PACKAGE_NETCAT_OPENBSD=y
|
||||
BR2_PACKAGE_NETSNMP=y
|
||||
@@ -114,10 +109,8 @@ BR2_PACKAGE_TRACEROUTE=y
|
||||
BR2_PACKAGE_ULOGD=y
|
||||
BR2_PACKAGE_WHOIS=y
|
||||
BR2_PACKAGE_BASH_COMPLETION=y
|
||||
BR2_PACKAGE_NEOFETCH=y
|
||||
BR2_PACKAGE_SUDO=y
|
||||
BR2_PACKAGE_TTYD=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
BR2_PACKAGE_HTOP=y
|
||||
BR2_PACKAGE_IRQBALANCE=y
|
||||
BR2_PACKAGE_KMOD_TOOLS=y
|
||||
@@ -130,6 +123,12 @@ BR2_PACKAGE_RAUC_JSON=y
|
||||
BR2_PACKAGE_SYSKLOGD=y
|
||||
BR2_PACKAGE_SYSKLOGD_LOGGER=y
|
||||
BR2_PACKAGE_WATCHDOGD=y
|
||||
BR2_PACKAGE_WATCHDOGD_GENERIC=y
|
||||
BR2_PACKAGE_WATCHDOGD_LOADAVG=y
|
||||
BR2_PACKAGE_WATCHDOGD_FILENR=y
|
||||
BR2_PACKAGE_WATCHDOGD_MEMINFO=y
|
||||
BR2_PACKAGE_WATCHDOGD_FSMON=y
|
||||
BR2_PACKAGE_WATCHDOGD_TEMPMON
|
||||
BR2_PACKAGE_LESS=y
|
||||
BR2_PACKAGE_MG=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
@@ -168,14 +167,16 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
INFIX_VENDOR_HOME="https://kernelkit.org"
|
||||
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
|
||||
INFIX_VENDOR_HOME="https://github.com/kernelkit"
|
||||
INFIX_DESC="Infix is an operating system based on Linux and modeled with YANG. It can be set up both as a switch, with offloading using switchdev, a router with firewalling, or a secure end device. All while supporting advanced networking scenarios and running Docker containers."
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_CONFD=y
|
||||
# BR2_PACKAGE_CONFD_TEST_MODE is not set
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -197,11 +198,12 @@ BR2_PACKAGE_PODMAN=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
# GNS3_APPLIANCE is not set
|
||||
|
||||
@@ -14,8 +14,6 @@ BR2_SYSTEM_BIN_SH_NONE=y
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
# BR2_PACKAGE_BUSYBOX is not set
|
||||
BR2_PACKAGE_RPI_FIRMWARE=y
|
||||
BR2_PACKAGE_RPI_FIRMWARE_VARIANT_PI4=y
|
||||
BR2_PACKAGE_RPI_FIRMWARE_VARIANT_PI4_X=y
|
||||
BR2_PACKAGE_RPI_FIRMWARE_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/src/board/raspberry-pi-4/config.txt"
|
||||
BR2_PACKAGE_RPI_FIRMWARE_CMDLINE_FILE="${BR2_EXTERNAL_INFIX_PATH}/src/board/raspberry-pi-4/cmdline.txt"
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
@@ -36,5 +34,4 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN=y
|
||||
# GNS3_APPLIANCE is not set
|
||||
|
||||
+16
-14
@@ -7,7 +7,6 @@ BR2_CCACHE=y
|
||||
BR2_CCACHE_DIR="${BR2_EXTERNAL_INFIX_PATH}/.ccache"
|
||||
BR2_ENABLE_DEBUG=y
|
||||
BR2_GLOBAL_PATCH_DIR="${BR2_EXTERNAL_INFIX_PATH}/patches"
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_TARGET_GENERIC_HOSTNAME="infix"
|
||||
BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
|
||||
BR2_INIT_FINIT=y
|
||||
@@ -27,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.49"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.63"
|
||||
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
|
||||
@@ -35,9 +34,9 @@ BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_E2FSPROGS_RESIZE2FS=y
|
||||
BR2_PACKAGE_DBUS_CXX=y
|
||||
BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
@@ -60,7 +59,6 @@ BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_NETOPEER2_CLI=y
|
||||
BR2_PACKAGE_NSS_MDNS=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_LINUX_PAM=y
|
||||
BR2_PACKAGE_LIBPAM_RADIUS_AUTH=y
|
||||
BR2_PACKAGE_ONIGURUMA=y
|
||||
@@ -73,13 +71,11 @@ BR2_PACKAGE_ETHTOOL=y
|
||||
BR2_PACKAGE_FPING=y
|
||||
BR2_PACKAGE_FRR=y
|
||||
# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set
|
||||
BR2_PACKAGE_IPERF3=y
|
||||
BR2_PACKAGE_IPROUTE2=y
|
||||
BR2_PACKAGE_IPTABLES_NFTABLES=y
|
||||
BR2_PACKAGE_IPUTILS=y
|
||||
BR2_PACKAGE_LLDPD=y
|
||||
BR2_PACKAGE_MSTPD=y
|
||||
BR2_PACKAGE_MTR=y
|
||||
BR2_PACKAGE_NETCALC=y
|
||||
BR2_PACKAGE_NETCAT_OPENBSD=y
|
||||
BR2_PACKAGE_NETSNMP=y
|
||||
@@ -99,10 +95,8 @@ BR2_PACKAGE_TRACEROUTE=y
|
||||
BR2_PACKAGE_ULOGD=y
|
||||
BR2_PACKAGE_WHOIS=y
|
||||
BR2_PACKAGE_BASH_COMPLETION=y
|
||||
BR2_PACKAGE_NEOFETCH=y
|
||||
BR2_PACKAGE_SUDO=y
|
||||
BR2_PACKAGE_TTYD=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
BR2_PACKAGE_HTOP=y
|
||||
BR2_PACKAGE_IRQBALANCE=y
|
||||
BR2_PACKAGE_KMOD_TOOLS=y
|
||||
@@ -115,6 +109,12 @@ BR2_PACKAGE_RAUC_JSON=y
|
||||
BR2_PACKAGE_SYSKLOGD=y
|
||||
BR2_PACKAGE_SYSKLOGD_LOGGER=y
|
||||
BR2_PACKAGE_WATCHDOGD=y
|
||||
BR2_PACKAGE_WATCHDOGD_GENERIC=y
|
||||
BR2_PACKAGE_WATCHDOGD_LOADAVG=y
|
||||
BR2_PACKAGE_WATCHDOGD_FILENR=y
|
||||
BR2_PACKAGE_WATCHDOGD_MEMINFO=y
|
||||
BR2_PACKAGE_WATCHDOGD_FSMON=y
|
||||
BR2_PACKAGE_WATCHDOGD_TEMPMON
|
||||
BR2_PACKAGE_LESS=y
|
||||
BR2_PACKAGE_MG=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
@@ -137,19 +137,18 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
INFIX_VENDOR_HOME="https://kernelkit.org"
|
||||
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
|
||||
INFIX_VENDOR_HOME="https://github.com/kernelkit"
|
||||
INFIX_DESC="Infix is an operating system based on Linux and modeled with YANG. It can be set up both as a switch, with offloading using switchdev, a router with firewalling, or a secure end device. All while supporting advanced networking scenarios and running Docker containers."
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
BR2_PACKAGE_CURIOS_HTTPD=y
|
||||
BR2_PACKAGE_CURIOS_NFTABLES=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -171,11 +170,14 @@ BR2_PACKAGE_PODMAN=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_DONGLE_REALTEK=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
GNS3_APPLIANCE_RAM=512
|
||||
|
||||
@@ -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.49"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.63"
|
||||
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
|
||||
@@ -93,6 +93,12 @@ BR2_PACKAGE_RAUC_JSON=y
|
||||
BR2_PACKAGE_SYSKLOGD=y
|
||||
BR2_PACKAGE_SYSKLOGD_LOGGER=y
|
||||
BR2_PACKAGE_WATCHDOGD=y
|
||||
BR2_PACKAGE_WATCHDOGD_GENERIC=y
|
||||
BR2_PACKAGE_WATCHDOGD_LOADAVG=y
|
||||
BR2_PACKAGE_WATCHDOGD_FILENR=y
|
||||
BR2_PACKAGE_WATCHDOGD_MEMINFO=y
|
||||
BR2_PACKAGE_WATCHDOGD_FSMON=y
|
||||
BR2_PACKAGE_WATCHDOGD_TEMPMON=y
|
||||
BR2_PACKAGE_LESS=y
|
||||
BR2_PACKAGE_MG=y
|
||||
BR2_PACKAGE_NANO=y
|
||||
@@ -114,10 +120,10 @@ BR2_PACKAGE_HOST_RAUC=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SUPPORT=y
|
||||
BR2_PACKAGE_HOST_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
INFIX_VENDOR_HOME="https://kernelkit.org"
|
||||
INFIX_DESC="Infix is an immutable, friendly, and secure operating system that turns any ARM or x86 device into a powerful, manageable network appliance. Deploy on anything from $35 Raspberry Pi boards to enterprise switches as routers, IoT gateways, or edge devices. Infix models Linux networking features using YANG so you can manage your devices using NETCONF/RESTCONF APIs and focus on your business logic running in isolated containers."
|
||||
INFIX_VENDOR_HOME="https://github.com/kernelkit"
|
||||
INFIX_DESC="Infix is an operating system based on Linux and modeled with YANG. It can be set up both as a switch, with offloading using switchdev, a router with firewalling, or a secure end device. All while supporting advanced networking scenarios and running Docker containers."
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_DOC="https://github.com/kernelkit/infix/tree/main/doc"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
|
||||
+32
-24
@@ -3,36 +3,44 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v25.09.0][] - 2025-09-30
|
||||
[v25.08.2][] - 2025-12-19
|
||||
-------------------------
|
||||
|
||||
### Changes
|
||||
|
||||
- Upgrade Buildroot to 2025.02.6 (LTS)
|
||||
- Upgrade Linux kernel to 6.12.49 (LTS)
|
||||
- Upgrade libyang to 3.13.5
|
||||
- Upgrade sysrepo to 3.7.11
|
||||
- Upgrade netopeer2 (NETCONF) to 2.4.5
|
||||
- Upgrade rousette (RESTCONF) to v2
|
||||
- Add support for [Banana Pi R3][BPI-R3], a 7 port switch with 2 WiFi chip
|
||||
- Add neofetch system information tool for system introspection, issue #1143
|
||||
- Add mtr and iperf3 network diagnostic tools, issue #1144
|
||||
- Improve default bash settings with better history handling and tab completion
|
||||
- cli: new `terminal reset` and `terminal resize` convenience commands
|
||||
- Upgrade Linux kernel to 6.12.63 (LTS)
|
||||
- Enable workaround for issue #670 by disabling iitod on styx platform. This
|
||||
prohibits software control of LEDs, leaving the default HW control, which
|
||||
has proven more stable on this platform
|
||||
- Add support for configurable OSPF debug logging, issue #1281. Debug options
|
||||
can now be enabled per category (bfd, packet, ism, nsm, default-information,
|
||||
nssa). All debug options are disabled by default to prevent log flooding in
|
||||
production environments. See the documentation for usage examples
|
||||
- Add support data collection script, useful when troubleshooting issues on
|
||||
deployed systems. Gathers system information, logs, and more. Issue #1287
|
||||
- Enable kernel panic on lockups + hung tasks => console log + reboot. Also,
|
||||
enable watchdogd resource monitors, logs: memory/file system + descriptor
|
||||
usage. Issue #1318
|
||||
- Enable CN9130 HW watchdog, and kernel `test_lockup` module, issue #1320
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix #1080: Error message in log from rauc, deprecated 'Install' D-Bus method
|
||||
- Fix #1100: Reduce DHCP client logging verbosity by 70% and include interface
|
||||
names in log messages for easier troubleshooting
|
||||
- Fix #1119: CLI UX regression, restore proper behavior for `no enabled` command
|
||||
- Fix #1155: `show ospf` commands regression
|
||||
- Fix #1150: `show-legacy` wrapper permissions
|
||||
- Fix #1161: error in log during boot about unsupported command
|
||||
- Fix #1169: Expected neighbors not shown in sysrepocfg
|
||||
- Fixes for unicode translation in log and pager outputs as well as `syslogd`
|
||||
- Fix #981: copying any file, including `running-config`, to the persistent
|
||||
back-end store for `startup-config`, does not take
|
||||
- Fix #1203: copying any file, including `startup-config`, to `running-config`
|
||||
does not take
|
||||
|
||||
[BPI-R3]: https://wiki.banana-pi.org/Banana_Pi_BPI-R3
|
||||
[v25.08.1][] - 2025-10-03
|
||||
-------------------------
|
||||
|
||||
### Changes
|
||||
- N/A
|
||||
|
||||
### Fixes
|
||||
- Fix #1150: `show-legacy` wrapper permissions
|
||||
- Fix #1155: `show ospf` commands regression
|
||||
- Fix #1169: Expected OSPF neighbors not shown in `sysrepocfg` when the
|
||||
system has at least one non-OSPF interface
|
||||
|
||||
[v25.08.0][] - 2025-09-01
|
||||
-------------------------
|
||||
@@ -1643,8 +1651,8 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
- N/A
|
||||
|
||||
[buildroot]: https://buildroot.org/
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.09.0...HEAD
|
||||
[v25.09.0]: https://github.com/kernelkit/infix/compare/v25.08.0...v26.09.0
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.08.0...HEAD
|
||||
[v25.08.1]: https://github.com/kernelkit/infix/compare/v25.08.0...v26.08.1
|
||||
[v25.08.0]: https://github.com/kernelkit/infix/compare/v25.06.1...v26.08.0
|
||||
[v25.06.0]: https://github.com/kernelkit/infix/compare/v25.05.1...v26.06.0
|
||||
[v25.05.1]: https://github.com/kernelkit/infix/compare/v25.05.0...v25.05.1
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="489.27203mm"
|
||||
height="159.49489mm"
|
||||
viewBox="0 0 489.27202 159.49489"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="logo-large.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.57493546"
|
||||
inkscape:cx="104.35954"
|
||||
inkscape:cy="264.3775"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1385"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer6" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer6"
|
||||
inkscape:label="bg"
|
||||
style="display:none;fill:#ff0000;fill-opacity:1"
|
||||
transform="matrix(1.2831201,0,0,3.2117929,118.15852,-263.46763)">
|
||||
<rect
|
||||
style="fill:#22272e;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect10264"
|
||||
width="163.66356"
|
||||
height="92.471725"
|
||||
x="20.800945"
|
||||
y="69.866936"
|
||||
inkscape:label="bg" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="tagline"
|
||||
style="display:inline"
|
||||
transform="translate(144.84863,-39.0695)">
|
||||
<g
|
||||
aria-label="Immutable . Friendly . Secure"
|
||||
transform="matrix(5.7077268,0,0,6.3072396,-498.93695,-594.0847)"
|
||||
id="text9503"
|
||||
style="font-size:6.44339px;font-family:Laksaman;-inkscape-font-specification:'Laksaman, Normal';stroke:#5c5f5c;stroke-width:0;stroke-miterlimit:0"
|
||||
inkscape:label="linux-netconf"
|
||||
inkscape:export-filename="../../../Pictures/logo-medium.svg"
|
||||
inkscape:export-xdpi="191.95932"
|
||||
inkscape:export-ydpi="191.95932">
|
||||
<path
|
||||
d="m 62.472956,124.15871 h -0.386604 v -4.61347 h 0.386604 z"
|
||||
id="path2380" />
|
||||
<path
|
||||
d="m 67.144418,120.79526 q 0.573462,0 0.869857,0.4317 0.296396,0.43171 0.296396,1.10182 v 1.82993 h -0.386603 v -1.82348 q 0,-0.54125 -0.219075,-0.8763 -0.212632,-0.3415 -0.663669,-0.3415 -0.367274,0 -0.657226,0.28995 -0.289953,0.28995 -0.289953,0.65722 v 2.09411 h -0.386603 v -1.99745 q 0,-1.04383 -0.831197,-1.04383 -0.38016,0 -0.689443,0.32861 -0.309283,0.32861 -0.309283,0.71522 v 1.99745 H 63.491016 V 121.678 q 0,-0.28995 -0.06443,-0.81187 h 0.354386 q 0.06443,0.19975 0.07732,0.58635 0.373717,-0.65722 1.108263,-0.65722 0.670113,0 0.998725,0.73454 0.386604,-0.73454 1.179141,-0.73454 z"
|
||||
id="path2382" />
|
||||
<path
|
||||
d="m 72.969238,120.79526 q 0.573462,0 0.869858,0.4317 0.296396,0.43171 0.296396,1.10182 v 1.82993 h -0.386604 v -1.82348 q 0,-0.54125 -0.219075,-0.8763 -0.212632,-0.3415 -0.663669,-0.3415 -0.367273,0 -0.657226,0.28995 -0.289953,0.28995 -0.289953,0.65722 v 2.09411 h -0.386603 v -1.99745 q 0,-1.04383 -0.831197,-1.04383 -0.38016,0 -0.689443,0.32861 -0.309283,0.32861 -0.309283,0.71522 v 1.99745 H 69.315836 V 121.678 q 0,-0.28995 -0.06443,-0.81187 h 0.354386 q 0.06443,0.19975 0.07732,0.58635 0.373717,-0.65722 1.108263,-0.65722 0.670113,0 0.998726,0.73454 0.386603,-0.73454 1.17914,-0.73454 z"
|
||||
id="path2384" />
|
||||
<path
|
||||
d="m 78.078841,124.14582 -0.36083,0.0838 q -0.115981,-0.32861 -0.154641,-0.64434 -0.431707,0.66367 -1.237131,0.66367 -1.185584,0 -1.185584,-1.53352 v -1.84926 h 0.386604 v 1.77838 q 0,1.28223 0.882744,1.28223 0.457481,0 0.786094,-0.30928 0.328613,-0.31572 0.328613,-0.76032 v -1.99101 h 0.386603 v 2.48715 q 0,0.34795 0.167528,0.79254 z"
|
||||
id="path2386" />
|
||||
<path
|
||||
d="m 79.973201,124.24891 q -0.39949,0 -0.625009,-0.29639 -0.219075,-0.30284 -0.219075,-0.76032 v -2.03611 h -0.657226 v -0.28996 h 0.657226 v -0.97939 l 0.386603,-0.17397 v 1.15336 h 1.050273 v 0.28996 H 79.51572 v 2.03611 q 0,0.73454 0.489698,0.73454 0.302839,0 0.444594,-0.058 l 0.03866,0.28996 q -0.199745,0.0902 -0.515471,0.0902 z"
|
||||
id="path2388" />
|
||||
<path
|
||||
d="m 82.015758,124.24891 q -0.43815,0 -0.747433,-0.25129 -0.302839,-0.25773 -0.302839,-0.66367 0,-1.23068 2.190752,-1.23068 0,-0.47037 -0.225519,-0.72811 -0.225518,-0.25773 -0.663669,-0.25773 -0.354386,0 -1.050272,0.21907 l -0.0451,-0.36727 q 0.541245,-0.17397 1.12115,-0.17397 1.250018,0 1.250018,1.37888 v 1.17914 q 0,0.40594 0.115981,0.80543 l -0.335057,0.0709 -0.135311,-0.5348 q -0.444594,0.55413 -1.172697,0.55413 z m -0.663669,-0.97939 q 0,0.30284 0.186858,0.48325 0.186859,0.17397 0.489698,0.17397 0.386603,0 0.753876,-0.2384 0.373717,-0.24485 0.373717,-0.59279 v -0.67012 q -1.804149,0 -1.804149,0.84409 z"
|
||||
id="path2390" />
|
||||
<path
|
||||
d="m 86.178189,120.79526 q 0.683,0 1.08249,0.47681 0.39949,0.47037 0.39949,1.19847 0,0.77965 -0.412377,1.28223 -0.412377,0.49614 -1.127593,0.49614 -0.766764,0 -1.224244,-0.67011 0,0.32861 -0.01933,0.57991 h -0.354387 q 0.02577,-0.22552 0.02577,-0.80543 v -4.09155 h 0.386603 v 2.22941 q 0.425264,-0.69588 1.243574,-0.69588 z m -1.243574,2.04255 q 0,0.46393 0.328613,0.77965 0.328613,0.30928 0.805424,0.30928 1.204914,0 1.204914,-1.44331 0,-0.61213 -0.30284,-0.98584 -0.302839,-0.38016 -0.863414,-0.38016 -0.489698,0 -0.831197,0.3415 -0.3415,0.3415 -0.3415,0.83119 z"
|
||||
id="path2392" />
|
||||
<path
|
||||
d="m 89.696285,123.92674 -0.03866,0.32217 q -1.224244,-0.058 -1.224244,-1.74615 v -3.24103 h 0.386603 v 3.10571 q 0,0.36728 0.03222,0.61857 0.03222,0.25129 0.115981,0.48325 0.09021,0.23197 0.270623,0.34795 0.180415,0.10953 0.457481,0.10953 z"
|
||||
id="path2394" />
|
||||
<path
|
||||
d="m 90.51459,122.54142 q 0.01933,0.625 0.373717,1.00516 0.36083,0.38016 0.960065,0.38016 0.560575,0 1.153367,-0.23196 l 0.03222,0.31573 q -0.573462,0.2384 -1.224245,0.2384 -0.740989,0 -1.211357,-0.45748 -0.470367,-0.46392 -0.470367,-1.19202 0,-0.77965 0.451037,-1.28868 0.457481,-0.51547 1.192027,-0.51547 1.385329,0 1.417546,1.74616 z m 2.287403,-0.32217 q -0.01289,-0.50259 -0.309282,-0.79898 -0.289953,-0.30284 -0.766764,-0.30284 -0.47681,0 -0.79898,0.31572 -0.322169,0.30929 -0.393047,0.7861 z"
|
||||
id="path2396" />
|
||||
<path
|
||||
d="m 96.345854,123.54658 q 0.135311,0 0.238405,0.1031 0.103095,0.10309 0.103095,0.2384 0,0.13532 -0.103095,0.23197 -0.103094,0.0966 -0.238405,0.0966 -0.135311,0 -0.231962,-0.0966 -0.09665,-0.0967 -0.09665,-0.23197 0,-0.13531 0.09665,-0.2384 0.09665,-0.1031 0.231962,-0.1031 z"
|
||||
id="path2398" />
|
||||
<path
|
||||
d="m 102.20934,121.94218 h -2.07477 v 2.21653 h -0.386608 v -4.61347 h 2.609578 v 0.32217 h -2.22297 v 1.7526 h 2.07477 z"
|
||||
id="path2400" />
|
||||
<path
|
||||
d="m 104.6385,121.13031 q -0.0387,-0.0129 -0.11598,-0.0129 -0.42527,0 -0.72166,0.34794 -0.28996,0.3415 -0.28996,0.9214 v 1.77194 h -0.3866 v -2.21653 q 0,-0.7281 -0.0967,-1.07605 h 0.32861 q 0.1031,0.20619 0.1031,0.65723 0.30928,-0.7281 0.99228,-0.7281 0.0902,0 0.18686,0.0193 z"
|
||||
id="path2402" />
|
||||
<path
|
||||
d="m 105.60501,124.15871 h -0.38661 v -3.29258 h 0.38661 z m -0.20619,-4.60058 q 0.13531,0 0.21907,0.0902 0.0902,0.0838 0.0902,0.21908 0,0.13531 -0.0902,0.21907 -0.0838,0.0773 -0.21907,0.0773 -0.13531,0 -0.21908,-0.0773 -0.0773,-0.0838 -0.0773,-0.21907 0,-0.13531 0.0773,-0.21908 0.0838,-0.0902 0.21908,-0.0902 z"
|
||||
id="path2404" />
|
||||
<path
|
||||
d="m 106.75838,122.54142 q 0.0193,0.625 0.37371,1.00516 0.36083,0.38016 0.96007,0.38016 0.56057,0 1.15336,-0.23196 l 0.0322,0.31573 q -0.57346,0.2384 -1.22424,0.2384 -0.74099,0 -1.21136,-0.45748 -0.47037,-0.46392 -0.47037,-1.19202 0,-0.77965 0.45104,-1.28868 0.45748,-0.51547 1.19203,-0.51547 1.38533,0 1.41754,1.74616 z m 2.2874,-0.32217 q -0.0129,-0.50259 -0.30928,-0.79898 -0.28996,-0.30284 -0.76677,-0.30284 -0.47681,0 -0.79898,0.31572 -0.32217,0.30929 -0.39304,0.7861 z"
|
||||
id="path2406" />
|
||||
<path
|
||||
d="m 113.00202,124.15871 h -0.3866 v -1.86859 q 0,-1.17269 -0.9794,-1.17269 -0.3866,0 -0.70233,0.32861 -0.31572,0.32861 -0.31572,0.71522 v 1.99745 h -0.38661 V 121.678 q 0,-0.28995 -0.0644,-0.81187 h 0.36083 q 0.0644,0.19975 0.0773,0.58635 0.37372,-0.65722 1.10826,-0.65722 1.28868,0 1.28868,1.49486 z"
|
||||
id="path2408" />
|
||||
<path
|
||||
d="m 113.77523,122.47054 q 0,-0.72166 0.39949,-1.19847 0.40593,-0.47681 1.08249,-0.47681 0.81831,0 1.24357,0.69588 v -2.22941 h 0.38661 v 4.09155 q 0,0.57991 0.0258,0.80543 h -0.35439 q -0.0193,-0.2513 -0.0193,-0.57991 -0.45748,0.67011 -1.22424,0.67011 -0.72166,0 -1.13404,-0.49614 -0.40593,-0.50258 -0.40593,-1.28223 z m 1.59152,1.4562 q 0.47681,0 0.80542,-0.30928 0.32861,-0.31572 0.32861,-0.77965 v -0.54769 q 0,-0.49614 -0.3415,-0.83119 -0.33505,-0.3415 -0.83119,-0.3415 -0.56058,0 -0.86342,0.38016 -0.30284,0.38016 -0.30284,0.98584 0,1.44331 1.20492,1.44331 z"
|
||||
id="path2410" />
|
||||
<path
|
||||
d="m 119.15547,123.92674 -0.0387,0.32217 q -1.22424,-0.058 -1.22424,-1.74615 v -3.24103 h 0.3866 v 3.10571 q 0,0.36728 0.0322,0.61857 0.0322,0.25129 0.11598,0.48325 0.0902,0.23197 0.27062,0.34795 0.18042,0.10953 0.45749,0.10953 z"
|
||||
id="path2412" />
|
||||
<path
|
||||
d="m 121.10781,124.15871 q -0.57346,1.28867 -1.52064,1.51419 l -0.0902,-0.30284 q 0.82476,-0.18685 1.25002,-1.32089 0,-0.058 -0.0258,-0.11598 l -1.366,-3.06706 h 0.40593 l 1.23069,2.78999 1.15337,-2.78999 h 0.41237 z"
|
||||
id="path2414" />
|
||||
<path
|
||||
d="m 125.44421,123.54658 q 0.13531,0 0.2384,0.1031 0.1031,0.10309 0.1031,0.2384 0,0.13532 -0.1031,0.23197 -0.10309,0.0966 -0.2384,0.0966 -0.13531,0 -0.23196,-0.0966 -0.0966,-0.0967 -0.0966,-0.23197 0,-0.13531 0.0966,-0.2384 0.0966,-0.1031 0.23196,-0.1031 z"
|
||||
id="path2416" />
|
||||
<path
|
||||
d="m 131.42367,122.95379 q 0,0.59924 -0.45103,0.94718 -0.4446,0.34794 -1.11471,0.34794 -0.76032,0 -1.26935,-0.35438 l 0.15464,-0.32862 q 0.47681,0.36083 1.15337,0.36083 0.50903,0 0.82475,-0.24484 0.31573,-0.24485 0.31573,-0.69589 0,-0.43815 -0.25129,-0.65723 -0.2513,-0.22551 -0.75388,-0.38016 -1.28868,-0.41882 -1.28868,-1.30156 0,-0.54125 0.41238,-0.85697 0.41238,-0.32217 1.01805,-0.32217 0.64434,0 1.05672,0.24485 -0.0129,0.0258 -0.0773,0.14175 -0.058,0.10954 -0.0838,0.16109 -0.41882,-0.22552 -0.90208,-0.22552 -0.45104,0 -0.74743,0.21263 -0.28995,0.21263 -0.28995,0.59924 0,0.63789 0.99872,0.98583 0.32217,0.11599 0.51547,0.21264 0.19975,0.0902 0.39949,0.24484 0.19975,0.15465 0.28995,0.38016 0.0902,0.22552 0.0902,0.52836 z"
|
||||
id="path2418" />
|
||||
<path
|
||||
d="m 132.38373,122.54142 q 0.0193,0.625 0.37372,1.00516 0.36083,0.38016 0.96006,0.38016 0.56058,0 1.15337,-0.23196 l 0.0322,0.31573 q -0.57347,0.2384 -1.22425,0.2384 -0.74099,0 -1.21136,-0.45748 -0.47036,-0.46392 -0.47036,-1.19202 0,-0.77965 0.45103,-1.28868 0.45749,-0.51547 1.19203,-0.51547 1.38533,0 1.41755,1.74616 z m 2.2874,-0.32217 q -0.0129,-0.50259 -0.30928,-0.79898 -0.28995,-0.30284 -0.76676,-0.30284 -0.47681,0 -0.79898,0.31572 -0.32217,0.30929 -0.39305,0.7861 z"
|
||||
id="path2420" />
|
||||
<path
|
||||
d="m 137.3387,121.11743 q -0.61212,0 -0.97295,0.3866 -0.36083,0.3866 -0.36083,1.01806 0,0.625 0.36727,1.01805 0.36727,0.3866 0.96651,0.3866 0.54124,0 0.90207,-0.23196 l 0.13531,0.31573 q -0.42526,0.2384 -1.01805,0.2384 -0.79898,0 -1.26935,-0.46392 -0.47037,-0.47037 -0.47037,-1.2629 0,-0.79254 0.47037,-1.25647 0.47037,-0.47036 1.26935,-0.47036 0.59923,0 1.01805,0.23196 l -0.13531,0.32217 q -0.36083,-0.23196 -0.90207,-0.23196 z"
|
||||
id="path2422" />
|
||||
<path
|
||||
d="m 141.99727,124.14582 -0.36083,0.0838 q -0.11598,-0.32861 -0.15464,-0.64434 -0.4317,0.66367 -1.23713,0.66367 -1.18558,0 -1.18558,-1.53352 v -1.84926 h 0.3866 v 1.77838 q 0,1.28223 0.88275,1.28223 0.45748,0 0.78609,-0.30928 0.32861,-0.31572 0.32861,-0.76032 v -1.99101 h 0.38661 v 2.48715 q 0,0.34795 0.16752,0.79254 z"
|
||||
id="path2424" />
|
||||
<path
|
||||
d="m 144.34911,121.13031 q -0.0387,-0.0129 -0.11598,-0.0129 -0.42526,0 -0.72166,0.34794 -0.28995,0.3415 -0.28995,0.9214 v 1.77194 h -0.38661 v -2.21653 q 0,-0.7281 -0.0967,-1.07605 h 0.32862 q 0.10309,0.20619 0.10309,0.65723 0.30928,-0.7281 0.99228,-0.7281 0.0902,0 0.18686,0.0193 z"
|
||||
id="path2426" />
|
||||
<path
|
||||
d="m 145.08366,122.54142 q 0.0193,0.625 0.37372,1.00516 0.36083,0.38016 0.96006,0.38016 0.56058,0 1.15337,-0.23196 l 0.0322,0.31573 q -0.57346,0.2384 -1.22424,0.2384 -0.74099,0 -1.21136,-0.45748 -0.47036,-0.46392 -0.47036,-1.19202 0,-0.77965 0.45103,-1.28868 0.45748,-0.51547 1.19203,-0.51547 1.38533,0 1.41755,1.74616 z m 2.2874,-0.32217 q -0.0129,-0.50259 -0.30928,-0.79898 -0.28995,-0.30284 -0.76676,-0.30284 -0.47681,0 -0.79898,0.31572 -0.32217,0.30929 -0.39305,0.7861 z"
|
||||
id="path2428" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.308367;stroke-linejoin:bevel"
|
||||
d="m -144.31598,174.51773 v -14.33904 h 0.9251 0.9251 v 14.33904 14.33901 h -0.9251 -0.9251 z"
|
||||
id="path1834" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.308367;stroke-linejoin:bevel"
|
||||
d="m -136.30031,181.22465 c -0.001,-4.1976 -0.0919,-8.78814 -0.20181,-10.20114 l -0.19998,-2.56908 0.89565,0.1023 c 0.8478,0.0966 0.90062,0.1887 0.98885,1.7211 0.0513,0.8904 0.20586,1.61892 0.34352,1.61892 0.13764,0 0.618,-0.53058 1.0674,-1.17906 1.45087,-2.09358 4.23746,-3.17568 6.56441,-2.5491 1.36671,0.36801 3.24026,2.05989 3.81345,3.44367 0.20532,0.49566 0.46053,0.89748 0.56716,0.89292 0.10668,-0.006 0.53985,-0.56889 0.96272,-1.25409 2.58453,-4.18785 8.33854,-4.38975 10.69125,-0.37515 1.35685,2.31528 1.63536,4.25529 1.64063,11.42817 l 0.005,6.55278 h -0.92514 -0.9251 v -6.12522 c 0,-6.42789 -0.25833,-8.71464 -1.18804,-10.51704 -1.54819,-3.00141 -5.32325,-3.35445 -7.81531,-0.73086 -1.64902,1.73601 -1.78946,2.54217 -1.78946,10.2708 v 7.10232 h -0.8975 -0.89747 l -0.11478,-7.63206 c -0.12399,-8.24403 -0.26391,-9.02502 -1.88595,-10.52016 -0.8482,-0.78183 -2.9732,-1.19196 -4.3655,-0.84252 -1.15129,0.2889 -3.25337,2.29635 -3.90658,3.73062 -0.49008,1.07607 -0.56263,2.12859 -0.56854,8.2488 l -0.006,7.01532 h -0.92514 -0.9251 z"
|
||||
id="path1836" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.308367;stroke-linejoin:bevel"
|
||||
d="m -103.02389,181.53305 c -0.0159,-4.02804 -0.12003,-8.6073 -0.23127,-10.17615 l -0.20232,-2.85249 0.84214,9e-5 c 0.91226,9e-5 1.08223,0.35814 1.23423,2.59995 l 0.0822,1.21224 0.90345,-1.30884 c 1.592469,-2.30697 4.454556,-3.48627 6.877557,-2.83383 1.285035,0.34605 3.171906,2.10468 3.846369,3.58494 0.18903,0.4149 0.425514,0.74997 0.525495,0.74463 0.10002,-0.006 0.549228,-0.61569 0.998349,-1.35624 1.190781,-1.9635 3.276012,-3.10278 5.681709,-3.10422 2.125218,-0.001 3.530028,0.73824 4.730481,2.49021 1.425795,2.0808 1.662396,3.54492 1.802493,11.15388 l 0.13191,7.16952 h -0.953946 -0.953958 l -0.0096,-5.4735 c -0.0111,-6.4119 -0.345123,-9.45564 -1.227219,-11.18469 -1.697829,-3.32802 -6.20544,-3.26967 -8.549655,0.1107 l -0.852249,1.22895 -0.09222,7.65927 -0.09222,7.6593 h -0.872124 -0.872127 l -0.13461,-7.47789 c -0.12207,-6.78261 -0.19539,-7.61328 -0.78783,-8.93439 -1.868358,-4.16616 -7.021416,-3.50571 -9.439975,1.20993 -0.42611,0.83082 -0.50616,2.11092 -0.50616,8.0946 v 7.10775 h -0.9251 -0.92509 z"
|
||||
id="path1838" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.308367;stroke-linejoin:bevel"
|
||||
d="m -65.528455,189.01025 c -0.678402,-0.24 -1.67559,-0.91593 -2.215989,-1.50231 -1.721088,-1.86762 -2.021112,-3.47595 -2.178735,-11.67969 l -0.14064,-7.32369 h 0.957168 0.957168 l 0.0048,5.01096 c 0.0063,6.21369 0.397179,10.10931 1.161543,11.59083 1.796637,3.48225 6.830331,3.36933 9.425844,-0.2115 l 0.970869,-1.33941 0.0924,-7.52544 0.0924,-7.52547 h 1.063956 1.063959 l 0.0078,8.71134 c 0.0048,4.79124 0.14874,9.324 0.321357,10.07277 0.24207,1.05027 0.22275,1.4202 -0.0846,1.61853 -0.877587,0.56631 -1.320246,0.0885 -1.745649,-1.88487 -0.390237,-1.81014 -0.463677,-1.92957 -0.851025,-1.3839 -2.309766,3.2538 -5.654358,4.52049 -8.902956,3.37182 z"
|
||||
id="path1840" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m -42.840682,189.54167 c -0.830898,-0.0516 -1.586373,-0.32814 -2.21226,-0.80976 -0.11286,-0.0867 -0.337599,-0.2976 -0.49935,-0.46863 -0.813387,-0.85929 -1.347882,-2.09901 -1.613787,-3.7431 l -0.08223,-0.50814 -0.0111,-6.97791 -0.0111,-6.97791 h -1.868187 -1.868187 v -0.88926 -0.88926 h 1.869252 1.869249 v -3.09453 -3.09453 l 1.070895,-0.53508 c 0.588993,-0.2943 1.078992,-0.53508 1.088883,-0.53508 0.0096,0 0.018,1.63332 0.018,3.62961 v 3.62961 h 2.994423 2.99442 v 0.88926 0.88926 h -2.995308 -2.995296 l 0.0111,7.03233 c 0.0126,7.55964 0.0048,7.20882 0.18975,8.02035 0.25452,1.11204 0.752538,1.8687 1.452594,2.20695 0.397431,0.192 0.639018,0.234 1.352916,0.2346 0.894777,7.5e-4 1.608678,-0.0864 2.103723,-0.2568 0.11145,-0.0381 0.20673,-0.0654 0.21195,-0.0603 0.0048,0.006 0.05667,0.40134 0.1143,0.88029 l 0.10479,0.87081 -0.07494,0.0441 c -0.12462,0.0735 -0.530289,0.2211 -0.818958,0.2979 -0.672816,0.1791 -1.614678,0.2637 -2.395539,0.2151 z"
|
||||
id="path1842" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m -31.281523,189.54041 c -2.117439,-0.1116 -4.0266,-1.30272 -4.90974,-3.0633 -0.382494,-0.76251 -0.552753,-1.49451 -0.57882,-2.48841 -0.0258,-0.98481 0.08034,-1.78161 0.342393,-2.57181 0.550053,-1.65828 1.703355,-2.91345 3.498849,-3.80796 1.8933,-0.94317 4.422204,-1.44672 7.615539,-1.51629 l 1.030845,-0.024 -0.0231,-0.58419 c -0.12114,-3.08307 -1.437594,-5.03694 -3.728196,-5.53323 -0.18975,-0.0411 -0.568755,-0.0933 -0.842232,-0.1161 -1.32177,-0.1101 -3.283998,0.2748 -5.922663,1.16184 -0.29799,0.1002 -0.545955,0.1779 -0.550989,0.1731 -0.0048,-0.006 -0.05937,-0.46629 -0.12081,-1.02495 -0.06129,-0.55869 -0.11985,-1.06599 -0.13002,-1.12737 l -0.0183,-0.1116 0.590262,-0.1863 c 1.911207,-0.60366 3.694686,-0.88557 5.635404,-0.89073 1.226661,-0.003 1.966944,0.0957 2.823465,0.37677 2.327178,0.76416 3.733878,2.60757 4.236099,5.55114 0.19122,1.12077 0.18897,1.05471 0.21954,6.47901 0.0159,2.7948 0.03777,5.13861 0.0489,5.20848 0.0111,0.0699 0.0462,0.37203 0.07794,0.67149 0.07383,0.69654 0.23166,1.62993 0.392748,2.32266 0.06954,0.2994 0.12129,0.54936 0.11493,0.55569 -0.0159,0.015 -1.847232,0.44229 -1.854438,0.4323 -0.0033,-0.006 -0.17214,-0.74271 -0.375747,-1.64103 -0.20358,-0.89835 -0.378585,-1.65783 -0.388872,-1.68777 -0.0144,-0.0411 -0.03825,-0.027 -0.0978,0.054 -0.396462,0.54423 -0.982011,1.17393 -1.476264,1.58757 -1.569006,1.31304 -3.44154,1.91388 -5.609082,1.7997 z m 1.47774,-2.04291 c 1.043211,-0.1572 2.05602,-0.55764 3.024108,-1.19553 1.290702,-0.85047 2.04543,-1.78962 2.365311,-2.94327 l 0.10065,-0.36297 0.0126,-2.46459 0.0126,-2.46462 -1.009095,0.024 c -5.651721,0.1266 -8.751711,1.62918 -9.249408,4.48323 -0.0708,0.40623 -0.07002,1.35084 0.0016,1.77069 0.29403,1.72404 1.366362,2.86329 2.968689,3.15396 0.360186,0.0654 1.342119,0.0657 1.774029,5.1e-4 z"
|
||||
id="path1844" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m -7.8875825,189.54161 c -1.538988,-0.0744 -2.9374805,-0.60105 -4.2103355,-1.58514 -0.65556,-0.50682 -1.496838,-1.44732 -2.046417,-2.2878 l -0.22209,-0.33963 -0.0078,0.2307 c -0.0048,0.1269 -0.0252,0.79425 -0.04539,1.48296 -0.0204,0.68871 -0.04764,1.41147 -0.06063,1.60611 l -0.0234,0.35388 h -0.994473 -0.994473 l 0.0216,-0.2088 c 0.09669,-0.93147 0.1035,-1.8627 0.11637,-15.98841 l 0.0126,-14.65452 h 1.087533 1.087548 l 0.0011,7.03233 c 9.21e-4,5.53779 0.0111,7.01928 0.04539,6.97074 0.0243,-0.0339 0.15033,-0.2355 0.28014,-0.44802 0.379557,-0.62154 0.777366,-1.12458 1.322769,-1.67265 1.260618,-1.26678 2.5565885,-1.90101 4.4120105,-2.15913 0.469743,-0.0654 1.713279,-0.0552 2.232201,0.018 2.097834,0.2973 3.715656,1.25979 5.01145196,2.98143 1.20858,1.60575 1.89528304,3.44268 2.16369904,5.78775 0.06396,0.55932 0.06462,2.90985 9.51e-4,3.50256 -0.19008,1.76865 -0.59969404,3.26412 -1.26256804,4.60959 -1.64027096,3.32937 -4.32204896,4.94199 -7.92795896,4.76736 z m 1.368312,-2.0466 c 2.796684,-0.36504 4.538139,-2.03373 5.306523,-5.08476 0.47731196,-1.89534 0.56147996,-4.59102 0.20358,-6.52143 -0.47136,-2.54259 -1.745583,-4.51794 -3.485817,-5.4039 -0.920607,-0.46869 -1.741026,-0.65262 -2.920761,-0.65481 -1.00611,-0.002 -1.613154,0.1107 -2.474007,0.45876 -0.9240365,0.37362 -1.9277015,1.15236 -2.6473175,2.05398 -0.710628,0.8904 -1.236075,2.02467 -1.4628,3.15777 -0.15747,0.78702 -0.17418,1.18113 -0.15843,3.73848 0.0159,2.6016 0.0165,2.60625 0.24939,3.48444 0.568038,2.14122 2.215881,3.91269 4.2321965,4.54965 0.25773,0.0813 0.876282,0.2127 1.190181,0.2529 0.313497,0.0399 1.575021,0.021 1.967196,-0.0312 z"
|
||||
id="path1846" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 12.038954,189.48929 c -1.380489,-0.2007 -2.5289035,-0.74685 -3.4593055,-1.64538 -1.650744,-1.59426 -2.5149,-4.11801 -2.710227,-7.91517 -0.0225,-0.43971 -0.03651,-4.69791 -0.03666,-11.24271 l -3.48e-4,-10.53492 h 1.087644 1.087644 l 0.0144,10.91604 c 0.0144,11.15352 0.0126,11.12724 0.15351,12.40416 0.16794,1.5306 0.555705,3.06945 0.985725,3.91164 0.709041,1.38867 1.7957965,2.07525 3.4244605,2.16342 l 0.41886,0.024 -0.10701,0.98907 -0.10701,0.98907 -0.20292,-0.003 c -0.1116,-0.002 -0.358092,-0.027 -0.547737,-0.054 z"
|
||||
id="path1848" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 24.488495,189.5423 c -1.316022,-0.0591 -2.562162,-0.366 -3.65757,-0.90096 -0.600471,-0.2931 -0.872235,-0.45927 -1.423875,-0.86988 -1.613886,-1.20126 -2.83002,-3.00558 -3.445749,-5.11236 -0.12336,-0.42213 -0.26172,-1.10259 -0.348948,-1.71612 -0.09144,-0.64314 -0.11256,-2.46267 -0.03777,-3.24603 0.18384,-1.92444 0.568215,-3.31806 1.321434,-4.79121 0.980265,-1.91715 2.435433,-3.40968 4.126182,-4.2321 1.230918,-0.59871 2.366169,-0.84465 3.883677,-0.84129 0.897063,0.002 1.51638,0.0747 2.251233,0.264 2.647791,0.68271 4.409805,2.70723 5.225415,6.00387 0.30423,1.22967 0.462249,2.45031 0.546609,4.22232 l 0.0228,0.48093 h -7.644861 -7.644861 l 0.0183,0.1725 c 0.0096,0.0948 0.0279,0.33576 0.03969,0.53538 0.10095,1.71342 0.604836,3.41064 1.388331,4.67724 0.498126,0.80532 1.268001,1.64076 1.96539,2.13282 0.885237,0.62463 1.959198,1.02819 3.134232,1.17774 0.593217,0.0756 2.155158,0.0561 2.848515,-0.0357 1.538922,-0.2034 3.239802,-0.66696 4.594113,-1.25232 0.09651,-0.0417 0.18762,-0.0759 0.20256,-0.0759 0.0285,0 0.20805,1.91424 0.18165,1.9362 -0.0078,0.006 -0.1785,0.0822 -0.378141,0.1671 -1.84893,0.78693 -3.835227,1.23453 -5.734782,1.29228 -0.339375,0.012 -0.674193,0.024 -0.744075,0.027 -0.06984,0.006 -0.38019,-0.003 -0.689628,-0.015 z m 6.279102,-12.88929 c -3.96e-4,-0.49158 -0.13002,-1.44957 -0.27408,-2.02635 -0.337248,-1.34979 -0.923781,-2.373 -1.893666,-3.30354 -0.946179,-0.9078 -2.048889,-1.37718 -3.490149,-1.48563 -2.248362,-0.1692 -4.144344,0.70395 -5.586063,2.5725 -0.829104,1.07454 -1.417035,2.45454 -1.657095,3.88956 -0.03174,0.1893 -0.06588,0.38112 -0.07587,0.42606 l -0.0183,0.0816 h 6.497685 6.497685 l -1.11e-4,-0.1542 z"
|
||||
id="path1850" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="M 70.403009,174.46616 V 159.9296 h 7.440693 7.440693 v 0.99816 0.99813 h -6.35181 -6.35181 v 5.5533 5.5533 h 5.916261 5.916264 v 0.99813 0.99813 h -5.916264 -5.916261 v 6.987 6.987 h -1.088883 -1.088883 z"
|
||||
id="path1852" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 89.675846,181.22633 c -3.96e-4,-8.42712 -0.0159,-9.27867 -0.20184,-10.95234 -0.06081,-0.54813 -0.20655,-1.46937 -0.28287,-1.78818 l -0.05001,-0.2094 0.929625,0.009 0.929622,0.009 0.11667,0.34482 c 0.24405,0.72153 0.38751,1.73133 0.438294,3.08502 l 0.0264,0.70767 0.17553,-0.4173 c 0.582486,-1.38471 1.378806,-2.47692 2.332371,-3.1989 0.355965,-0.2694 1.123425,-0.6519 1.556829,-0.77565 0.713328,-0.2037 1.640634,-0.2709 2.314305,-0.1677 l 0.31758,0.0486 v 0.97788 0.97788 l -0.13605,-0.03 c -0.22089,-0.0468 -1.025748,-0.021 -1.369377,0.0426 -0.976185,0.183 -1.774746,0.60066 -2.534016,1.32558 -1.109838,1.05963 -1.80732,2.38722 -2.146092,4.08492 -0.22383,1.12158 -0.21426,0.80616 -0.2286,7.5393 l -0.0126,6.16125 h -1.087404 -1.087407 z"
|
||||
id="path1854" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="M 101.65393,178.64024 V 168.2777 h 1.07073 1.07073 v 10.36254 10.36251 h -1.07073 -1.07073 z"
|
||||
id="path1856" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 117.20677,189.54065 c -1.9962,-0.0849 -3.77313,-0.72921 -5.28687,-1.91715 -0.39069,-0.3066 -1.11243,-1.02423 -1.42371,-1.41555 -1.60191,-2.01393 -2.34033,-4.45113 -2.25987,-7.45884 0.024,-0.84651 0.0549,-1.25475 0.1539,-1.94184 0.36663,-2.54832 1.4064,-4.76052 3.02607,-6.43812 1.344,-1.39212 2.97423,-2.20983 4.93761,-2.47665 0.61383,-0.0834 1.84362,-0.0825 2.48049,0.002 3.47037,0.4608 5.57949,2.73192 6.4251,6.91866 0.2106,1.04241 0.38616,2.72055 0.38616,3.69009 v 0.30006 h -7.62216 -7.62216 l 7.2e-4,0.2268 c 10e-4,0.30822 0.0912,1.21647 0.1638,1.65045 0.58707,3.50589 2.59434,5.95074 5.46024,6.65055 0.76488,0.1869 1.27044,0.2316 2.36031,0.2085 1.92048,-0.0402 3.57471,-0.38781 5.58582,-1.1736 0.3273,-0.1281 0.60087,-0.2268 0.60792,-0.2196 0.03,0.03 0.1878,1.89675 0.1626,1.92225 -0.0591,0.0594 -1.1994,0.50613 -1.73637,0.67998 -1.89084,0.61224 -3.82146,0.87579 -5.79948,0.79173 z m 6.24924,-13.21431 c -0.0957,-2.00139 -0.78849,-3.6687 -2.03955,-4.90902 -0.9795,-0.97116 -2.09058,-1.46088 -3.58356,-1.57962 -1.31247,-0.1044 -2.65083,0.192 -3.67557,0.81426 -1.09476,0.66462 -2.09601,1.78734 -2.742,3.07458 -0.42441,0.8457 -0.76491,1.95288 -0.88953,2.89242 l -0.024,0.1884 h 6.48912 6.48909 z"
|
||||
id="path1858" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 130.246,180.50039 c -0.012,-7.70118 -0.018,-8.57418 -0.0753,-9.26457 -0.0666,-0.80139 -0.2277,-2.49213 -0.2658,-2.78571 l -0.021,-0.1725 h 1.01796 1.01793 l 0.0744,0.31758 c 0.1575,0.67284 0.2772,1.6569 0.31812,2.61228 0.0345,0.80928 0.0366,0.81345 0.2283,0.46161 0.93858,-1.72203 2.27916,-2.93382 3.8508,-3.48087 1.75359,-0.61038 4.23234,-0.44265 5.8749,0.39753 1.41144,0.72198 2.43792,1.968 3.05133,3.70395 0.2595,0.7341 0.4899,1.80171 0.59922,2.7753 0.1071,0.95442 0.1269,2.20143 0.1275,8.04864 l 6.3e-4,5.88903 h -1.08726 -1.0872 l -0.015,-6.48792 c -0.015,-6.91569 -0.012,-6.74199 -0.1899,-7.78422 -0.44652,-2.59383 -1.69596,-4.18083 -3.70875,-4.71075 -0.79704,-0.2097 -1.92897,-0.258 -2.62404,-0.1113 -0.96021,0.2022 -1.78407,0.66915 -2.62653,1.48845 -1.14552,1.11399 -1.87053,2.382 -2.17077,3.79641 l -0.0843,0.39816 -0.009,6.70569 -0.009,6.70572 h -1.08687 -1.08687 z"
|
||||
id="path1860" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 158.71132,189.5417 c -2.91762,-0.1311 -5.18709,-1.59285 -6.66876,-4.29561 -0.60435,-1.10238 -1.08093,-2.50629 -1.31427,-3.8715 -0.51462,-3.01113 -0.2292,-6.08682 0.77988,-8.40225 1.14759,-2.63334 3.06033,-4.36614 5.44281,-4.93071 1.55871,-0.36936 3.38505,-0.27 4.80921,0.2616 1.45209,0.54207 2.69157,1.55238 3.68406,3.00291 0.1398,0.2043 0.31938,0.48798 0.39924,0.63057 0.0798,0.1425 0.1575,0.2598 0.1725,0.2601 0.015,4.5e-4 0.027,-3.15966 0.027,-7.02246 v -7.02327 h 1.08888 1.08888 l 9e-5,14.16453 c 9e-5,13.49286 0.012,15.33414 0.1038,16.38768 l 0.027,0.2994 h -0.98556 -0.98556 l -0.021,-0.1905 c -0.027,-0.2265 -0.096,-2.13246 -0.1071,-2.93091 l -0.009,-0.56259 -0.1773,0.2721 c -0.53577,0.82308 -1.42497,1.82859 -2.05977,2.32926 -1.32408,1.04424 -2.78742,1.57869 -4.47777,1.63542 -0.1596,0.006 -0.52722,-8.7e-4 -0.81666,-0.015 z m 2.15235,-2.09568 c 0.57033,-0.1161 0.99816,-0.2643 1.53171,-0.53058 0.59409,-0.2964 1.05009,-0.62085 1.55505,-1.10673 0.84591,-0.81402 1.46733,-1.83672 1.78131,-2.93163 0.2856,-0.99594 0.3213,-1.50216 0.30141,-4.27485 -0.018,-2.44896 -0.03,-2.61489 -0.2607,-3.51579 -0.35535,-1.38651 -1.02678,-2.50746 -2.13348,-3.56184 -0.95904,-0.91368 -2.00778,-1.43454 -3.29436,-1.63623 -0.50643,-0.0795 -1.58565,-0.0792 -2.10177,4.8e-4 -0.73497,0.1134 -1.47864,0.35451 -2.01039,0.6519 -1.78428,0.9978 -3.04128,3.1326 -3.43863,5.83986 -0.246,1.67523 -0.1632,3.98358 0.2037,5.68635 0.57291,2.6589 1.91361,4.38552 3.95682,5.09574 0.85908,0.2985 1.49043,0.38781 2.6463,0.37377 0.72558,-0.009 0.9366,-0.024 1.26309,-0.0906 z"
|
||||
id="path1862" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 180.18046,189.48815 c -2.2323,-0.3471 -3.81351,-1.50858 -4.84362,-3.55797 -0.64728,-1.28769 -1.01874,-2.74482 -1.26159,-4.94886 -0.057,-0.51579 -0.063,-1.48887 -0.0735,-11.6964 l -0.012,-11.13381 h 1.06932 1.06935 l 0.015,10.64382 c 0.015,10.83192 0.018,11.11626 0.1545,12.45861 0.2028,2.0022 0.65679,3.64941 1.26459,4.58895 0.2085,0.32232 0.69909,0.83706 0.97977,1.02795 0.60663,0.41262 1.51659,0.67965 2.31819,0.68025 0.198,1.5e-4 0.2814,0.012 0.2814,0.0447 0,0.0795 -0.1842,1.78593 -0.2019,1.87011 -0.015,0.0738 -0.0372,0.0813 -0.2253,0.0777 -0.1146,-0.002 -0.35499,-0.027 -0.53466,-0.0549 z"
|
||||
id="path1864" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 183.62242,198.44585 c -0.012,-0.0534 -0.1263,-0.47253 -0.2508,-0.93168 -0.1245,-0.45915 -0.2238,-0.8361 -0.2208,-0.83769 0.003,-0.002 0.2115,-0.0693 0.46311,-0.1503 1.79742,-0.57957 3.34176,-1.85958 4.61625,-3.82614 0.6873,-1.06047 1.37181,-2.48631 1.89687,-3.95127 0.1506,-0.42048 0.1638,-0.60156 0.0675,-0.93942 -0.0306,-0.1074 -1.7352,-4.36221 -3.78795,-9.4551 -2.05275,-5.09289 -3.80616,-9.44355 -3.89643,-9.66813 l -0.1641,-0.40833 h 1.13391 1.13394 l 1.88088,4.7094 c 1.03452,2.5902 2.61192,6.53874 3.50532,8.77458 0.89343,2.23584 1.6383,4.06152 1.65528,4.05705 0.018,-0.006 1.50456,-3.94716 3.3057,-8.76156 1.80114,-4.81443 3.28092,-8.75955 3.28833,-8.76699 0.009,-0.009 0.52716,-0.009 1.15491,-0.003 l 1.14138,0.009 -2.37141,5.95254 c -5.33205,13.38411 -6.05598,15.19422 -6.2784,15.69807 -2.06613,4.68051 -4.71939,7.51905 -7.94979,8.50488 l -0.2994,0.0915 z"
|
||||
id="path1866" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 241.59337,189.54203 c -2.30475,-0.0822 -4.35306,-0.70485 -6.02514,-1.83138 -0.2196,-0.1479 -0.4287,-0.2919 -0.46467,-0.31959 -0.0615,-0.0477 -0.0405,-0.1092 0.35664,-1.04307 0.2322,-0.54588 0.4281,-0.99852 0.43548,-1.00587 0.009,-0.009 0.1653,0.1008 0.35112,0.24 1.06728,0.80076 2.1348,1.31283 3.42627,1.64358 0.86898,0.2226 1.41873,0.2925 2.47128,0.31497 1.00587,0.021 1.46865,-0.012 2.17122,-0.1536 2.10912,-0.4272 3.73479,-1.73637 4.3308,-3.48762 0.2931,-0.86106 0.41376,-1.90122 0.33672,-2.90118 -0.1395,-1.80786 -0.6534,-2.93001 -1.76901,-3.86127 -0.97362,-0.81276 -2.03499,-1.38099 -3.75489,-2.01033 -2.0091,-0.73512 -3.25644,-1.37757 -4.4781,-2.30646 -0.44967,-0.34188 -1.30902,-1.19166 -1.6125,-1.59453 -1.03554,-1.37463 -1.51131,-2.91504 -1.44294,-4.67202 0.0846,-2.17602 0.82695,-3.77448 2.36478,-5.09244 1.02237,-0.87618 2.27838,-1.50102 3.58158,-1.78173 0.73059,-0.1572 1.05438,-0.1914 2.00802,-0.2106 1.70271,-0.0342 3.16122,0.1812 4.46778,0.65937 0.3816,0.1398 1.26171,0.5628 1.54623,0.74331 l 0.1704,0.108 -0.438,0.89952 c -0.2409,0.49476 -0.44949,0.91224 -0.46353,0.92778 -0.015,0.015 -0.1632,-0.048 -0.33123,-0.1413 -1.14852,-0.63687 -2.49195,-1.047 -3.86232,-1.17903 -0.56211,-0.054 -1.84353,-0.024 -2.3052,0.0564 -1.36629,0.2346 -2.43981,0.74907 -3.25305,1.55919 -0.66813,0.66558 -1.03581,1.36992 -1.23555,2.36694 -0.093,0.46467 -0.0933,1.6863 -2.7e-4,2.11788 0.30807,1.42977 1.06047,2.5218 2.45406,3.56166 0.80493,0.6006 1.77321,1.10781 3.28704,1.72188 2.76288,1.12068 4.2393,1.98141 5.40978,3.15372 0.69783,0.69894 1.08957,1.33416 1.39341,2.25948 0.33486,1.01973 0.45378,1.93737 0.42714,3.29625 -0.021,0.99504 -0.06,1.35537 -0.2304,2.05074 -0.44691,1.82274 -1.54683,3.32724 -3.24318,4.4361 -1.68516,1.10154 -3.59061,1.56384 -6.07959,1.47498 z"
|
||||
id="path1868" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 263.46175,189.54323 c -2.80899,-0.1308 -5.22276,-1.38135 -6.87114,-3.56007 -0.4245,-0.56109 -0.6738,-0.96681 -0.98814,-1.60818 -0.52932,-1.08 -0.84054,-2.14656 -1.0221,-3.50256 -0.0939,-0.70014 -0.0927,-2.75589 0.002,-3.53886 0.2937,-2.43396 1.01424,-4.32531 2.2989,-6.03567 0.36348,-0.48396 1.13139,-1.28982 1.55358,-1.63047 1.20624,-0.97314 2.58915,-1.55871 4.1922,-1.77507 0.54591,-0.0738 1.80054,-0.0825 2.34111,-0.015 2.5482,0.31149 4.38213,1.57836 5.51166,3.80742 0.85362,1.68453 1.29828,3.73545 1.42377,6.56667 l 0.024,0.5535 h -7.63599 -7.63602 l 0.027,0.49665 c 0.1689,3.18186 1.43247,5.72484 3.54882,7.14141 0.83019,0.55566 1.90509,0.93372 3.04812,1.07208 0.39846,0.0483 1.71624,0.0477 2.25129,-9.6e-4 1.60254,-0.1455 3.34935,-0.58182 4.92957,-1.23093 0.2058,-0.0846 0.3795,-0.1482 0.38568,-0.141 0.006,0.006 0.051,0.44307 0.0996,0.96903 l 0.0882,0.95631 -0.2019,0.0894 c -1.84176,0.81639 -3.96888,1.31205 -5.90028,1.37487 -0.31938,0.012 -0.65421,0.024 -0.74406,0.03 -0.0897,0.006 -0.41649,-0.002 -0.72591,-0.015 z m 6.27882,-12.94467 c -6e-4,-0.32358 -0.0996,-1.1841 -0.1848,-1.60608 -0.31341,-1.55172 -1.01139,-2.81103 -2.10477,-3.79752 -1.10712,-0.99891 -2.66208,-1.48497 -4.34538,-1.35828 -0.90084,0.0678 -1.52766,0.2337 -2.2752,0.60285 -0.63606,0.31395 -1.07013,0.63144 -1.63392,1.19508 -0.95667,0.95637 -1.56321,1.9479 -1.99737,3.2649 -0.1857,0.56301 -0.41574,1.55532 -0.41574,1.79244 v 0.1155 h 6.47883 6.47886 l -3.9e-4,-0.2088 z"
|
||||
id="path1870" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 284.44087,189.54212 c -3.24438,-0.1257 -5.8536,-1.54668 -7.52163,-4.09638 -0.30882,-0.47208 -0.76221,-1.37259 -0.96384,-1.91436 -0.57444,-1.54371 -0.85107,-3.38343 -0.79269,-5.27226 0.099,-3.20727 0.94287,-5.55774 2.68761,-7.48674 1.27536,-1.41003 2.92155,-2.3352 4.84833,-2.72475 2.16588,-0.43788 4.818,-0.2328 6.71478,0.51963 0.37071,0.147 1.10307,0.49998 1.32684,0.63945 l 0.111,0.069 -0.37239,0.97527 c -0.2049,0.5364 -0.37752,0.98043 -0.38382,0.98673 -0.006,0.006 -0.1254,-0.0627 -0.2649,-0.1539 -0.36555,-0.2385 -1.13112,-0.60219 -1.61448,-0.76683 -1.88151,-0.64089 -4.23801,-0.66975 -5.95125,-0.0729 -2.21205,0.77064 -3.92331,2.79354 -4.58943,5.42523 -0.2493,0.98457 -0.33003,1.73625 -0.32727,3.04332 0.003,1.14081 0.0474,1.64979 0.219,2.484 0.66186,3.21606 2.73378,5.52453 5.53848,6.17091 1.08297,0.2496 2.75193,0.2487 4.0035,-0.002 1.00905,-0.2022 2.0469,-0.61467 2.75373,-1.09416 0.1632,-0.1107 0.2199,-0.1335 0.2427,-0.0966 0.078,0.126 0.72657,1.86156 0.71097,1.90221 -0.0303,0.0783 -1.32156,0.69663 -1.78362,0.85386 -1.44879,0.49299 -2.84556,0.67896 -4.59144,0.61131 z"
|
||||
id="path1872" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 301.06447,189.54269 c -3.29796,-0.1524 -5.33844,-2.26452 -6.00708,-6.21795 -0.2553,-1.50936 -0.2715,-2.04396 -0.2718,-9.01284 l -3.6e-4,-6.0342 h 1.08729 1.08729 l 0.015,6.34275 c 0.015,6.81669 0.009,6.6264 0.2052,7.83087 0.33183,2.04864 1.04052,3.47316 2.14131,4.3041 0.35019,0.2643 0.91161,0.52767 1.40709,0.65994 0.42285,0.1128 0.43758,0.114 1.29882,0.1149 0.95967,6e-4 1.26414,-0.042 1.95999,-0.2739 1.55616,-0.5187 2.95113,-1.77549 3.72168,-3.35301 0.35256,-0.72171 0.55941,-1.45686 0.65067,-2.31246 0.024,-0.2295 0.0378,-2.61342 0.0378,-6.83271 v -6.4803 h 1.08804 1.08807 l 0.012,8.33904 c 0.012,7.66665 0.018,8.37378 0.0744,8.77035 0.1428,0.98406 0.39552,2.082 0.69543,3.02172 0.0879,0.2754 0.1566,0.50394 0.1527,0.5076 -0.003,0.003 -0.43185,0.1152 -0.95088,0.2481 -0.51903,0.1329 -0.96993,0.249 -1.002,0.2586 -0.1329,0.0384 -0.64872,-2.03064 -0.84747,-3.39957 -0.0498,-0.34338 -0.0975,-0.63111 -0.1056,-0.63945 -0.009,-0.009 -0.0678,0.0768 -0.132,0.1893 -0.30909,0.54027 -1.0674,1.49451 -1.52232,1.91568 -1.16817,1.08153 -2.46564,1.71837 -4.01253,1.96953 -0.47538,0.0771 -1.25811,0.1125 -1.86924,0.0843 z"
|
||||
id="path1874" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 316.34506,181.51667 c -3e-5,-8.6226 -0.024,-9.77421 -0.255,-11.5875 -0.0678,-0.53586 -0.2301,-1.47663 -0.2745,-1.59234 -0.021,-0.0537 0.0627,-0.0591 0.90414,-0.0591 h 0.92682 l 0.0792,0.2088 c 0.1203,0.31725 0.2739,0.95907 0.34128,1.42461 0.0558,0.38475 0.0864,0.78735 0.1572,2.05071 l 0.027,0.47187 0.105,-0.2721 c 0.1659,-0.43044 0.62172,-1.30971 0.8892,-1.71555 0.95484,-1.44867 2.16645,-2.28351 3.70614,-2.55366 0.42108,-0.0738 1.39458,-0.0777 1.74222,-0.006 l 0.2358,0.048 0.009,0.97368 0.009,0.97368 -0.1005,-0.021 c -0.41964,-0.0825 -1.16352,-0.0432 -1.70343,0.09 -1.25616,0.30921 -2.35041,1.12935 -3.21057,2.4063 -0.59457,0.88263 -0.99009,1.89357 -1.21293,3.10002 -0.1968,1.06509 -0.1983,1.1235 -0.1983,7.58067 v 5.9649 h -1.08888 -1.08888 l -3e-5,-7.48608 z"
|
||||
id="path1876" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0362961;stroke-linejoin:bevel"
|
||||
d="m 335.98129,189.54323 c -2.62656,-0.1134 -4.88073,-1.18362 -6.53217,-3.10116 -1.32438,-1.53783 -2.06556,-3.27531 -2.38359,-5.5878 -0.0924,-0.67071 -0.0936,-2.70963 -0.002,-3.46626 0.1236,-1.02396 0.2679,-1.73994 0.51606,-2.55945 0.8235,-2.72073 2.61351,-4.99845 4.80111,-6.1092 1.33455,-0.67758 2.90184,-0.98193 4.5321,-0.88008 1.7079,0.1068 3.00651,0.5583 4.1319,1.43682 0.32757,0.2559 0.8991,0.83427 1.14711,1.16121 1.1439,1.50795 1.8381,3.59547 2.1003,6.31554 0.0504,0.52383 0.1098,1.49682 0.1098,1.8057 l 1.2e-4,0.2451 h -7.6278 -7.6278 l 0.024,0.48375 c 0.075,1.56153 0.44094,3.03714 1.05954,4.27104 0.37419,0.74634 0.79491,1.35066 1.32336,1.90071 1.01376,1.05522 2.19021,1.68849 3.65853,1.96929 0.42336,0.081 0.62955,0.0978 1.37457,0.1134 0.52479,0.012 1.07097,0.001 1.3611,-0.024 1.61388,-0.1401 3.16101,-0.51303 4.7805,-1.15224 0.32652,-0.129 0.59898,-0.2292 0.60546,-0.2226 0.006,0.006 0.0453,0.38718 0.0864,0.84603 0.0411,0.45885 0.0822,0.88797 0.0912,0.95361 0.015,0.1149 0.009,0.1227 -0.1902,0.2121 -1.7916,0.80517 -3.98541,1.31487 -5.92326,1.37613 -0.3294,0.012 -0.65604,0.024 -0.72594,0.027 -0.0699,0.006 -0.38019,-0.002 -0.68961,-0.015 z m 6.23133,-13.18554 c -0.0591,-1.16901 -0.35121,-2.34747 -0.80586,-3.2526 -0.45792,-0.9117 -1.23573,-1.81122 -2.03418,-2.35245 -1.01961,-0.69114 -2.37819,-1.02192 -3.76728,-0.91722 -0.9042,0.0681 -1.58085,0.2487 -2.32809,0.62076 -0.58911,0.2934 -1.0392,0.62271 -1.5618,1.14261 -1.04076,1.03533 -1.72257,2.19609 -2.13825,3.64038 -0.1272,0.44184 -0.31065,1.31451 -0.31065,1.47759 v 0.0906 h 6.48444 6.48444 z"
|
||||
id="path1878" />
|
||||
<path
|
||||
style="fill:#ff7f2a;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.00620118;stroke-linejoin:bevel"
|
||||
d="m 50.862044,189.3734 c -0.303291,-0.024 -0.554784,-0.1056 -0.78756,-0.252 -0.11526,-0.0726 -0.21627,-0.1548 -0.33231,-0.2706 -0.22431,-0.2238 -0.366474,-0.43287 -0.476934,-0.70161 -0.18369,-0.44685 -0.20955,-1.00887 -0.06888,-1.4958 0.1251,-0.43269 0.40821,-0.85758 0.757158,-1.13604 0.412605,-0.32928 0.936354,-0.43947 1.454181,-0.30603 0.21978,0.0567 0.4365,0.1665 0.641811,0.32499 0.09129,0.0705 0.301623,0.2802 0.381936,0.38106 0.27891,0.34977 0.429213,0.70716 0.48114,1.14414 0.0111,0.0903 0.0111,0.39948 0,0.48987 -0.06159,0.51813 -0.26865,0.92478 -0.661704,1.29948 -0.326436,0.31116 -0.709485,0.48696 -1.133997,0.52047 -0.07446,0.006 -0.19752,0.006 -0.25488,0.002 z"
|
||||
id="path2003" />
|
||||
<path
|
||||
style="fill:#ff7f2a;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0109968;stroke-linejoin:bevel"
|
||||
d="m 216.94123,189.36752 c -0.51726,-0.0315 -0.97512,-0.2949 -1.32675,-0.76323 -0.2478,-0.32988 -0.37818,-0.68661 -0.4131,-1.12965 -0.0519,-0.65604 0.1791,-1.2891 0.64977,-1.78191 0.2568,-0.2688 0.57717,-0.44529 0.92586,-0.5097 0.1413,-0.027 0.42816,-0.027 0.57111,-6e-4 0.38352,0.0693 0.71907,0.2577 1.02084,0.5739 0.40176,0.42084 0.61104,0.9003 0.63585,1.45689 0.027,0.5913 -0.1734,1.12062 -0.58497,1.54935 -0.42063,0.43821 -0.91278,0.63957 -1.47861,0.60495 z"
|
||||
id="path2005" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0225439;stroke-linejoin:bevel"
|
||||
d="m 102.26473,163.77071 c -0.48993,-0.096 -0.86955,-0.37644 -1.06938,-0.7896 -0.174,-0.35997 -0.2061,-0.52104 -0.2055,-1.03449 4.5e-4,-0.38628 0.006,-0.46284 0.0522,-0.63123 0.1728,-0.64161 0.60519,-1.10346 1.17597,-1.2561 0.183,-0.0489 0.65316,-0.054 0.83415,-0.009 0.1815,0.045 0.46689,0.1851 0.6063,0.2973 0.31089,0.2505 0.5793,0.66225 0.68235,1.04703 0.0705,0.2631 0.0708,0.84519 5.4e-4,1.10466 -0.1764,0.65178 -0.62106,1.10604 -1.22382,1.25055 -0.2031,0.0486 -0.65523,0.06 -0.85272,0.021 z"
|
||||
id="path2161" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="heading"
|
||||
transform="translate(144.84863,-39.0695)">
|
||||
<g
|
||||
aria-label="Infix"
|
||||
id="text1160"
|
||||
style="font-size:25.4px;font-family:'URW Bookman';-inkscape-font-specification:'URW Bookman, Normal';fill:#5c5f5c;stroke-width:1.029"
|
||||
inkscape:label="infix"
|
||||
transform="matrix(6,0,0,6,-498.93695,-594.0847)">
|
||||
<path
|
||||
d="m 85.532731,107.6593 0.508,0.0762 c 1.3462,0.1778 1.6256,0.635 1.651,2.794 v 9.9822 c -0.0254,2.159 -0.3048,2.6162 -1.651,2.794 l -0.508,0.0762 v 0.7874 h 6.858 v -0.7874 l -0.508,-0.0762 c -1.3462,-0.1778 -1.6256,-0.635 -1.651,-2.794 v -9.9822 c 0.0254,-2.159 0.3048,-2.6162 1.651,-2.794 l 0.508,-0.0762 v -0.7874 h -6.858 z"
|
||||
id="path2431" />
|
||||
<path
|
||||
d="m 98.588334,111.8503 h -0.8636 c -1.5748,0.3048 -2.413,0.4064 -3.8354,0.508 v 0.762 l 0.6096,0.0508 c 1.3462,0.1524 1.6764,0.5588 1.6764,2.1082 v 5.969 c 0,1.397 -0.4572,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.832596 v -0.762 l -0.635,-0.0508 c -1.117596,-0.1016 -1.574796,-0.7112 -1.574796,-2.1082 v -6.2738 c 1.574796,-1.4224 3.073396,-2.1336 4.495796,-2.1336 1.397,0 2.159,0.889 2.159,2.54 v 5.8674 c 0,1.397 -0.4826,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.8326 v -0.762 l -0.635,-0.0508 c -1.1176,-0.1016 -1.5748,-0.7112 -1.5748,-2.1082 v -6.2738 c 0,-2.1082 -1.4224,-3.3528 -3.8354,-3.3528 -1.9558,0 -3.429,0.635 -5.232396,2.2352 z"
|
||||
id="path2433" />
|
||||
<path
|
||||
d="m 115.27613,111.8503 v -1.5748 c 0,-1.651 0.0508,-1.9812 0.4064,-2.5908 0.4826,-0.8382 1.4986,-1.3462 2.6416,-1.3462 1.27,0 2.4638,0.762 2.4638,1.6256 0.0254,1.0414 0.0254,1.0414 0.2286,1.397 0.2286,0.4064 0.635,0.635 1.143,0.635 0.762,0 1.27,-0.5334 1.27,-1.27 0,-0.5842 -0.2286,-1.0668 -0.762,-1.6256 -1.016,-1.0414 -2.413,-1.5748 -4.191,-1.5748 -1.9558,0 -3.7338,0.6604 -4.6482,1.7272 -0.6858,0.8128 -0.9652,1.8542 -0.9652,3.6322 v 0.9652 h -2.159 v 0.8382 h 2.159 v 8.5598 c 0,1.397 -0.4572,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.8326 v -0.762 l -0.635,-0.0508 c -1.1176,-0.1016 -1.5748,-0.7112 -1.5748,-2.1082 v -8.5598 h 3.3782 c 1.8288,0 2.3368,0.5842 2.3114,2.667 v 5.8928 c 0,1.397 -0.4572,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.8326 v -0.762 l -0.635,-0.0508 c -1.1176,-0.1016 -1.5748,-0.7112 -1.5748,-2.1082 v -9.398 z"
|
||||
id="path2435" />
|
||||
<path
|
||||
d="m 134.09755,117.0827 2.2098,-2.3622 c 1.397,-1.3716 2.1082,-1.778 3.81,-2.1082 v -0.762 h -5.3594 v 0.762 c 0.9144,0.1016 1.143,0.2286 1.143,0.6604 0,0.2032 -0.0762,0.4064 -0.254,0.6096 l -2.159,2.4384 -2.0066,-2.4384 c -0.1778,-0.254 -0.3048,-0.4826 -0.3048,-0.635 0,-0.3556 0.3048,-0.5334 1.0414,-0.6096 l 0.254,-0.0254 v -0.762 h -6.2992 v 0.762 c 1.2192,0.1778 1.7018,0.4826 2.5908,1.5494 l 2.9718,3.7338 -3.2258,3.6322 c -1.143,1.2954 -1.8034,1.7018 -3.0226,1.8796 v 0.762 h 5.6896 v -0.762 c -0.889,-0.1524 -1.0922,-0.2032 -1.27,-0.3048 -0.254,-0.1524 -0.4318,-0.4064 -0.4318,-0.6604 0,-0.254 0.1778,-0.5842 0.4572,-0.9144 l 2.54,-2.8448 2.286,3.2004 c 0.1524,0.2286 0.254,0.4826 0.254,0.6858 0,0.4826 -0.3048,0.6604 -1.524,0.8382 v 0.762 h 7.0358 v -0.762 c -1.3716,-0.2286 -1.8288,-0.5334 -2.9464,-1.8796 z"
|
||||
id="path2437" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="tux"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="display:inline"
|
||||
transform="translate(144.84863,-39.0695)">
|
||||
<path
|
||||
id="rect1698"
|
||||
style="fill:#f9f9f9;stroke:#5c5f5c;stroke-width:6.174;stroke-linejoin:round"
|
||||
inkscape:label="body"
|
||||
d="M -141.76163,48.090612 H -6.9369515 V 149.9153 H -141.76163 Z" />
|
||||
<path
|
||||
id="rect184-3"
|
||||
style="fill:#ff7f2a;stroke:#5c5f5c;stroke-width:6;stroke-linejoin:round"
|
||||
inkscape:label="foot-r"
|
||||
d="m -42.936952,125.9153 h 36.0000005 v 24 H -42.936952 Z" />
|
||||
<path
|
||||
id="rect184"
|
||||
style="fill:#ff7f2a;stroke:#5c5f5c;stroke-width:6;stroke-linejoin:round"
|
||||
inkscape:label="foot-l"
|
||||
d="m -141.76163,125.9153 h 36 v 24 h -36 z" />
|
||||
<path
|
||||
id="path11801"
|
||||
style="fill:#ff7f2a;stroke-width:6"
|
||||
inkscape:label="nose"
|
||||
inkscape:transform-center-y="2.9490877"
|
||||
d="M -74.349298,107.85026 -84.565276,90.155655 h 20.43195 z" />
|
||||
<path
|
||||
id="path6519-5"
|
||||
style="display:inline;fill:#5c5f5c;stroke-width:29.9999"
|
||||
inkscape:label="eye-r"
|
||||
d="m -56.67046,72.046755 a 3,3 0 0 1 -3,3 3,3 0 0 1 -3,-3 3,3 0 0 1 3,-3 3,3 0 0 1 3,3 z" />
|
||||
<path
|
||||
id="path6519"
|
||||
style="fill:#5c5f5c;stroke-width:29.9999"
|
||||
inkscape:label="eye-l"
|
||||
d="m -86.028172,72.046755 a 3,3 0 0 1 -3,3 3,3 0 0 1 -3,-3 3,3 0 0 1 3,-3 3,3 0 0 1 3,3 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 45 KiB |
@@ -1,312 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="163.09068mm"
|
||||
height="53.164967mm"
|
||||
viewBox="0 0 163.09068 53.164966"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="logo-medium.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4929971"
|
||||
inkscape:cx="304.08633"
|
||||
inkscape:cy="286.0019"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1385"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer6" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer6"
|
||||
inkscape:label="bg"
|
||||
style="display:none;fill:#ff0000;fill-opacity:1"
|
||||
transform="matrix(1.2831201,0,0,3.2117929,-44.932163,-316.63259)">
|
||||
<rect
|
||||
style="fill:#22272e;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect10264"
|
||||
width="163.66356"
|
||||
height="92.471725"
|
||||
x="20.800945"
|
||||
y="69.866936"
|
||||
inkscape:label="bg" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="tagline"
|
||||
style="display:inline"
|
||||
transform="translate(-18.242052,-92.23446)">
|
||||
<g
|
||||
aria-label="Immutable . Friendly . Secure"
|
||||
transform="matrix(1.9025756,0,0,2.1024132,-99.78739,-118.81694)"
|
||||
id="text9503"
|
||||
style="font-size:6.44339px;font-family:Laksaman;-inkscape-font-specification:'Laksaman, Normal';stroke:#5c5f5c;stroke-width:0;stroke-miterlimit:0"
|
||||
inkscape:label="linux-netconf"
|
||||
inkscape:export-filename="../../../Pictures/logo-medium.svg"
|
||||
inkscape:export-xdpi="191.95932"
|
||||
inkscape:export-ydpi="191.95932">
|
||||
<path
|
||||
d="m 62.472956,124.15871 h -0.386604 v -4.61347 h 0.386604 z"
|
||||
id="path2380" />
|
||||
<path
|
||||
d="m 67.144418,120.79526 q 0.573462,0 0.869857,0.4317 0.296396,0.43171 0.296396,1.10182 v 1.82993 h -0.386603 v -1.82348 q 0,-0.54125 -0.219075,-0.8763 -0.212632,-0.3415 -0.663669,-0.3415 -0.367274,0 -0.657226,0.28995 -0.289953,0.28995 -0.289953,0.65722 v 2.09411 h -0.386603 v -1.99745 q 0,-1.04383 -0.831197,-1.04383 -0.38016,0 -0.689443,0.32861 -0.309283,0.32861 -0.309283,0.71522 v 1.99745 H 63.491016 V 121.678 q 0,-0.28995 -0.06443,-0.81187 h 0.354386 q 0.06443,0.19975 0.07732,0.58635 0.373717,-0.65722 1.108263,-0.65722 0.670113,0 0.998725,0.73454 0.386604,-0.73454 1.179141,-0.73454 z"
|
||||
id="path2382" />
|
||||
<path
|
||||
d="m 72.969238,120.79526 q 0.573462,0 0.869858,0.4317 0.296396,0.43171 0.296396,1.10182 v 1.82993 h -0.386604 v -1.82348 q 0,-0.54125 -0.219075,-0.8763 -0.212632,-0.3415 -0.663669,-0.3415 -0.367273,0 -0.657226,0.28995 -0.289953,0.28995 -0.289953,0.65722 v 2.09411 h -0.386603 v -1.99745 q 0,-1.04383 -0.831197,-1.04383 -0.38016,0 -0.689443,0.32861 -0.309283,0.32861 -0.309283,0.71522 v 1.99745 H 69.315836 V 121.678 q 0,-0.28995 -0.06443,-0.81187 h 0.354386 q 0.06443,0.19975 0.07732,0.58635 0.373717,-0.65722 1.108263,-0.65722 0.670113,0 0.998726,0.73454 0.386603,-0.73454 1.17914,-0.73454 z"
|
||||
id="path2384" />
|
||||
<path
|
||||
d="m 78.078841,124.14582 -0.36083,0.0838 q -0.115981,-0.32861 -0.154641,-0.64434 -0.431707,0.66367 -1.237131,0.66367 -1.185584,0 -1.185584,-1.53352 v -1.84926 h 0.386604 v 1.77838 q 0,1.28223 0.882744,1.28223 0.457481,0 0.786094,-0.30928 0.328613,-0.31572 0.328613,-0.76032 v -1.99101 h 0.386603 v 2.48715 q 0,0.34795 0.167528,0.79254 z"
|
||||
id="path2386" />
|
||||
<path
|
||||
d="m 79.973201,124.24891 q -0.39949,0 -0.625009,-0.29639 -0.219075,-0.30284 -0.219075,-0.76032 v -2.03611 h -0.657226 v -0.28996 h 0.657226 v -0.97939 l 0.386603,-0.17397 v 1.15336 h 1.050273 v 0.28996 H 79.51572 v 2.03611 q 0,0.73454 0.489698,0.73454 0.302839,0 0.444594,-0.058 l 0.03866,0.28996 q -0.199745,0.0902 -0.515471,0.0902 z"
|
||||
id="path2388" />
|
||||
<path
|
||||
d="m 82.015758,124.24891 q -0.43815,0 -0.747433,-0.25129 -0.302839,-0.25773 -0.302839,-0.66367 0,-1.23068 2.190752,-1.23068 0,-0.47037 -0.225519,-0.72811 -0.225518,-0.25773 -0.663669,-0.25773 -0.354386,0 -1.050272,0.21907 l -0.0451,-0.36727 q 0.541245,-0.17397 1.12115,-0.17397 1.250018,0 1.250018,1.37888 v 1.17914 q 0,0.40594 0.115981,0.80543 l -0.335057,0.0709 -0.135311,-0.5348 q -0.444594,0.55413 -1.172697,0.55413 z m -0.663669,-0.97939 q 0,0.30284 0.186858,0.48325 0.186859,0.17397 0.489698,0.17397 0.386603,0 0.753876,-0.2384 0.373717,-0.24485 0.373717,-0.59279 v -0.67012 q -1.804149,0 -1.804149,0.84409 z"
|
||||
id="path2390" />
|
||||
<path
|
||||
d="m 86.178189,120.79526 q 0.683,0 1.08249,0.47681 0.39949,0.47037 0.39949,1.19847 0,0.77965 -0.412377,1.28223 -0.412377,0.49614 -1.127593,0.49614 -0.766764,0 -1.224244,-0.67011 0,0.32861 -0.01933,0.57991 h -0.354387 q 0.02577,-0.22552 0.02577,-0.80543 v -4.09155 h 0.386603 v 2.22941 q 0.425264,-0.69588 1.243574,-0.69588 z m -1.243574,2.04255 q 0,0.46393 0.328613,0.77965 0.328613,0.30928 0.805424,0.30928 1.204914,0 1.204914,-1.44331 0,-0.61213 -0.30284,-0.98584 -0.302839,-0.38016 -0.863414,-0.38016 -0.489698,0 -0.831197,0.3415 -0.3415,0.3415 -0.3415,0.83119 z"
|
||||
id="path2392" />
|
||||
<path
|
||||
d="m 89.696285,123.92674 -0.03866,0.32217 q -1.224244,-0.058 -1.224244,-1.74615 v -3.24103 h 0.386603 v 3.10571 q 0,0.36728 0.03222,0.61857 0.03222,0.25129 0.115981,0.48325 0.09021,0.23197 0.270623,0.34795 0.180415,0.10953 0.457481,0.10953 z"
|
||||
id="path2394" />
|
||||
<path
|
||||
d="m 90.51459,122.54142 q 0.01933,0.625 0.373717,1.00516 0.36083,0.38016 0.960065,0.38016 0.560575,0 1.153367,-0.23196 l 0.03222,0.31573 q -0.573462,0.2384 -1.224245,0.2384 -0.740989,0 -1.211357,-0.45748 -0.470367,-0.46392 -0.470367,-1.19202 0,-0.77965 0.451037,-1.28868 0.457481,-0.51547 1.192027,-0.51547 1.385329,0 1.417546,1.74616 z m 2.287403,-0.32217 q -0.01289,-0.50259 -0.309282,-0.79898 -0.289953,-0.30284 -0.766764,-0.30284 -0.47681,0 -0.79898,0.31572 -0.322169,0.30929 -0.393047,0.7861 z"
|
||||
id="path2396" />
|
||||
<path
|
||||
d="m 96.345854,123.54658 q 0.135311,0 0.238405,0.1031 0.103095,0.10309 0.103095,0.2384 0,0.13532 -0.103095,0.23197 -0.103094,0.0966 -0.238405,0.0966 -0.135311,0 -0.231962,-0.0966 -0.09665,-0.0967 -0.09665,-0.23197 0,-0.13531 0.09665,-0.2384 0.09665,-0.1031 0.231962,-0.1031 z"
|
||||
id="path2398" />
|
||||
<path
|
||||
d="m 102.20934,121.94218 h -2.07477 v 2.21653 h -0.386608 v -4.61347 h 2.609578 v 0.32217 h -2.22297 v 1.7526 h 2.07477 z"
|
||||
id="path2400" />
|
||||
<path
|
||||
d="m 104.6385,121.13031 q -0.0387,-0.0129 -0.11598,-0.0129 -0.42527,0 -0.72166,0.34794 -0.28996,0.3415 -0.28996,0.9214 v 1.77194 h -0.3866 v -2.21653 q 0,-0.7281 -0.0967,-1.07605 h 0.32861 q 0.1031,0.20619 0.1031,0.65723 0.30928,-0.7281 0.99228,-0.7281 0.0902,0 0.18686,0.0193 z"
|
||||
id="path2402" />
|
||||
<path
|
||||
d="m 105.60501,124.15871 h -0.38661 v -3.29258 h 0.38661 z m -0.20619,-4.60058 q 0.13531,0 0.21907,0.0902 0.0902,0.0838 0.0902,0.21908 0,0.13531 -0.0902,0.21907 -0.0838,0.0773 -0.21907,0.0773 -0.13531,0 -0.21908,-0.0773 -0.0773,-0.0838 -0.0773,-0.21907 0,-0.13531 0.0773,-0.21908 0.0838,-0.0902 0.21908,-0.0902 z"
|
||||
id="path2404" />
|
||||
<path
|
||||
d="m 106.75838,122.54142 q 0.0193,0.625 0.37371,1.00516 0.36083,0.38016 0.96007,0.38016 0.56057,0 1.15336,-0.23196 l 0.0322,0.31573 q -0.57346,0.2384 -1.22424,0.2384 -0.74099,0 -1.21136,-0.45748 -0.47037,-0.46392 -0.47037,-1.19202 0,-0.77965 0.45104,-1.28868 0.45748,-0.51547 1.19203,-0.51547 1.38533,0 1.41754,1.74616 z m 2.2874,-0.32217 q -0.0129,-0.50259 -0.30928,-0.79898 -0.28996,-0.30284 -0.76677,-0.30284 -0.47681,0 -0.79898,0.31572 -0.32217,0.30929 -0.39304,0.7861 z"
|
||||
id="path2406" />
|
||||
<path
|
||||
d="m 113.00202,124.15871 h -0.3866 v -1.86859 q 0,-1.17269 -0.9794,-1.17269 -0.3866,0 -0.70233,0.32861 -0.31572,0.32861 -0.31572,0.71522 v 1.99745 h -0.38661 V 121.678 q 0,-0.28995 -0.0644,-0.81187 h 0.36083 q 0.0644,0.19975 0.0773,0.58635 0.37372,-0.65722 1.10826,-0.65722 1.28868,0 1.28868,1.49486 z"
|
||||
id="path2408" />
|
||||
<path
|
||||
d="m 113.77523,122.47054 q 0,-0.72166 0.39949,-1.19847 0.40593,-0.47681 1.08249,-0.47681 0.81831,0 1.24357,0.69588 v -2.22941 h 0.38661 v 4.09155 q 0,0.57991 0.0258,0.80543 h -0.35439 q -0.0193,-0.2513 -0.0193,-0.57991 -0.45748,0.67011 -1.22424,0.67011 -0.72166,0 -1.13404,-0.49614 -0.40593,-0.50258 -0.40593,-1.28223 z m 1.59152,1.4562 q 0.47681,0 0.80542,-0.30928 0.32861,-0.31572 0.32861,-0.77965 v -0.54769 q 0,-0.49614 -0.3415,-0.83119 -0.33505,-0.3415 -0.83119,-0.3415 -0.56058,0 -0.86342,0.38016 -0.30284,0.38016 -0.30284,0.98584 0,1.44331 1.20492,1.44331 z"
|
||||
id="path2410" />
|
||||
<path
|
||||
d="m 119.15547,123.92674 -0.0387,0.32217 q -1.22424,-0.058 -1.22424,-1.74615 v -3.24103 h 0.3866 v 3.10571 q 0,0.36728 0.0322,0.61857 0.0322,0.25129 0.11598,0.48325 0.0902,0.23197 0.27062,0.34795 0.18042,0.10953 0.45749,0.10953 z"
|
||||
id="path2412" />
|
||||
<path
|
||||
d="m 121.10781,124.15871 q -0.57346,1.28867 -1.52064,1.51419 l -0.0902,-0.30284 q 0.82476,-0.18685 1.25002,-1.32089 0,-0.058 -0.0258,-0.11598 l -1.366,-3.06706 h 0.40593 l 1.23069,2.78999 1.15337,-2.78999 h 0.41237 z"
|
||||
id="path2414" />
|
||||
<path
|
||||
d="m 125.44421,123.54658 q 0.13531,0 0.2384,0.1031 0.1031,0.10309 0.1031,0.2384 0,0.13532 -0.1031,0.23197 -0.10309,0.0966 -0.2384,0.0966 -0.13531,0 -0.23196,-0.0966 -0.0966,-0.0967 -0.0966,-0.23197 0,-0.13531 0.0966,-0.2384 0.0966,-0.1031 0.23196,-0.1031 z"
|
||||
id="path2416" />
|
||||
<path
|
||||
d="m 131.42367,122.95379 q 0,0.59924 -0.45103,0.94718 -0.4446,0.34794 -1.11471,0.34794 -0.76032,0 -1.26935,-0.35438 l 0.15464,-0.32862 q 0.47681,0.36083 1.15337,0.36083 0.50903,0 0.82475,-0.24484 0.31573,-0.24485 0.31573,-0.69589 0,-0.43815 -0.25129,-0.65723 -0.2513,-0.22551 -0.75388,-0.38016 -1.28868,-0.41882 -1.28868,-1.30156 0,-0.54125 0.41238,-0.85697 0.41238,-0.32217 1.01805,-0.32217 0.64434,0 1.05672,0.24485 -0.0129,0.0258 -0.0773,0.14175 -0.058,0.10954 -0.0838,0.16109 -0.41882,-0.22552 -0.90208,-0.22552 -0.45104,0 -0.74743,0.21263 -0.28995,0.21263 -0.28995,0.59924 0,0.63789 0.99872,0.98583 0.32217,0.11599 0.51547,0.21264 0.19975,0.0902 0.39949,0.24484 0.19975,0.15465 0.28995,0.38016 0.0902,0.22552 0.0902,0.52836 z"
|
||||
id="path2418" />
|
||||
<path
|
||||
d="m 132.38373,122.54142 q 0.0193,0.625 0.37372,1.00516 0.36083,0.38016 0.96006,0.38016 0.56058,0 1.15337,-0.23196 l 0.0322,0.31573 q -0.57347,0.2384 -1.22425,0.2384 -0.74099,0 -1.21136,-0.45748 -0.47036,-0.46392 -0.47036,-1.19202 0,-0.77965 0.45103,-1.28868 0.45749,-0.51547 1.19203,-0.51547 1.38533,0 1.41755,1.74616 z m 2.2874,-0.32217 q -0.0129,-0.50259 -0.30928,-0.79898 -0.28995,-0.30284 -0.76676,-0.30284 -0.47681,0 -0.79898,0.31572 -0.32217,0.30929 -0.39305,0.7861 z"
|
||||
id="path2420" />
|
||||
<path
|
||||
d="m 137.3387,121.11743 q -0.61212,0 -0.97295,0.3866 -0.36083,0.3866 -0.36083,1.01806 0,0.625 0.36727,1.01805 0.36727,0.3866 0.96651,0.3866 0.54124,0 0.90207,-0.23196 l 0.13531,0.31573 q -0.42526,0.2384 -1.01805,0.2384 -0.79898,0 -1.26935,-0.46392 -0.47037,-0.47037 -0.47037,-1.2629 0,-0.79254 0.47037,-1.25647 0.47037,-0.47036 1.26935,-0.47036 0.59923,0 1.01805,0.23196 l -0.13531,0.32217 q -0.36083,-0.23196 -0.90207,-0.23196 z"
|
||||
id="path2422" />
|
||||
<path
|
||||
d="m 141.99727,124.14582 -0.36083,0.0838 q -0.11598,-0.32861 -0.15464,-0.64434 -0.4317,0.66367 -1.23713,0.66367 -1.18558,0 -1.18558,-1.53352 v -1.84926 h 0.3866 v 1.77838 q 0,1.28223 0.88275,1.28223 0.45748,0 0.78609,-0.30928 0.32861,-0.31572 0.32861,-0.76032 v -1.99101 h 0.38661 v 2.48715 q 0,0.34795 0.16752,0.79254 z"
|
||||
id="path2424" />
|
||||
<path
|
||||
d="m 144.34911,121.13031 q -0.0387,-0.0129 -0.11598,-0.0129 -0.42526,0 -0.72166,0.34794 -0.28995,0.3415 -0.28995,0.9214 v 1.77194 h -0.38661 v -2.21653 q 0,-0.7281 -0.0967,-1.07605 h 0.32862 q 0.10309,0.20619 0.10309,0.65723 0.30928,-0.7281 0.99228,-0.7281 0.0902,0 0.18686,0.0193 z"
|
||||
id="path2426" />
|
||||
<path
|
||||
d="m 145.08366,122.54142 q 0.0193,0.625 0.37372,1.00516 0.36083,0.38016 0.96006,0.38016 0.56058,0 1.15337,-0.23196 l 0.0322,0.31573 q -0.57346,0.2384 -1.22424,0.2384 -0.74099,0 -1.21136,-0.45748 -0.47036,-0.46392 -0.47036,-1.19202 0,-0.77965 0.45103,-1.28868 0.45748,-0.51547 1.19203,-0.51547 1.38533,0 1.41755,1.74616 z m 2.2874,-0.32217 q -0.0129,-0.50259 -0.30928,-0.79898 -0.28995,-0.30284 -0.76676,-0.30284 -0.47681,0 -0.79898,0.31572 -0.32217,0.30929 -0.39305,0.7861 z"
|
||||
id="path2428" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.102789;stroke-linejoin:bevel"
|
||||
d="m 18.4196,137.38387 v -4.77968 h 0.308366 0.308367 v 4.77968 4.77967 H 18.727966 18.4196 Z"
|
||||
id="path1834" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.102789;stroke-linejoin:bevel"
|
||||
d="m 21.091489,139.61951 c -3.38e-4,-1.3992 -0.03064,-2.92938 -0.06727,-3.40038 l -0.06666,-0.85636 0.298551,0.0341 c 0.282601,0.0322 0.300207,0.0629 0.329618,0.5737 0.01709,0.2968 0.06862,0.53964 0.114506,0.53964 0.04588,0 0.205999,-0.17686 0.355801,-0.39302 0.483621,-0.69786 1.412484,-1.05856 2.188136,-0.8497 0.45557,0.12267 1.080087,0.68663 1.271148,1.14789 0.06844,0.16522 0.153512,0.29916 0.189056,0.29764 0.03556,-0.002 0.179948,-0.18963 0.320907,-0.41803 0.86151,-1.39595 2.779512,-1.46325 3.563747,-0.12505 0.452284,0.77176 0.545121,1.41843 0.546878,3.80939 l 0.0016,2.18426 H 29.829126 29.52076 v -2.04174 c 0,-2.14263 -0.08611,-2.90488 -0.396013,-3.50568 -0.516064,-1.00047 -1.774417,-1.11815 -2.605103,-0.24362 -0.549672,0.57867 -0.596487,0.84739 -0.596487,3.4236 v 2.36744 h -0.299165 -0.299159 l -0.03826,-2.54402 c -0.04133,-2.74801 -0.08797,-3.00834 -0.62865,-3.50672 -0.282733,-0.26061 -0.991065,-0.39732 -1.455166,-0.28084 -0.383762,0.0963 -1.084458,0.76545 -1.302194,1.24354 -0.163359,0.35869 -0.187542,0.70953 -0.189511,2.7496 l -0.0021,2.33844 H 21.40057 21.092204 Z"
|
||||
id="path1836" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.102789;stroke-linejoin:bevel"
|
||||
d="m 32.183632,139.72231 c -0.0053,-1.34268 -0.04001,-2.8691 -0.07709,-3.39205 l -0.06744,-0.95083 0.280712,3e-5 c 0.304086,3e-5 0.360744,0.11938 0.411411,0.86665 l 0.02741,0.40408 0.301149,-0.43628 c 0.530823,-0.76899 1.484852,-1.16209 2.292519,-0.94461 0.428345,0.11535 1.057302,0.70156 1.282123,1.19498 0.06301,0.1383 0.141838,0.24999 0.175165,0.24821 0.03334,-0.002 0.183076,-0.20523 0.332783,-0.45208 0.396927,-0.6545 1.092004,-1.03426 1.893903,-1.03474 0.708406,-4.2e-4 1.176676,0.24608 1.576827,0.83007 0.475265,0.6936 0.554132,1.18164 0.600831,3.71796 l 0.04397,2.38984 h -0.317982 -0.317986 l -0.0032,-1.8245 c -0.0037,-2.1373 -0.115041,-3.15188 -0.409073,-3.72823 -0.565943,-1.10934 -2.06848,-1.08989 -2.849885,0.0369 l -0.284083,0.40965 -0.03074,2.55309 -0.03074,2.5531 h -0.290708 -0.290709 l -0.04487,-2.49263 c -0.04069,-2.26087 -0.06513,-2.53776 -0.26261,-2.97813 -0.622786,-1.38872 -2.340472,-1.16857 -3.146657,0.40331 -0.142039,0.27694 -0.16872,0.70364 -0.16872,2.6982 v 2.36925 h -0.308367 -0.308366 z"
|
||||
id="path1838" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.102789;stroke-linejoin:bevel"
|
||||
d="m 44.682109,142.21471 c -0.226134,-0.08 -0.55853,-0.30531 -0.738663,-0.50077 -0.573696,-0.62254 -0.673704,-1.15865 -0.726245,-3.89323 l -0.04688,-2.44123 h 0.319056 0.319056 l 0.0016,1.67032 c 0.0021,2.07123 0.132393,3.36977 0.387181,3.86361 0.598879,1.16075 2.276777,1.12311 3.141948,-0.0705 l 0.323623,-0.44647 0.0308,-2.50848 0.0308,-2.50849 h 0.354652 0.354653 l 0.0026,2.90378 c 0.0016,1.59708 0.04958,3.108 0.107119,3.35759 0.08069,0.35009 0.07425,0.4734 -0.0282,0.53951 -0.292529,0.18877 -0.440082,0.0295 -0.581883,-0.62829 -0.130079,-0.60338 -0.154559,-0.64319 -0.283675,-0.4613 -0.769922,1.0846 -1.884786,1.50683 -2.967652,1.12394 z"
|
||||
id="path1840" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 52.2447,142.39185 c -0.276966,-0.0172 -0.528791,-0.10938 -0.73742,-0.26992 -0.03762,-0.0289 -0.112533,-0.0992 -0.16645,-0.15621 -0.271129,-0.28643 -0.449294,-0.69967 -0.537929,-1.2477 l -0.02741,-0.16938 -0.0037,-2.32597 -0.0037,-2.32597 h -0.622729 -0.622729 v -0.29642 -0.29642 h 0.623084 0.623083 v -1.03151 -1.03151 l 0.356965,-0.17836 c 0.196331,-0.0981 0.359664,-0.17836 0.362961,-0.17836 0.0032,0 0.006,0.54444 0.006,1.20987 v 1.20987 h 0.998141 0.99814 v 0.29642 0.29642 h -0.998436 -0.998432 l 0.0037,2.34411 c 0.0042,2.51988 0.0016,2.40294 0.06325,2.67345 0.08484,0.37068 0.250846,0.6229 0.484198,0.73565 0.132477,0.064 0.213006,0.078 0.450972,0.0782 0.298259,2.5e-4 0.536226,-0.0288 0.701241,-0.0856 0.03715,-0.0127 0.06891,-0.0218 0.07065,-0.0201 0.0016,0.002 0.01889,0.13378 0.0381,0.29343 l 0.03493,0.29027 -0.02498,0.0147 c -0.04154,0.0245 -0.176763,0.0737 -0.272986,0.0993 -0.224272,0.0597 -0.538226,0.0879 -0.798513,0.0717 z"
|
||||
id="path1842" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 56.097753,142.39143 c -0.705813,-0.0372 -1.3422,-0.43424 -1.63658,-1.0211 -0.127498,-0.25417 -0.184251,-0.49817 -0.19294,-0.82947 -0.0086,-0.32827 0.02678,-0.59387 0.114131,-0.85727 0.183351,-0.55276 0.567785,-0.97115 1.166283,-1.26932 0.6311,-0.31439 1.474068,-0.48224 2.538513,-0.50543 l 0.343615,-0.008 -0.0077,-0.19473 c -0.04038,-1.02769 -0.479198,-1.67898 -1.242732,-1.84441 -0.06325,-0.0137 -0.189585,-0.0311 -0.280744,-0.0387 -0.44059,-0.0367 -1.094666,0.0916 -1.974221,0.38728 -0.09933,0.0334 -0.181985,0.0593 -0.183663,0.0577 -0.0016,-0.002 -0.01979,-0.15543 -0.04027,-0.34165 -0.02043,-0.18623 -0.03995,-0.35533 -0.04334,-0.37579 l -0.0061,-0.0372 0.196754,-0.0621 c 0.637069,-0.20122 1.231562,-0.29519 1.878468,-0.29691 0.408887,-10e-4 0.655648,0.0319 0.941155,0.12559 0.775726,0.25472 1.244626,0.86919 1.412033,1.85038 0.06374,0.37359 0.06299,0.35157 0.07318,2.15967 0.0053,0.9316 0.01259,1.71287 0.0163,1.73616 0.0037,0.0233 0.0154,0.12401 0.02598,0.22383 0.02461,0.23218 0.07722,0.54331 0.130916,0.77422 0.02318,0.0998 0.04043,0.18312 0.03831,0.18523 -0.0053,0.005 -0.615744,0.14743 -0.618146,0.1441 -0.0011,-0.002 -0.05738,-0.24757 -0.125249,-0.54701 -0.06786,-0.29945 -0.126195,-0.55261 -0.129624,-0.56259 -0.0048,-0.0137 -0.01275,-0.009 -0.0326,0.018 -0.132154,0.18141 -0.327337,0.39131 -0.492088,0.52919 -0.523002,0.43768 -1.14718,0.63796 -1.869694,0.5999 z m 0.49258,-0.68097 c 0.347737,-0.0524 0.68534,-0.18588 1.008036,-0.39851 0.430234,-0.28349 0.68181,-0.59654 0.788437,-0.98109 l 0.03355,-0.12099 0.0042,-0.82153 0.0042,-0.82154 -0.336365,0.008 c -1.883907,0.0422 -2.917237,0.54306 -3.083136,1.49441 -0.0236,0.13541 -0.02334,0.45028 5.29e-4,0.59023 0.09801,0.57468 0.455454,0.95443 0.989563,1.05132 0.120062,0.0218 0.447373,0.0219 0.591343,1.7e-4 z"
|
||||
id="path1844" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 63.895733,142.39183 c -0.512996,-0.0248 -0.97916,-0.20035 -1.403445,-0.52838 -0.21852,-0.16894 -0.498946,-0.48244 -0.682139,-0.7626 l -0.07403,-0.11321 -0.0026,0.0769 c -0.0016,0.0423 -0.0084,0.26475 -0.01513,0.49432 -0.0068,0.22957 -0.01588,0.47049 -0.02021,0.53537 l -0.0078,0.11796 h -0.331491 -0.331491 l 0.0072,-0.0696 c 0.03223,-0.31049 0.0345,-0.6209 0.03879,-5.32947 l 0.0042,-4.88484 h 0.362511 0.362516 l 3.81e-4,2.34411 c 3.07e-4,1.84593 0.0037,2.33976 0.01513,2.32358 0.0081,-0.0113 0.05011,-0.0785 0.09338,-0.14934 0.126519,-0.20718 0.259122,-0.37486 0.440923,-0.55755 0.420206,-0.42226 0.852196,-0.63367 1.47067,-0.71971 0.156581,-0.0218 0.571093,-0.0184 0.744067,0.006 0.699278,0.0991 1.238552,0.41993 1.670484,0.99381 0.40286,0.53525 0.631761,1.14756 0.721233,1.92925 0.02132,0.18644 0.02154,0.96995 3.17e-4,1.16752 -0.06336,0.58955 -0.199898,1.08804 -0.420856,1.53653 -0.546757,1.10979 -1.440683,1.64733 -2.642653,1.58912 z m 0.456104,-0.6822 c 0.932228,-0.12168 1.512713,-0.67791 1.768841,-1.69492 0.159104,-0.63178 0.18716,-1.53034 0.06786,-2.17381 -0.15712,-0.84753 -0.581861,-1.50598 -1.161939,-1.8013 -0.306869,-0.15623 -0.580342,-0.21754 -0.973587,-0.21827 -0.33537,-5.3e-4 -0.537718,0.0369 -0.824669,0.15292 -0.308012,0.12454 -0.642567,0.38412 -0.882439,0.68466 -0.236876,0.2968 -0.412025,0.67489 -0.4876,1.05259 -0.05249,0.26234 -0.05806,0.39371 -0.05281,1.24616 0.0053,0.8672 0.0055,0.86875 0.08313,1.16148 0.189346,0.71374 0.738627,1.30423 1.410732,1.51655 0.08591,0.0271 0.292094,0.0709 0.396727,0.0843 0.104499,0.0133 0.525007,0.007 0.655732,-0.0104 z"
|
||||
id="path1846" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 70.537912,142.37439 c -0.460163,-0.0669 -0.842968,-0.24895 -1.153102,-0.54846 -0.550248,-0.53142 -0.8383,-1.37267 -0.903409,-2.63839 -0.0075,-0.14657 -0.01217,-1.56597 -0.01222,-3.74757 l -1.16e-4,-3.51164 h 0.362548 0.362548 l 0.0048,3.63868 c 0.0048,3.71784 0.0042,3.70908 0.05117,4.13472 0.05598,0.5102 0.185235,1.02315 0.328575,1.30388 0.236347,0.46289 0.598599,0.69175 1.141487,0.72114 l 0.13962,0.008 -0.03567,0.32969 -0.03567,0.32969 -0.06764,-10e-4 c -0.0372,-5.3e-4 -0.119364,-0.009 -0.182579,-0.018 z"
|
||||
id="path1848" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 74.687759,142.39206 c -0.438674,-0.0197 -0.854054,-0.122 -1.21919,-0.30032 -0.200157,-0.0977 -0.290745,-0.15309 -0.474625,-0.28996 -0.537962,-0.40042 -0.94334,-1.00186 -1.148583,-1.70412 -0.04112,-0.14071 -0.08724,-0.36753 -0.116316,-0.57204 -0.03048,-0.21438 -0.03752,-0.82089 -0.01259,-1.08201 0.06128,-0.64148 0.189405,-1.10602 0.440478,-1.59707 0.326755,-0.63905 0.811811,-1.13656 1.375394,-1.4107 0.410306,-0.19957 0.788723,-0.28155 1.294559,-0.28043 0.299021,5.3e-4 0.50546,0.0249 0.750411,0.088 0.882597,0.22757 1.469935,0.90241 1.741805,2.00129 0.10141,0.40989 0.154083,0.81677 0.182203,1.40744 l 0.0076,0.16031 h -2.548287 -2.548287 l 0.0061,0.0575 c 0.0032,0.0316 0.0093,0.11192 0.01323,0.17846 0.03365,0.57114 0.201612,1.13688 0.462777,1.55908 0.166042,0.26844 0.422667,0.54692 0.65513,0.71094 0.295079,0.20821 0.653066,0.34273 1.044744,0.39258 0.197739,0.0252 0.718386,0.0187 0.949505,-0.0119 0.512974,-0.0678 1.079934,-0.22232 1.531371,-0.41744 0.03217,-0.0139 0.06254,-0.0253 0.06752,-0.0253 0.0095,0 0.06935,0.63808 0.06055,0.6454 -0.0026,0.002 -0.0595,0.0274 -0.126047,0.0557 -0.61631,0.26231 -1.278409,0.41151 -1.911594,0.43076 -0.113125,0.004 -0.224731,0.008 -0.248025,0.009 -0.02328,0.002 -0.12673,-0.001 -0.229876,-0.005 z m 2.093034,-4.29643 c -1.32e-4,-0.16386 -0.04334,-0.48319 -0.09136,-0.67545 -0.112416,-0.44993 -0.307927,-0.791 -0.631222,-1.10118 -0.315393,-0.3026 -0.682963,-0.45906 -1.163383,-0.49521 -0.749454,-0.0564 -1.381448,0.23465 -1.862021,0.8575 -0.276368,0.35818 -0.472345,0.81818 -0.552365,1.29652 -0.01058,0.0631 -0.02196,0.12704 -0.02529,0.14202 l -0.0061,0.0272 h 2.165895 2.165895 l -3.7e-5,-0.0514 z"
|
||||
id="path1850" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 89.992597,137.36668 v -4.84552 h 2.480231 2.480231 v 0.33272 0.33271 h -2.11727 -2.11727 v 1.8511 1.8511 h 1.972087 1.972088 v 0.33271 0.33271 h -1.972088 -1.972087 v 2.329 2.329 h -0.362961 -0.362961 z"
|
||||
id="path1852" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 96.416876,139.62007 c -1.32e-4,-2.80904 -0.0053,-3.09289 -0.06728,-3.65078 -0.02027,-0.18271 -0.06885,-0.48979 -0.09429,-0.59606 l -0.01667,-0.0698 0.309875,0.003 0.309874,0.003 0.03889,0.11494 c 0.08135,0.24051 0.12917,0.57711 0.146098,1.02834 l 0.0088,0.23589 0.05851,-0.1391 c 0.194162,-0.46157 0.459602,-0.82564 0.777457,-1.0663 0.118655,-0.0898 0.374475,-0.2173 0.518943,-0.25855 0.237776,-0.0679 0.546878,-0.0903 0.771435,-0.0559 l 0.10586,0.0162 v 0.32596 0.32596 l -0.04535,-0.01 c -0.07363,-0.0156 -0.341916,-0.007 -0.456459,0.0142 -0.325395,0.061 -0.591582,0.20022 -0.844672,0.44186 -0.369946,0.35321 -0.60244,0.79574 -0.715364,1.36164 -0.07461,0.37386 -0.07142,0.26872 -0.0762,2.5131 l -0.0042,2.05375 h -0.362468 -0.362469 z"
|
||||
id="path1854" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 100.40957,138.75804 v -3.45418 h 0.35691 0.35691 v 3.45418 3.45417 h -0.35691 -0.35691 z"
|
||||
id="path1856" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 105.59385,142.39151 c -0.6654,-0.0283 -1.25771,-0.24307 -1.76229,-0.63905 -0.13023,-0.1022 -0.37081,-0.34141 -0.47457,-0.47185 -0.53397,-0.67131 -0.78011,-1.48371 -0.75329,-2.48628 0.008,-0.28217 0.0183,-0.41825 0.0513,-0.64728 0.12221,-0.84944 0.4688,-1.58684 1.00869,-2.14604 0.448,-0.46404 0.99141,-0.73661 1.64587,-0.82555 0.20461,-0.0278 0.61454,-0.0275 0.82683,5.3e-4 1.15679,0.1536 1.85983,0.91064 2.1417,2.30622 0.0702,0.34747 0.12872,0.90685 0.12872,1.23003 v 0.10002 h -2.54072 -2.54072 l 2.4e-4,0.0756 c 3.4e-4,0.10274 0.0304,0.40549 0.0546,0.55015 0.19569,1.16863 0.86478,1.98358 1.82008,2.21685 0.25496,0.0623 0.42348,0.0772 0.78677,0.0695 0.64016,-0.0134 1.19157,-0.12927 1.86194,-0.3912 0.1091,-0.0427 0.20029,-0.0756 0.20264,-0.0732 0.01,0.01 0.0626,0.63225 0.0542,0.64075 -0.0197,0.0198 -0.3998,0.16871 -0.57879,0.22666 -0.63028,0.20408 -1.27382,0.29193 -1.93316,0.26391 z m 2.08308,-4.40477 c -0.0319,-0.66713 -0.26283,-1.2229 -0.67985,-1.63634 -0.3265,-0.32372 -0.69686,-0.48696 -1.19452,-0.52654 -0.43749,-0.0348 -0.88361,0.064 -1.22519,0.27142 -0.36492,0.22154 -0.69867,0.59578 -0.914,1.02486 -0.14147,0.2819 -0.25497,0.65096 -0.29651,0.96414 l -0.008,0.0628 h 2.16304 2.16303 z"
|
||||
id="path1858" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 109.94026,139.37809 c -0.004,-2.56706 -0.006,-2.85806 -0.0251,-3.08819 -0.0222,-0.26713 -0.0759,-0.83071 -0.0886,-0.92857 l -0.007,-0.0575 h 0.33932 0.33931 l 0.0248,0.10586 c 0.0525,0.22428 0.0924,0.5523 0.10604,0.87076 0.0115,0.26976 0.0122,0.27115 0.0761,0.15387 0.31286,-0.57401 0.75972,-0.97794 1.2836,-1.16029 0.58453,-0.20346 1.41078,-0.14755 1.9583,0.13251 0.47048,0.24066 0.81264,0.656 1.01711,1.23465 0.0865,0.2447 0.1633,0.60057 0.19974,0.9251 0.0357,0.31814 0.0423,0.73381 0.0425,2.68288 l 2.1e-4,1.96301 h -0.36242 -0.3624 l -0.005,-2.16264 c -0.005,-2.30523 -0.004,-2.24733 -0.0633,-2.59474 -0.14884,-0.86461 -0.56532,-1.39361 -1.23625,-1.57025 -0.26568,-0.0699 -0.64299,-0.086 -0.87468,-0.0371 -0.32007,0.0674 -0.59469,0.22305 -0.87551,0.49615 -0.38184,0.37133 -0.62351,0.794 -0.72359,1.26547 l -0.0281,0.13272 -0.003,2.23523 -0.003,2.23524 h -0.36229 -0.36229 z"
|
||||
id="path1860" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 119.4287,142.39186 c -0.97254,-0.0437 -1.72903,-0.53095 -2.22292,-1.43187 -0.20145,-0.36746 -0.36031,-0.83543 -0.43809,-1.2905 -0.17154,-1.00371 -0.0764,-2.02894 0.25996,-2.80075 0.38253,-0.87778 1.02011,-1.45538 1.81427,-1.64357 0.51957,-0.12312 1.12835,-0.09 1.60307,0.0872 0.48403,0.18069 0.89719,0.51746 1.22802,1.00097 0.0466,0.0681 0.10646,0.16266 0.13308,0.21019 0.0266,0.0475 0.0525,0.0866 0.0575,0.0867 0.005,1.5e-4 0.009,-1.05322 0.009,-2.34082 v -2.34109 h 0.36296 0.36296 l 3e-5,4.72151 c 3e-5,4.49762 0.004,5.11138 0.0346,5.46256 l 0.009,0.0998 h -0.32852 -0.32852 l -0.007,-0.0635 c -0.009,-0.0755 -0.032,-0.71082 -0.0357,-0.97697 l -0.003,-0.18753 -0.0591,0.0907 c -0.17859,0.27436 -0.47499,0.60953 -0.68659,0.77642 -0.44136,0.34808 -0.92914,0.52623 -1.49259,0.54514 -0.0532,0.002 -0.17574,-2.9e-4 -0.27222,-0.005 z m 0.71745,-0.69856 c 0.19011,-0.0387 0.33272,-0.0881 0.51057,-0.17686 0.19803,-0.0988 0.35003,-0.20695 0.51835,-0.36891 0.28197,-0.27134 0.48911,-0.61224 0.59377,-0.97721 0.0952,-0.33198 0.1071,-0.50072 0.10047,-1.42495 -0.006,-0.81632 -0.01,-0.87163 -0.0869,-1.17193 -0.11845,-0.46217 -0.34226,-0.83582 -0.71116,-1.18728 -0.31968,-0.30456 -0.66926,-0.47818 -1.09812,-0.54541 -0.16881,-0.0265 -0.52855,-0.0264 -0.70059,1.6e-4 -0.24499,0.0378 -0.49288,0.11817 -0.67013,0.2173 -0.59476,0.3326 -1.01376,1.0442 -1.14621,1.94662 -0.082,0.55841 -0.0544,1.32786 0.0679,1.89545 0.19097,0.8863 0.63787,1.46184 1.31894,1.69858 0.28636,0.0995 0.49681,0.12927 0.8821,0.12459 0.24186,-0.003 0.3122,-0.008 0.42103,-0.0302 z"
|
||||
id="path1862" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 126.58508,142.37401 c -0.7441,-0.1157 -1.27117,-0.50286 -1.61454,-1.18599 -0.21576,-0.42923 -0.33958,-0.91494 -0.42053,-1.64962 -0.019,-0.17193 -0.021,-0.49629 -0.0245,-3.8988 l -0.004,-3.71127 h 0.35644 0.35645 l 0.005,3.54794 c 0.005,3.61064 0.006,3.70542 0.0515,4.15287 0.0676,0.6674 0.21893,1.21647 0.42153,1.52965 0.0695,0.10744 0.23303,0.27902 0.32659,0.34265 0.20221,0.13754 0.50553,0.22655 0.77273,0.22675 0.066,5e-5 0.0938,0.004 0.0938,0.0149 0,0.0265 -0.0614,0.59531 -0.0673,0.62337 -0.005,0.0246 -0.0124,0.0271 -0.0751,0.0259 -0.0382,-5.3e-4 -0.11833,-0.009 -0.17822,-0.0183 z"
|
||||
id="path1864" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 127.7324,145.35991 c -0.004,-0.0178 -0.0421,-0.15751 -0.0836,-0.31056 -0.0415,-0.15305 -0.0746,-0.2787 -0.0736,-0.27923 0.001,-5.3e-4 0.0705,-0.0231 0.15437,-0.0501 0.59914,-0.19319 1.11392,-0.61986 1.53875,-1.27538 0.2291,-0.35349 0.45727,-0.82877 0.63229,-1.31709 0.0502,-0.14016 0.0546,-0.20052 0.0225,-0.31314 -0.0102,-0.0358 -0.5784,-1.45407 -1.26265,-3.1517 -0.68425,-1.69763 -1.26872,-3.14785 -1.29881,-3.22271 l -0.0547,-0.13611 h 0.37797 0.37798 l 0.62696,1.5698 c 0.34484,0.8634 0.87064,2.17958 1.16844,2.92486 0.29781,0.74528 0.5461,1.35384 0.55176,1.35235 0.006,-0.002 0.50152,-1.31572 1.1019,-2.92052 0.60038,-1.60481 1.09364,-2.91985 1.09611,-2.92233 0.003,-0.003 0.17572,-0.003 0.38497,-0.001 l 0.38046,0.003 -0.79047,1.98418 c -1.77735,4.46137 -2.01866,5.06474 -2.0928,5.23269 -0.68871,1.56017 -1.57313,2.50635 -2.64993,2.83496 l -0.0998,0.0305 z"
|
||||
id="path1866" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 147.05605,142.39197 c -0.76825,-0.0274 -1.45102,-0.23495 -2.00838,-0.61046 -0.0732,-0.0493 -0.1429,-0.0973 -0.15489,-0.10653 -0.0205,-0.0159 -0.0135,-0.0364 0.11888,-0.34769 0.0774,-0.18196 0.1427,-0.33284 0.14516,-0.33529 0.003,-0.003 0.0551,0.0336 0.11704,0.08 0.35576,0.26692 0.7116,0.43761 1.14209,0.54786 0.28966,0.0742 0.47291,0.0975 0.82376,0.10499 0.33529,0.007 0.48955,-0.004 0.72374,-0.0512 0.70304,-0.1424 1.24493,-0.57879 1.4436,-1.16254 0.0977,-0.28702 0.13792,-0.63374 0.11224,-0.96706 -0.0465,-0.60262 -0.2178,-0.97667 -0.58967,-1.28709 -0.32454,-0.27092 -0.67833,-0.46033 -1.25163,-0.67011 -0.6697,-0.24504 -1.08548,-0.45919 -1.4927,-0.76882 -0.14989,-0.11396 -0.43634,-0.39722 -0.5375,-0.53151 -0.34518,-0.45821 -0.50377,-0.97168 -0.48098,-1.55734 0.0282,-0.72534 0.27565,-1.25816 0.78826,-1.69748 0.34079,-0.29206 0.75946,-0.50034 1.19386,-0.59391 0.24353,-0.0524 0.35146,-0.0638 0.66934,-0.0702 0.56757,-0.0114 1.05374,0.0604 1.48926,0.21979 0.1272,0.0466 0.42057,0.1876 0.51541,0.24777 l 0.0568,0.036 -0.146,0.29984 c -0.0803,0.16492 -0.14983,0.30408 -0.15451,0.30926 -0.005,0.005 -0.0544,-0.016 -0.11041,-0.0471 -0.38284,-0.21229 -0.83065,-0.349 -1.28744,-0.39301 -0.18737,-0.018 -0.61451,-0.008 -0.7684,0.0188 -0.45543,0.0782 -0.81327,0.24969 -1.08435,0.51973 -0.22271,0.22186 -0.34527,0.45664 -0.41185,0.78898 -0.031,0.15489 -0.0311,0.5621 -9e-5,0.70596 0.10269,0.47659 0.35349,0.8406 0.81802,1.18722 0.26831,0.2002 0.59107,0.36927 1.09568,0.57396 0.92096,0.37356 1.4131,0.66047 1.80326,1.05124 0.23261,0.23298 0.36319,0.44472 0.46447,0.75316 0.11162,0.33991 0.15126,0.64579 0.14238,1.09875 -0.007,0.33168 -0.02,0.45179 -0.0768,0.68358 -0.14897,0.60758 -0.51561,1.10908 -1.08106,1.4787 -0.56172,0.36718 -1.19687,0.52128 -2.02653,0.49166 z"
|
||||
id="path1868" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 154.34551,142.39237 c -0.93633,-0.0436 -1.74092,-0.46045 -2.29038,-1.18669 -0.1415,-0.18703 -0.2246,-0.32227 -0.32938,-0.53606 -0.17644,-0.36 -0.28018,-0.71552 -0.3407,-1.16752 -0.0313,-0.23338 -0.0309,-0.91863 5.3e-4,-1.17962 0.0979,-0.81132 0.33808,-1.44177 0.7663,-2.01189 0.12116,-0.16132 0.37713,-0.42994 0.51786,-0.54349 0.40208,-0.32438 0.86305,-0.51957 1.3974,-0.59169 0.18197,-0.0246 0.60018,-0.0275 0.78037,-0.005 0.8494,0.10383 1.46071,0.52612 1.83722,1.26914 0.28454,0.56151 0.43276,1.24515 0.47459,2.18889 l 0.008,0.1845 h -2.54533 -2.54534 l 0.009,0.16555 c 0.0563,1.06062 0.47749,1.90828 1.18294,2.38047 0.27673,0.18522 0.63503,0.31124 1.01604,0.35736 0.13282,0.0161 0.57208,0.0159 0.75043,-3.2e-4 0.53418,-0.0485 1.11645,-0.19394 1.64319,-0.41031 0.0686,-0.0282 0.1265,-0.0494 0.12856,-0.047 0.002,0.002 0.017,0.14769 0.0332,0.32301 l 0.0294,0.31877 -0.0673,0.0298 c -0.61392,0.27213 -1.32296,0.43735 -1.96676,0.45829 -0.10646,0.004 -0.21807,0.008 -0.24802,0.01 -0.0299,0.002 -0.13883,-5.3e-4 -0.24197,-0.005 z m 2.09294,-4.31489 c -2e-4,-0.10786 -0.0332,-0.3947 -0.0616,-0.53536 -0.10447,-0.51724 -0.33713,-0.93701 -0.70159,-1.26584 -0.36904,-0.33297 -0.88736,-0.49499 -1.44846,-0.45276 -0.30028,0.0226 -0.50922,0.0779 -0.7584,0.20095 -0.21202,0.10465 -0.35671,0.21048 -0.54464,0.39836 -0.31889,0.31879 -0.52107,0.6493 -0.66579,1.0883 -0.0619,0.18767 -0.13858,0.51844 -0.13858,0.59748 v 0.0385 h 2.15961 2.15962 l -1.3e-4,-0.0696 z"
|
||||
id="path1870" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 161.33855,142.392 c -1.08146,-0.0419 -1.9512,-0.51556 -2.50721,-1.36546 -0.10294,-0.15736 -0.25407,-0.45753 -0.32128,-0.63812 -0.19148,-0.51457 -0.28369,-1.12781 -0.26423,-1.75742 0.033,-1.06909 0.31429,-1.85258 0.89587,-2.49558 0.42512,-0.47001 0.97385,-0.7784 1.61611,-0.90825 0.72196,-0.14596 1.606,-0.0776 2.23826,0.17321 0.12357,0.049 0.36769,0.16666 0.44228,0.21315 l 0.037,0.023 -0.12413,0.32509 c -0.0683,0.1788 -0.12584,0.32681 -0.12794,0.32891 -0.002,0.002 -0.0418,-0.0209 -0.0883,-0.0513 -0.12185,-0.0795 -0.37704,-0.20073 -0.53816,-0.25561 -0.62717,-0.21363 -1.41267,-0.22325 -1.98375,-0.0243 -0.73735,0.25688 -1.30777,0.93118 -1.52981,1.80841 -0.0831,0.32819 -0.11001,0.57875 -0.10909,1.01444 0.001,0.38027 0.0158,0.54993 0.073,0.828 0.22062,1.07202 0.91126,1.84151 1.84616,2.05697 0.36099,0.0832 0.91731,0.0829 1.3345,-5.3e-4 0.33635,-0.0674 0.6823,-0.20489 0.91791,-0.36472 0.0544,-0.0369 0.0733,-0.0445 0.0809,-0.0322 0.026,0.042 0.24219,0.62052 0.23699,0.63407 -0.0101,0.0261 -0.44052,0.23221 -0.59454,0.28462 -0.48293,0.16433 -0.94852,0.22632 -1.53048,0.20377 z"
|
||||
id="path1872" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 166.87975,142.39219 c -1.09932,-0.0508 -1.77948,-0.75484 -2.00236,-2.07265 -0.0851,-0.50312 -0.0905,-0.68132 -0.0906,-3.00428 l -1.2e-4,-2.0114 h 0.36243 0.36243 l 0.005,2.11425 c 0.005,2.27223 0.003,2.2088 0.0684,2.61029 0.11061,0.68288 0.34684,1.15772 0.71377,1.4347 0.11673,0.0881 0.30387,0.17589 0.46903,0.21998 0.14095,0.0376 0.14586,0.038 0.43294,0.0383 0.31989,2e-4 0.42138,-0.014 0.65333,-0.0913 0.51872,-0.1729 0.98371,-0.59183 1.24056,-1.11767 0.11752,-0.24057 0.18647,-0.48562 0.21689,-0.77082 0.008,-0.0765 0.0126,-0.87114 0.0126,-2.27757 v -2.1601 h 0.36268 0.36269 l 0.004,2.77968 c 0.004,2.55555 0.006,2.79126 0.0248,2.92345 0.0476,0.32802 0.13184,0.694 0.23181,1.00724 0.0293,0.0918 0.0522,0.16798 0.0509,0.1692 -0.001,0.001 -0.14395,0.0384 -0.31696,0.0827 -0.17301,0.0443 -0.32331,0.083 -0.334,0.0862 -0.0443,0.0128 -0.21624,-0.67688 -0.28249,-1.13319 -0.0166,-0.11446 -0.0325,-0.21037 -0.0352,-0.21315 -0.003,-0.003 -0.0226,0.0256 -0.044,0.0631 -0.10303,0.18009 -0.3558,0.49817 -0.50744,0.63856 -0.38939,0.36051 -0.82188,0.57279 -1.33751,0.65651 -0.15846,0.0257 -0.41937,0.0375 -0.62308,0.0281 z"
|
||||
id="path1874" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 171.97328,139.71685 c -10e-6,-2.8742 -0.008,-3.25807 -0.085,-3.8625 -0.0226,-0.17862 -0.0767,-0.49221 -0.0915,-0.53078 -0.007,-0.0179 0.0209,-0.0197 0.30138,-0.0197 h 0.30894 l 0.0264,0.0696 c 0.0401,0.10575 0.0913,0.31969 0.11376,0.47487 0.0186,0.12825 0.0288,0.26245 0.0524,0.68357 l 0.009,0.15729 0.035,-0.0907 c 0.0553,-0.14348 0.20724,-0.43657 0.2964,-0.57185 0.31828,-0.48289 0.72215,-0.76117 1.23538,-0.85122 0.14036,-0.0246 0.46486,-0.0259 0.58074,-0.002 l 0.0786,0.016 0.003,0.32456 0.003,0.32456 -0.0335,-0.007 c -0.13988,-0.0275 -0.38784,-0.0144 -0.56781,0.03 -0.41872,0.10307 -0.78347,0.37645 -1.07019,0.8021 -0.19819,0.29421 -0.33003,0.63119 -0.40431,1.03334 -0.0656,0.35503 -0.0661,0.3745 -0.0661,2.52689 v 1.9883 h -0.36296 -0.36296 l -1e-5,-2.49536 z"
|
||||
id="path1876" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.0120987;stroke-linejoin:bevel"
|
||||
d="m 178.51869,142.39237 c -0.87552,-0.0378 -1.62691,-0.39454 -2.17739,-1.03372 -0.44146,-0.51261 -0.68852,-1.09177 -0.79453,-1.8626 -0.0308,-0.22357 -0.0312,-0.90321 -5.3e-4,-1.15542 0.0412,-0.34132 0.0893,-0.57998 0.17202,-0.85315 0.2745,-0.90691 0.87117,-1.66615 1.60037,-2.0364 0.44485,-0.22586 0.96728,-0.32731 1.5107,-0.29336 0.5693,0.0356 1.00217,0.1861 1.3773,0.47894 0.10919,0.0853 0.2997,0.27809 0.38237,0.38707 0.3813,0.50265 0.6127,1.19849 0.7001,2.10518 0.0168,0.17461 0.0366,0.49894 0.0366,0.6019 l 4e-5,0.0817 h -2.5426 -2.5426 l 0.008,0.16125 c 0.025,0.52051 0.14698,1.01238 0.35318,1.42368 0.12473,0.24878 0.26497,0.45022 0.44112,0.63357 0.33792,0.35174 0.73007,0.56283 1.21951,0.65643 0.14112,0.027 0.20985,0.0326 0.45819,0.0378 0.17493,0.004 0.35699,3.9e-4 0.4537,-0.008 0.53796,-0.0467 1.05367,-0.17101 1.5935,-0.38408 0.10884,-0.043 0.19966,-0.0764 0.20182,-0.0742 0.002,0.002 0.0151,0.12906 0.0288,0.28201 0.0137,0.15295 0.0274,0.29599 0.0304,0.31787 0.005,0.0383 0.003,0.0409 -0.0634,0.0707 -0.5972,0.26839 -1.32847,0.43829 -1.97442,0.45871 -0.1098,0.004 -0.21868,0.008 -0.24198,0.009 -0.0233,0.002 -0.12673,-5.3e-4 -0.22987,-0.005 z m 2.07711,-4.39518 c -0.0197,-0.38967 -0.11707,-0.78249 -0.26862,-1.0842 -0.15264,-0.3039 -0.41191,-0.60374 -0.67806,-0.78415 -0.33987,-0.23038 -0.79273,-0.34064 -1.25576,-0.30574 -0.3014,0.0227 -0.52695,0.0829 -0.77603,0.20692 -0.19637,0.0978 -0.3464,0.20757 -0.5206,0.38087 -0.34692,0.34511 -0.57419,0.73203 -0.71275,1.21346 -0.0424,0.14728 -0.10355,0.43817 -0.10355,0.49253 v 0.0302 h 2.16148 2.16148 z"
|
||||
id="path1878" />
|
||||
<path
|
||||
style="fill:#ff7f2a;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.00206706;stroke-linejoin:bevel"
|
||||
d="m 83.478942,142.33576 c -0.101097,-0.008 -0.184928,-0.0352 -0.26252,-0.084 -0.03842,-0.0242 -0.07209,-0.0516 -0.11077,-0.0902 -0.07477,-0.0746 -0.122158,-0.14429 -0.158978,-0.23387 -0.06123,-0.14895 -0.06985,-0.33629 -0.02296,-0.4986 0.0417,-0.14423 0.13607,-0.28586 0.252386,-0.37868 0.137535,-0.10976 0.312118,-0.14649 0.484727,-0.10201 0.07326,0.0189 0.1455,0.0555 0.213937,0.10833 0.03043,0.0235 0.100541,0.0934 0.127312,0.12702 0.09297,0.11659 0.143071,0.23572 0.16038,0.38138 0.0037,0.0301 0.0037,0.13316 0,0.16329 -0.02053,0.17271 -0.08955,0.30826 -0.220568,0.43316 -0.108812,0.10372 -0.236495,0.16232 -0.377999,0.17349 -0.02482,0.002 -0.06584,0.002 -0.08496,5.3e-4 z"
|
||||
id="path2003" />
|
||||
<path
|
||||
style="fill:#ff7f2a;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.00366559;stroke-linejoin:bevel"
|
||||
d="m 138.83867,142.3338 c -0.17242,-0.0105 -0.32504,-0.0983 -0.44225,-0.25441 -0.0826,-0.10996 -0.12606,-0.22887 -0.1377,-0.37655 -0.0173,-0.21868 0.0597,-0.4297 0.21659,-0.59397 0.0856,-0.0896 0.19239,-0.14843 0.30862,-0.1699 0.0471,-0.009 0.14272,-0.009 0.19037,-2e-4 0.12784,0.0231 0.23969,0.0859 0.34028,0.1913 0.13392,0.14028 0.20368,0.3001 0.21195,0.48563 0.009,0.1971 -0.0578,0.37354 -0.19499,0.51645 -0.14021,0.14607 -0.30426,0.21319 -0.49287,0.20165 z"
|
||||
id="path2005" />
|
||||
<path
|
||||
style="fill:#5c5f5c;fill-opacity:1;stroke:#5c5f5c;stroke-width:0.00751464;stroke-linejoin:bevel"
|
||||
d="m 100.61317,133.80153 c -0.16331,-0.032 -0.28985,-0.12548 -0.35646,-0.2632 -0.058,-0.11999 -0.0687,-0.17368 -0.0685,-0.34483 1.5e-4,-0.12876 0.002,-0.15428 0.0174,-0.21041 0.0576,-0.21387 0.20173,-0.36782 0.39199,-0.4187 0.061,-0.0163 0.21772,-0.018 0.27805,-0.003 0.0605,0.015 0.15563,0.0617 0.2021,0.0991 0.10363,0.0835 0.1931,0.22075 0.22745,0.34901 0.0235,0.0877 0.0236,0.28173 1.8e-4,0.36822 -0.0588,0.21726 -0.20702,0.36868 -0.40794,0.41685 -0.0677,0.0162 -0.21841,0.02 -0.28424,0.007 z"
|
||||
id="path2161" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="heading"
|
||||
transform="translate(-18.242052,-92.23446)">
|
||||
<g
|
||||
aria-label="Infix"
|
||||
id="text1160"
|
||||
style="font-size:25.4px;font-family:'URW Bookman';-inkscape-font-specification:'URW Bookman, Normal';fill:#5c5f5c;stroke-width:1.029"
|
||||
inkscape:label="infix"
|
||||
transform="matrix(2,0,0,2,-99.78739,-118.81694)">
|
||||
<path
|
||||
d="m 85.532731,107.6593 0.508,0.0762 c 1.3462,0.1778 1.6256,0.635 1.651,2.794 v 9.9822 c -0.0254,2.159 -0.3048,2.6162 -1.651,2.794 l -0.508,0.0762 v 0.7874 h 6.858 v -0.7874 l -0.508,-0.0762 c -1.3462,-0.1778 -1.6256,-0.635 -1.651,-2.794 v -9.9822 c 0.0254,-2.159 0.3048,-2.6162 1.651,-2.794 l 0.508,-0.0762 v -0.7874 h -6.858 z"
|
||||
id="path2431" />
|
||||
<path
|
||||
d="m 98.588334,111.8503 h -0.8636 c -1.5748,0.3048 -2.413,0.4064 -3.8354,0.508 v 0.762 l 0.6096,0.0508 c 1.3462,0.1524 1.6764,0.5588 1.6764,2.1082 v 5.969 c 0,1.397 -0.4572,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.832596 v -0.762 l -0.635,-0.0508 c -1.117596,-0.1016 -1.574796,-0.7112 -1.574796,-2.1082 v -6.2738 c 1.574796,-1.4224 3.073396,-2.1336 4.495796,-2.1336 1.397,0 2.159,0.889 2.159,2.54 v 5.8674 c 0,1.397 -0.4826,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.8326 v -0.762 l -0.635,-0.0508 c -1.1176,-0.1016 -1.5748,-0.7112 -1.5748,-2.1082 v -6.2738 c 0,-2.1082 -1.4224,-3.3528 -3.8354,-3.3528 -1.9558,0 -3.429,0.635 -5.232396,2.2352 z"
|
||||
id="path2433" />
|
||||
<path
|
||||
d="m 115.27613,111.8503 v -1.5748 c 0,-1.651 0.0508,-1.9812 0.4064,-2.5908 0.4826,-0.8382 1.4986,-1.3462 2.6416,-1.3462 1.27,0 2.4638,0.762 2.4638,1.6256 0.0254,1.0414 0.0254,1.0414 0.2286,1.397 0.2286,0.4064 0.635,0.635 1.143,0.635 0.762,0 1.27,-0.5334 1.27,-1.27 0,-0.5842 -0.2286,-1.0668 -0.762,-1.6256 -1.016,-1.0414 -2.413,-1.5748 -4.191,-1.5748 -1.9558,0 -3.7338,0.6604 -4.6482,1.7272 -0.6858,0.8128 -0.9652,1.8542 -0.9652,3.6322 v 0.9652 h -2.159 v 0.8382 h 2.159 v 8.5598 c 0,1.397 -0.4572,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.8326 v -0.762 l -0.635,-0.0508 c -1.1176,-0.1016 -1.5748,-0.7112 -1.5748,-2.1082 v -8.5598 h 3.3782 c 1.8288,0 2.3368,0.5842 2.3114,2.667 v 5.8928 c 0,1.397 -0.4572,2.0066 -1.5748,2.1082 l -0.635,0.0508 v 0.762 h 6.8326 v -0.762 l -0.635,-0.0508 c -1.1176,-0.1016 -1.5748,-0.7112 -1.5748,-2.1082 v -9.398 z"
|
||||
id="path2435" />
|
||||
<path
|
||||
d="m 134.09755,117.0827 2.2098,-2.3622 c 1.397,-1.3716 2.1082,-1.778 3.81,-2.1082 v -0.762 h -5.3594 v 0.762 c 0.9144,0.1016 1.143,0.2286 1.143,0.6604 0,0.2032 -0.0762,0.4064 -0.254,0.6096 l -2.159,2.4384 -2.0066,-2.4384 c -0.1778,-0.254 -0.3048,-0.4826 -0.3048,-0.635 0,-0.3556 0.3048,-0.5334 1.0414,-0.6096 l 0.254,-0.0254 v -0.762 h -6.2992 v 0.762 c 1.2192,0.1778 1.7018,0.4826 2.5908,1.5494 l 2.9718,3.7338 -3.2258,3.6322 c -1.143,1.2954 -1.8034,1.7018 -3.0226,1.8796 v 0.762 h 5.6896 v -0.762 c -0.889,-0.1524 -1.0922,-0.2032 -1.27,-0.3048 -0.254,-0.1524 -0.4318,-0.4064 -0.4318,-0.6604 0,-0.254 0.1778,-0.5842 0.4572,-0.9144 l 2.54,-2.8448 2.286,3.2004 c 0.1524,0.2286 0.254,0.4826 0.254,0.6858 0,0.4826 -0.3048,0.6604 -1.524,0.8382 v 0.762 h 7.0358 v -0.762 c -1.3716,-0.2286 -1.8288,-0.5334 -2.9464,-1.8796 z"
|
||||
id="path2437" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="tux"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="display:inline"
|
||||
transform="translate(-18.242052,-92.23446)">
|
||||
<path
|
||||
id="rect1698"
|
||||
style="fill:#f9f9f9;stroke:#5c5f5c;stroke-width:2.058;stroke-linejoin:round"
|
||||
inkscape:label="body"
|
||||
d="M 19.271052,95.241499 H 64.21261 V 129.18306 H 19.271052 Z" />
|
||||
<path
|
||||
id="rect184-3"
|
||||
style="fill:#ff7f2a;stroke:#5c5f5c;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:label="foot-r"
|
||||
d="m 52.21261,121.18306 h 12 v 8 h -12 z" />
|
||||
<path
|
||||
id="rect184"
|
||||
style="fill:#ff7f2a;stroke:#5c5f5c;stroke-width:2;stroke-linejoin:round"
|
||||
inkscape:label="foot-l"
|
||||
d="m 19.271052,121.18306 h 12 v 8 h -12 z" />
|
||||
<path
|
||||
id="path11801"
|
||||
style="fill:#ff7f2a;stroke-width:2"
|
||||
inkscape:label="nose"
|
||||
inkscape:transform-center-y="0.98302844"
|
||||
d="m 41.741828,115.16138 -3.405326,-5.8982 h 6.81065 z" />
|
||||
<path
|
||||
id="path6519-5"
|
||||
style="display:inline;fill:#5c5f5c;stroke-width:9.99998"
|
||||
inkscape:label="eye-r"
|
||||
d="m 47.634774,103.22688 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z" />
|
||||
<path
|
||||
id="path6519"
|
||||
style="fill:#5c5f5c;stroke-width:9.99998"
|
||||
inkscape:label="eye-l"
|
||||
d="m 37.84887,103.22688 a 1,1 0 0 1 -1,1 1,1 0 0 1 -1,-1 1,1 0 0 1 1,-1 1,1 0 0 1 1,1 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 45 KiB |
@@ -42,5 +42,4 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/rauc-installation-status/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/bootloader-splashscreen/Config.in"
|
||||
endmenu
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ BIN_LICENSE = BSD-3-Clause
|
||||
BIN_LICENSE_FILES = LICENSE
|
||||
BIN_REDISTRIBUTE = NO
|
||||
BIN_DEPENDENCIES = sysrepo libite
|
||||
BIN_CONF_OPTS = --prefix= --disable-silent-rules
|
||||
BIN_CONF_OPTS = --disable-silent-rules
|
||||
BIN_AUTORECONF = YES
|
||||
|
||||
define BIN_CONF_ENV
|
||||
|
||||
@@ -7,7 +7,5 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/board/marvell-espressobin/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/board/microchip-sparx5-pcb135/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/board/styx-dcp-sc-28p/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/board/raspberry-pi-4/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/board/banana-pi-r3/Config.in"
|
||||
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
config BR2_PACKAGE_BANANA_PI_R3
|
||||
bool "Banana Pi R3"
|
||||
depends on BR2_aarch64
|
||||
select BR2_PACKAGE_FEATURE_WIFI
|
||||
select BR2_PACKAGE_LINUX_FIRMWARE
|
||||
select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK
|
||||
select BR2_PACKAGE_LINUX_FIRMWARE_MEDIATEK_MT7986
|
||||
select SDCARD_AUX
|
||||
help
|
||||
Build Banana PI R3 support
|
||||
@@ -1,40 +0,0 @@
|
||||
GENIMAGE_CFG="$(BUILD_DIR)/genimage.cfg"
|
||||
GENIMAGE_TMP="$(BUILD_DIR)/genimage.tmp"
|
||||
BOARD_DIR="$(BR2_EXTERNAL_INFIX_PATH)/src/board/banana-pi-r3"
|
||||
|
||||
define BANANA_PI_R3_LINUX_CONFIG_FIXUPS
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_ARCH_MEDIATEK)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MACH_MT7986)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_PINCTRL_MT7986)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_SERIAL_8250_MT6577)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_I2C_GPIO,y)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MTK_THERMAL,m)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MTK_UART)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MTK_WATCHDOG)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MEDIATEK_GE_PHY)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_REALTEK_PHY)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_NET_VENDOR_MEDIATEK)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_NET_MEDIATEK_SOC)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_NET_DSA_MT7530,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MT7915E,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_PCIE_MEDIATEK_GEN3,m)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MTK_SCPSYS)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MMC_MTK)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MTK_HSDMA)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MT7915E,m)
|
||||
$(call KCONFIG_ENABLE_OPT,CONFIG_MT798X_WMAC)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_MTK_SOC_THERMAL,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_I2C_MT65XX,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_USB_XHCI_MTK,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_PHY_MTK_TPHY,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_PHY_MTK_XSPHY,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_REGULATOR_GPIO,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_REGULATOR_MT6380,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_PWM_MEDIATEK,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_SENSORS_PWM_FAN,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_NVMEM_MTK_EFUSE,m)
|
||||
$(call KCONFIG_SET_OPT,CONFIG_CRYPTO_DEV_SAFEXCEL,m)
|
||||
endef
|
||||
|
||||
$(eval $(ix-board))
|
||||
$(eval $(generic-package))
|
||||
@@ -1,7 +1,6 @@
|
||||
config BR2_PACKAGE_RASPBERRY_PI_4
|
||||
bool "Raspberry Pi 4"
|
||||
depends on BR2_aarch64
|
||||
select SDCARD_AUX
|
||||
select BR2_PACKAGE_FEATURE_WIFI
|
||||
select BR2_PACKAGE_BRCMFMAC_SDIO_FIRMWARE_RPI
|
||||
select BR2_PACKAGE_BRCMFMAC_SDIO_FIRMWARE_RPI_WIFI
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
config BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN
|
||||
bool "bootloader-splashscreen"
|
||||
help
|
||||
Install a BMP splash screen image for bootloader.
|
||||
|
||||
config BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN_PATH
|
||||
string "Path to BMP image"
|
||||
default "$(BR2_EXTERNAL_INFIX_PATH)/board/common/uboot/splash.bmp"
|
||||
depends on BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN
|
||||
help
|
||||
Path to the BMP image file to be used as bootloader splash screen.
|
||||
The image will be installed to output/images/.
|
||||
@@ -1,19 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# bootloader-splashscreen
|
||||
#
|
||||
################################################################################
|
||||
|
||||
BOOTLOADER_SPLASHSCREEN_VERSION = 1.0
|
||||
BOOTLOADER_SPLASHSCREEN_SOURCE =
|
||||
BOOTLOADER_SPLASHSCREEN_SITE =
|
||||
|
||||
define BOOTLOADER_SPLASHSCREEN_BUILD_CMDS
|
||||
# Nothing to build
|
||||
endef
|
||||
|
||||
define BOOTLOADER_SPLASHSCREEN_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0644 $(call qstrip,$(BR2_PACKAGE_BOOTLOADER_SPLASHSCREEN_PATH)) $(BINARIES_DIR)/splash.bmp
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -25,3 +25,7 @@ run name:failure log:prio:user.crit norestart env:/etc/default/confd \
|
||||
run name:error :2 log:console norestart \
|
||||
if:<run/failure/failure> \
|
||||
[S] /usr/libexec/confd/error --
|
||||
|
||||
service name:netopeer notify:none log env:/etc/default/confd \
|
||||
[12345] <pid/confd> netopeer2-server -F -t $CONFD_TIMEOUT -v 1 \
|
||||
-- NETCONF server
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Locally calculated
|
||||
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
||||
sha256 a6fe769a8f24a065b274f4e0f5f1fec634b2aae5bdd586aa6f71fe60ca6b8a64 klish-plugin-sysrepo-30bc6294489807ea026abed20f94d1ad1583cc6b-git4.tar.gz
|
||||
sha256 598089ad964594bbbaf2b7d7214d8761c828174eef53b7cfb1cd98517f407d01 klish-plugin-sysrepo-b693714a1ff5f8021651d7619556afb19945e5e6-git4.tar.gz
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
################################################################################
|
||||
|
||||
KLISH_PLUGIN_SYSREPO_VERSION = 30bc6294489807ea026abed20f94d1ad1583cc6b
|
||||
KLISH_PLUGIN_SYSREPO_VERSION = b693714a1ff5f8021651d7619556afb19945e5e6
|
||||
KLISH_PLUGIN_SYSREPO_SITE = https://github.com/kernelkit/klish-plugin-sysrepo.git
|
||||
#KLISH_PLUGIN_SYSREPO_VERSION = cdd3eb51a7f7ee0ed5bd925fa636061d3b1b85fb
|
||||
#KLISH_PLUGIN_SYSREPO_SITE = https://src.libcode.org/pkun/klish-plugin-sysrepo.git
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
From 22b41e7ed8268b94ccc139138e2037c390a3a616 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Thu, 10 Jul 2025 15:00:45 +0200
|
||||
Subject: [PATCH] Implement `SchemaNode::actionRpcs()`
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This function allows to access list of actions
|
||||
within schema node.
|
||||
|
||||
It is implemented same as `Module::actionRpcs`
|
||||
to follow same approach, so there is no need to use
|
||||
collections.
|
||||
|
||||
Change-Id: Ie99908cfdd334433fd9ae6f1a909f196e61139fd
|
||||
Signed-off-by: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/SchemaNode.hpp | 1 +
|
||||
src/SchemaNode.cpp | 12 ++++++++++++
|
||||
tests/example_schema.hpp | 28 ++++++++++++++++++++++++++++
|
||||
tests/schema_node.cpp | 21 +++++++++++++++++++++
|
||||
4 files changed, 62 insertions(+)
|
||||
|
||||
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
|
||||
index 0f1a4c4..9f49fd7 100644
|
||||
--- a/include/libyang-cpp/SchemaNode.hpp
|
||||
+++ b/include/libyang-cpp/SchemaNode.hpp
|
||||
@@ -79,6 +79,7 @@ public:
|
||||
Collection<SchemaNode, IterationType::Sibling> siblings() const;
|
||||
Collection<SchemaNode, IterationType::Sibling> immediateChildren() const;
|
||||
std::vector<ExtensionInstance> extensionInstances() const;
|
||||
+ std::vector<SchemaNode> actionRpcs() const;
|
||||
|
||||
std::vector<When> when() const;
|
||||
|
||||
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||
index 26b5099..ce24203 100644
|
||||
--- a/src/SchemaNode.cpp
|
||||
+++ b/src/SchemaNode.cpp
|
||||
@@ -117,6 +117,18 @@ Collection<SchemaNode, IterationType::Sibling> SchemaNode::immediateChildren() c
|
||||
return c ? c->siblings() : Collection<SchemaNode, IterationType::Sibling>{nullptr, nullptr};
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns a collection of action nodes (not RPC nodes) as SchemaNode
|
||||
+ */
|
||||
+std::vector<SchemaNode> SchemaNode::actionRpcs() const
|
||||
+{
|
||||
+ std::vector<SchemaNode> res;
|
||||
+ for (auto action = reinterpret_cast<const struct lysc_node*>(lysc_node_actions(m_node)); action; action = action->next) {
|
||||
+ res.emplace_back(SchemaNode{action, m_ctx});
|
||||
+ }
|
||||
+ return res;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Returns the YANG description of the node.
|
||||
*
|
||||
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||
index 0d8acb9..02d3d74 100644
|
||||
--- a/tests/example_schema.hpp
|
||||
+++ b/tests/example_schema.hpp
|
||||
@@ -214,6 +214,34 @@ module example-schema {
|
||||
}
|
||||
|
||||
container bigTree {
|
||||
+ action firstAction {
|
||||
+ input {
|
||||
+ leaf inputLeaf1 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ output {
|
||||
+ leaf outputLeaf1 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ action secondAction {
|
||||
+ input {
|
||||
+ leaf inputLeaf2 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ output {
|
||||
+ leaf outputLeaf2 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
container one {
|
||||
leaf myLeaf {
|
||||
type string;
|
||||
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||
index 0001377..65ef462 100644
|
||||
--- a/tests/schema_node.cpp
|
||||
+++ b/tests/schema_node.cpp
|
||||
@@ -544,6 +544,27 @@ TEST_CASE("SchemaNode")
|
||||
REQUIRE(elem.extensionInstances()[2].argument() == "last-modified");
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("SchemaNode::actionRpcs")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("no actions")
|
||||
+ {
|
||||
+ auto actions = ctx->findPath("/example-schema:presenceContainer").actionRpcs();
|
||||
+ REQUIRE(actions.size() == 0);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("two actions")
|
||||
+ {
|
||||
+ auto actions = ctx->findPath("/example-schema:bigTree").actionRpcs();
|
||||
+ REQUIRE(actions.size() == 2);
|
||||
+ REQUIRE(actions[0].asActionRpc().name() == "firstAction");
|
||||
+ REQUIRE(actions[0].asActionRpc().input().child()->name() == "inputLeaf1");
|
||||
+ REQUIRE(actions[0].asActionRpc().output().child()->name() == "outputLeaf1");
|
||||
+ REQUIRE(actions[1].asActionRpc().name() == "secondAction");
|
||||
+ REQUIRE(actions[1].asActionRpc().input().child()->name() == "inputLeaf2");
|
||||
+ REQUIRE(actions[1].asActionRpc().output().child()->name() == "outputLeaf2");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("SchemaNode::operator==")
|
||||
{
|
||||
auto a = ctx->findPath("/type_module:leafString");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
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 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: Wires
|
||||
|
||||
Change-Id: I25182ea2d042be1e6e4246e18aee260cc032e547
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/Context.cpp | 6 ++++--
|
||||
tests/context.cpp | 21 +++++++++++++++++++++
|
||||
2 files changed, 25 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/Context.cpp b/src/Context.cpp
|
||||
index b844bd9..287f8c8 100644
|
||||
--- a/src/Context.cpp
|
||||
+++ b/src/Context.cpp
|
||||
@@ -221,7 +221,8 @@ std::optional<DataNode> Context::parseExtData(
|
||||
* - a NETCONF RPC,
|
||||
* - a NETCONF notification,
|
||||
* - a RESTCONF notification,
|
||||
- * - a YANG notification.
|
||||
+ * - a YANG notification,
|
||||
+ * - a YANG RPC.
|
||||
*
|
||||
* Parsing any of these requires just the schema (which is available through the Context), and the textual payload.
|
||||
* All the other information are encoded in the textual payload as per the standard.
|
||||
@@ -243,6 +244,7 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con
|
||||
auto in = wrap_ly_in_new_memory(input);
|
||||
|
||||
switch (opType) {
|
||||
+ case OperationType::RpcYang:
|
||||
case OperationType::RpcNetconf:
|
||||
case OperationType::NotificationNetconf:
|
||||
case OperationType::NotificationRestconf:
|
||||
@@ -254,7 +256,7 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con
|
||||
ParsedOp res;
|
||||
res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt;
|
||||
|
||||
- if (opType == OperationType::NotificationYang) {
|
||||
+ if ((opType == OperationType::NotificationYang) || (opType == OperationType::RpcYang)) {
|
||||
res.op = op && tree ? std::optional{DataNode(op, res.tree->m_refs)} : std::nullopt;
|
||||
} else {
|
||||
res.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt;
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 71ae873..11019eb 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -464,6 +464,27 @@ TEST_CASE("context")
|
||||
REQUIRE(data->findPath("/example-schema:leafInt8")->asTerm().valueStr() == "-43");
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("Context::parseOp")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("RPC")
|
||||
+ {
|
||||
+ ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
+ std::string dataJson = R"({"example-schema:myRpc":{"inputLeaf":"str"}})";
|
||||
+ auto pop = ctx->parseOp(dataJson, libyang::DataFormat::JSON, libyang::OperationType::RpcYang);
|
||||
+ REQUIRE(pop.op->schema().name() == "myRpc");
|
||||
+ REQUIRE(pop.tree->findPath("/example-schema:myRpc/inputLeaf")->asTerm().valueStr() == "str");
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("action")
|
||||
+ {
|
||||
+ ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
+ std::string datajson = R"({"example-schema:person":[{"name":"john", "poke":{}}]})";
|
||||
+ auto pop = ctx->parseOp(datajson, libyang::DataFormat::JSON, libyang::OperationType::RpcYang);
|
||||
+ REQUIRE(pop.op->schema().name() == "poke");
|
||||
+ REQUIRE(pop.tree->findPath("/example-schema:person[name='john']/poke")->schema().nodeType() == libyang::NodeType::Action);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+
|
||||
DOCTEST_SUBCASE("Context::parseExt")
|
||||
{
|
||||
ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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 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: Wires
|
||||
|
||||
Bug: https://github.com/CESNET/libyang-cpp/issues/20
|
||||
Change-Id: I005a2f1b057978573a4046e7b4cc31d77e36fde3
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/DataNode.cpp | 4 +++-
|
||||
tests/data_node.cpp | 7 +++++++
|
||||
2 files changed, 10 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 51c86c8..2ef17f2 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1170,7 +1170,9 @@ void validateAll(std::optional<libyang::DataNode>& node, const std::optional<Val
|
||||
}
|
||||
|
||||
// TODO: support the `diff` argument
|
||||
- lyd_validate_all(node ? &node->m_node : nullptr, nullptr, opts ? utils::toValidationOptions(*opts) : 0, nullptr);
|
||||
+ auto ret = lyd_validate_all(node ? &node->m_node : nullptr, nullptr, opts ? utils::toValidationOptions(*opts) : 0, nullptr);
|
||||
+ throwIfError(ret, "libyang:validateAll: lyd_validate_all failed");
|
||||
+
|
||||
if (!node->m_node) {
|
||||
node = std::nullopt;
|
||||
}
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index a23e4c2..8a2610e 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -489,6 +489,13 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE_THROWS_WITH_AS(libyang::validateAll(node, libyang::ValidationOptions::NoState), "validateAll: Node is not a unique reference", libyang::Error);
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("validateAll throws on validation failure")
|
||||
+ {
|
||||
+ ctx.parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ auto node = std::optional{ctx.newPath("/type_module:leafWithConfigFalse", "hi")};
|
||||
+ REQUIRE_THROWS_WITH_AS(libyang::validateAll(node, libyang::ValidationOptions::NoState), "libyang:validateAll: lyd_validate_all failed: LY_EVALID", libyang::ErrorWithCode);
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("unlink")
|
||||
{
|
||||
auto root = ctx.parseData(data2, libyang::DataFormat::JSON);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
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 03/18] remove a misleading comment
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
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
|
||||
nodes directly. However, looking at all the other classes this is a bit
|
||||
redundant, we're using a pattern like this all the time. Just drop the
|
||||
comment.
|
||||
|
||||
Change-Id: Ibd9bf9f1e83c650dda3bc43ef48e61dd6d95da5a
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/SchemaNode.cpp | 3 ---
|
||||
1 file changed, 3 deletions(-)
|
||||
|
||||
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||
index 81e938f..9934cea 100644
|
||||
--- a/src/SchemaNode.cpp
|
||||
+++ b/src/SchemaNode.cpp
|
||||
@@ -640,9 +640,6 @@ bool List::isUserOrdered() const
|
||||
*/
|
||||
ActionRpcInput ActionRpc::input() const
|
||||
{
|
||||
- // I need a lysc_node* for ActionRpcInput, but m_node->input is a lysp_node_action_inout. lysp_node_action_inout is
|
||||
- // still just a lysc_node, so I'll just convert to lysc_node.
|
||||
- // This is not very pretty, but I don't want to introduce another member for ActionRpcInput and ActionRpcOutput.
|
||||
return ActionRpcInput{reinterpret_cast<const lysc_node*>(&reinterpret_cast<const lysc_node_action*>(m_node)->input), m_ctx};
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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 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: Wires
|
||||
|
||||
`List::keys()` used `std::move` while iterating over array of leafs.
|
||||
This was solved without using `std::move`.
|
||||
|
||||
Change-Id: I8cbf8780ecd8848e46c1de5d4123a08624536bba
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/SchemaNode.cpp | 3 +--
|
||||
1 file changed, 1 insertion(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||
index 9934cea..20e2aff 100644
|
||||
--- a/src/SchemaNode.cpp
|
||||
+++ b/src/SchemaNode.cpp
|
||||
@@ -593,8 +593,7 @@ std::vector<Leaf> List::keys() const
|
||||
LY_LIST_FOR(list->child, elem)
|
||||
{
|
||||
if (lysc_is_key(elem)) {
|
||||
- Leaf leaf(elem, m_ctx);
|
||||
- res.emplace_back(std::move(leaf));
|
||||
+ res.emplace_back(Leaf(elem, m_ctx));
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
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 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: Wires
|
||||
|
||||
Make leaf-list's `default` statement available so that it can be
|
||||
accessed if end-user requires reading schema nodes.
|
||||
|
||||
`LeafList::defaultValuesStr()` returns array of canonized string default
|
||||
values.
|
||||
|
||||
Change-Id: Idc42cd877f1fd3d717d491d09c46b59492527bff
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/SchemaNode.hpp | 1 +
|
||||
src/SchemaNode.cpp | 17 +++++++++++++++++
|
||||
tests/context.cpp | 1 +
|
||||
tests/example_schema.hpp | 8 ++++++++
|
||||
tests/schema_node.cpp | 7 +++++++
|
||||
5 files changed, 34 insertions(+)
|
||||
|
||||
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
|
||||
index 83afc06..8ddf9be 100644
|
||||
--- a/include/libyang-cpp/SchemaNode.hpp
|
||||
+++ b/include/libyang-cpp/SchemaNode.hpp
|
||||
@@ -169,6 +169,7 @@ class LIBYANG_CPP_EXPORT LeafList : public SchemaNode {
|
||||
public:
|
||||
bool isMandatory() const;
|
||||
types::Type valueType() const;
|
||||
+ std::vector<std::string> defaultValuesStr() const;
|
||||
libyang::types::constraints::ListSize maxElements() const;
|
||||
libyang::types::constraints::ListSize minElements() const;
|
||||
std::optional<std::string> units() const;
|
||||
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||
index 9934cea..95bc09b 100644
|
||||
--- a/src/SchemaNode.cpp
|
||||
+++ b/src/SchemaNode.cpp
|
||||
@@ -472,6 +472,23 @@ types::Type LeafList::valueType() const
|
||||
return types::Type{reinterpret_cast<const lysc_node_leaflist*>(m_node)->type, typeParsed, m_ctx};
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Retrieves the default string values for this leaf-list.
|
||||
+ *
|
||||
+ * @return The default values, or an empty vector if the leaf-list does not have default values.
|
||||
+ *
|
||||
+ * Wraps `lysc_node_leaflist::dflts`.
|
||||
+ */
|
||||
+std::vector<std::string> LeafList::defaultValuesStr() const
|
||||
+{
|
||||
+ auto dflts = reinterpret_cast<const lysc_node_leaflist*>(m_node)->dflts;
|
||||
+ std::vector<std::string> res;
|
||||
+ for (const auto& it : std::span(dflts, LY_ARRAY_COUNT(dflts))) {
|
||||
+ res.emplace_back(lyd_value_get_canonical(m_ctx.get(), it));
|
||||
+ }
|
||||
+ return res;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Retrieves the units for this leaf.
|
||||
* @return The units, or std::nullopt if no units are available.
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 11019eb..902faf6 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -733,6 +733,7 @@ TEST_CASE("context")
|
||||
+--rw iid-valid? instance-identifier
|
||||
+--rw iid-relaxed? instance-identifier
|
||||
+--rw leafListBasic* string
|
||||
+ +--rw leafListWithDefault* int32
|
||||
+--rw leafListWithMinMaxElements* int32
|
||||
+--rw leafListWithUnits* int32
|
||||
+--rw listBasic* [primary-key]
|
||||
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||
index c093f50..2861b1a 100644
|
||||
--- a/tests/example_schema.hpp
|
||||
+++ b/tests/example_schema.hpp
|
||||
@@ -525,6 +525,14 @@ module type_module {
|
||||
ordered-by user;
|
||||
}
|
||||
|
||||
+ leaf-list leafListWithDefault {
|
||||
+ type int32;
|
||||
+ default -1;
|
||||
+ default +512;
|
||||
+ default 0x400;
|
||||
+ default 04000;
|
||||
+ }
|
||||
+
|
||||
leaf-list leafListWithMinMaxElements {
|
||||
type int32;
|
||||
min-elements 1;
|
||||
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||
index a86900d..80c7407 100644
|
||||
--- a/tests/schema_node.cpp
|
||||
+++ b/tests/schema_node.cpp
|
||||
@@ -200,6 +200,7 @@ TEST_CASE("SchemaNode")
|
||||
"/type_module:iid-valid",
|
||||
"/type_module:iid-relaxed",
|
||||
"/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
"/type_module:leafListWithMinMaxElements",
|
||||
"/type_module:leafListWithUnits",
|
||||
"/type_module:listBasic",
|
||||
@@ -606,6 +607,12 @@ TEST_CASE("SchemaNode")
|
||||
REQUIRE(!ctx->findPath("/type_module:leafListBasic").asLeafList().isMandatory());
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("LeafList::defaultValuesStr")
|
||||
+ {
|
||||
+ REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector<std::string>{"-1", "512", "1024", "2048"});
|
||||
+ REQUIRE(ctx->findPath("/type_module:leafListBasic").asLeafList().defaultValuesStr().size() == 0);
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("LeafList::maxElements")
|
||||
{
|
||||
REQUIRE(ctx->findPath("/type_module:leafListWithMinMaxElements").asLeafList().maxElements() == 5);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
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 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: Wires
|
||||
|
||||
Make choice and case statements available so that they can be accessed
|
||||
if end-user requires reading schema nodes.
|
||||
|
||||
By design, choice and case statements do not exist in data tree directly.
|
||||
Only children of one case can be present in the data tree at one time.
|
||||
That means that choice and case children are not instantiable, thus
|
||||
`SchemaNode::immediateChildren` must be used (instead of
|
||||
`SchemaNode::childInstantibles`) if end-user wants to access choice
|
||||
and case substatements.
|
||||
|
||||
Change-Id: Ib089672ad21dda8a0344895835d92d3432fcccb8
|
||||
Co-authored-by: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/SchemaNode.hpp | 34 +++++++++++++
|
||||
src/SchemaNode.cpp | 68 ++++++++++++++++++++++++++
|
||||
tests/context.cpp | 77 +++++++++++++++++++-----------
|
||||
tests/example_schema.hpp | 60 +++++++++++++++++++++++
|
||||
tests/schema_node.cpp | 72 ++++++++++++++++++++++++++++
|
||||
5 files changed, 284 insertions(+), 27 deletions(-)
|
||||
|
||||
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
|
||||
index 8ddf9be..0f1a4c4 100644
|
||||
--- a/include/libyang-cpp/SchemaNode.hpp
|
||||
+++ b/include/libyang-cpp/SchemaNode.hpp
|
||||
@@ -22,6 +22,8 @@ class AnyDataAnyXML;
|
||||
class ActionRpc;
|
||||
class ActionRpcInput;
|
||||
class ActionRpcOutput;
|
||||
+class Case;
|
||||
+class Choice;
|
||||
class Container;
|
||||
class Leaf;
|
||||
class LeafList;
|
||||
@@ -62,6 +64,8 @@ public:
|
||||
// drectly by the user.
|
||||
// TODO: turn these into a templated `as<>` method.
|
||||
AnyDataAnyXML asAnyDataAnyXML() const;
|
||||
+ Case asCase() const;
|
||||
+ Choice asChoice() const;
|
||||
Container asContainer() const;
|
||||
Leaf asLeaf() const;
|
||||
LeafList asLeafList() const;
|
||||
@@ -129,6 +133,36 @@ private:
|
||||
using SchemaNode::SchemaNode;
|
||||
};
|
||||
|
||||
+/**
|
||||
+ * @brief Class representing a schema definition of a `case` node.
|
||||
+ *
|
||||
+ * Wraps `lysc_node_case`.
|
||||
+ */
|
||||
+class LIBYANG_CPP_EXPORT Case : public SchemaNode {
|
||||
+public:
|
||||
+ friend SchemaNode;
|
||||
+ friend Choice;
|
||||
+
|
||||
+private:
|
||||
+ using SchemaNode::SchemaNode;
|
||||
+};
|
||||
+
|
||||
+/**
|
||||
+ * @brief Class representing a schema definition of a `choice` node.
|
||||
+ *
|
||||
+ * Wraps `lysc_node_choice`.
|
||||
+ */
|
||||
+class LIBYANG_CPP_EXPORT Choice : public SchemaNode {
|
||||
+public:
|
||||
+ bool isMandatory() const;
|
||||
+ std::vector<Case> cases() const;
|
||||
+ std::optional<Case> defaultCase() const;
|
||||
+ friend SchemaNode;
|
||||
+
|
||||
+private:
|
||||
+ using SchemaNode::SchemaNode;
|
||||
+};
|
||||
+
|
||||
/**
|
||||
* @brief Class representing a schema definition of a `container` node.
|
||||
*/
|
||||
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
|
||||
index bd20402..26b5099 100644
|
||||
--- a/src/SchemaNode.cpp
|
||||
+++ b/src/SchemaNode.cpp
|
||||
@@ -191,6 +191,32 @@ NodeType SchemaNode::nodeType() const
|
||||
return utils::toNodeType(m_node->nodetype);
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Try to cast this SchemaNode to a Case node.
|
||||
+ * @throws Error If this node is not a case.
|
||||
+ */
|
||||
+Case SchemaNode::asCase() const
|
||||
+{
|
||||
+ if (nodeType() != NodeType::Case) {
|
||||
+ throw Error("Schema node is not a case: " + path());
|
||||
+ }
|
||||
+
|
||||
+ return Case{m_node, m_ctx};
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Try to cast this SchemaNode to a Choice node.
|
||||
+ * @throws Error If this node is not a choice.
|
||||
+ */
|
||||
+Choice SchemaNode::asChoice() const
|
||||
+{
|
||||
+ if (nodeType() != NodeType::Choice) {
|
||||
+ throw Error("Schema node is not a choice: " + path());
|
||||
+ }
|
||||
+
|
||||
+ return Choice{m_node, m_ctx};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Try to cast this SchemaNode to a Container node.
|
||||
* @throws Error If this node is not a container.
|
||||
@@ -401,6 +427,48 @@ bool AnyDataAnyXML::isMandatory() const
|
||||
return m_node->flags & LYS_MAND_TRUE;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Checks whether this choice is mandatory.
|
||||
+ *
|
||||
+ * Wraps flag `LYS_MAND_TRUE`.
|
||||
+ */
|
||||
+bool Choice::isMandatory() const
|
||||
+{
|
||||
+ return m_node->flags & LYS_MAND_TRUE;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Retrieves the list of cases for this choice.
|
||||
+ *
|
||||
+ * Wraps `lysc_node_choice::cases`.
|
||||
+ */
|
||||
+std::vector<Case> Choice::cases() const
|
||||
+{
|
||||
+ auto choice = reinterpret_cast<const lysc_node_choice*>(m_node);
|
||||
+ auto cases = reinterpret_cast<lysc_node*>(choice->cases);
|
||||
+ std::vector<Case> res;
|
||||
+ lysc_node* elem;
|
||||
+ LY_LIST_FOR(cases, elem)
|
||||
+ {
|
||||
+ res.emplace_back(Case(elem, m_ctx));
|
||||
+ }
|
||||
+ return res;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Retrieves the default case for this choice.
|
||||
+ *
|
||||
+ * Wraps `lysc_node_choice::dflt`.
|
||||
+ */
|
||||
+std::optional<Case> Choice::defaultCase() const
|
||||
+{
|
||||
+ auto choice = reinterpret_cast<const lysc_node_choice*>(m_node);
|
||||
+ if (!choice->dflt) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+ return Case{reinterpret_cast<lysc_node*>(choice->dflt), m_ctx};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Checks whether this container is mandatory.
|
||||
*
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 902faf6..5929b75 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -709,33 +709,56 @@ TEST_CASE("context")
|
||||
auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
|
||||
REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module
|
||||
- +--rw anydataBasic? anydata
|
||||
- +--rw anydataWithMandatoryChild anydata
|
||||
- +--rw anyxmlBasic? anyxml
|
||||
- +--rw anyxmlWithMandatoryChild anyxml
|
||||
- +--rw leafBinary? binary
|
||||
- +--rw leafBits? bits
|
||||
- +--rw leafEnum? enumeration
|
||||
- +--rw leafEnum2? enumeration
|
||||
- +--rw leafNumber? int32
|
||||
- +--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol
|
||||
- +--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol
|
||||
- +--rw leafString? string
|
||||
- +--rw leafUnion? union
|
||||
- +--rw meal? identityref
|
||||
- +--ro leafWithConfigFalse? string
|
||||
- +--rw leafWithDefaultValue? string
|
||||
- +--rw leafWithDescription? string
|
||||
- +--rw leafWithMandatoryTrue string
|
||||
- x--rw leafWithStatusDeprecated? string
|
||||
- o--rw leafWithStatusObsolete? string
|
||||
- +--rw leafWithUnits? int32
|
||||
- +--rw iid-valid? instance-identifier
|
||||
- +--rw iid-relaxed? instance-identifier
|
||||
- +--rw leafListBasic* string
|
||||
- +--rw leafListWithDefault* int32
|
||||
- +--rw leafListWithMinMaxElements* int32
|
||||
- +--rw leafListWithUnits* int32
|
||||
+ +--rw anydataBasic? anydata
|
||||
+ +--rw anydataWithMandatoryChild anydata
|
||||
+ +--rw anyxmlBasic? anyxml
|
||||
+ +--rw anyxmlWithMandatoryChild anyxml
|
||||
+ +--rw choiceBasicContainer
|
||||
+ | +--rw (choiceBasic)?
|
||||
+ | +--:(case1)
|
||||
+ | | +--rw l? string
|
||||
+ | | +--rw ll* string
|
||||
+ | +--:(case2)
|
||||
+ | +--rw l2? string
|
||||
+ +--rw choiceWithMandatoryContainer
|
||||
+ | +--rw (choiceWithMandatory)
|
||||
+ | +--:(case3)
|
||||
+ | | +--rw l3? string
|
||||
+ | +--:(case4)
|
||||
+ | +--rw l4? string
|
||||
+ +--rw choiceWithDefaultContainer
|
||||
+ | +--rw (choiceWithDefault)?
|
||||
+ | +--:(case5)
|
||||
+ | | +--rw l5? string
|
||||
+ | +--:(case6)
|
||||
+ | +--rw l6? string
|
||||
+ +--rw implicitCaseContainer
|
||||
+ | +--rw (implicitCase)?
|
||||
+ | +--:(implicitLeaf)
|
||||
+ | +--rw implicitLeaf? string
|
||||
+ +--rw leafBinary? binary
|
||||
+ +--rw leafBits? bits
|
||||
+ +--rw leafEnum? enumeration
|
||||
+ +--rw leafEnum2? enumeration
|
||||
+ +--rw leafNumber? int32
|
||||
+ +--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol
|
||||
+ +--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol
|
||||
+ +--rw leafString? string
|
||||
+ +--rw leafUnion? union
|
||||
+ +--rw meal? identityref
|
||||
+ +--ro leafWithConfigFalse? string
|
||||
+ +--rw leafWithDefaultValue? string
|
||||
+ +--rw leafWithDescription? string
|
||||
+ +--rw leafWithMandatoryTrue string
|
||||
+ x--rw leafWithStatusDeprecated? string
|
||||
+ o--rw leafWithStatusObsolete? string
|
||||
+ +--rw leafWithUnits? int32
|
||||
+ +--rw iid-valid? instance-identifier
|
||||
+ +--rw iid-relaxed? instance-identifier
|
||||
+ +--rw leafListBasic* string
|
||||
+ +--rw leafListWithDefault* int32
|
||||
+ +--rw leafListWithMinMaxElements* int32
|
||||
+ +--rw leafListWithUnits* int32
|
||||
+--rw listBasic* [primary-key]
|
||||
| +--rw primary-key string
|
||||
+--rw listAdvancedWithOneKey* [lol]
|
||||
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||
index 2861b1a..ae3b4de 100644
|
||||
--- a/tests/example_schema.hpp
|
||||
+++ b/tests/example_schema.hpp
|
||||
@@ -390,6 +390,66 @@ module type_module {
|
||||
mandatory true;
|
||||
}
|
||||
|
||||
+ container choiceBasicContainer {
|
||||
+ choice choiceBasic {
|
||||
+ case case1 {
|
||||
+ leaf l {
|
||||
+ type string;
|
||||
+ }
|
||||
+ leaf-list ll {
|
||||
+ type string;
|
||||
+ ordered-by user;
|
||||
+ }
|
||||
+ }
|
||||
+ case case2 {
|
||||
+ leaf l2 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ container choiceWithMandatoryContainer {
|
||||
+ choice choiceWithMandatory {
|
||||
+ mandatory true;
|
||||
+ case case3 {
|
||||
+ leaf l3 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ case case4 {
|
||||
+ leaf l4 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ container choiceWithDefaultContainer {
|
||||
+ choice choiceWithDefault {
|
||||
+ default case5;
|
||||
+ case case5 {
|
||||
+ leaf l5 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ case case6 {
|
||||
+ leaf l6 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ container implicitCaseContainer {
|
||||
+ choice implicitCase {
|
||||
+ leaf implicitLeaf {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+
|
||||
leaf leafBinary {
|
||||
type binary;
|
||||
}
|
||||
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||
index 80c7407..8d74bd2 100644
|
||||
--- a/tests/schema_node.cpp
|
||||
+++ b/tests/schema_node.cpp
|
||||
@@ -58,6 +58,9 @@ TEST_CASE("SchemaNode")
|
||||
],
|
||||
"type_module:anydataWithMandatoryChild": {"content": "test-string"},
|
||||
"type_module:anyxmlWithMandatoryChild": {"content": "test-string"},
|
||||
+ "type_module:choiceWithMandatoryContainer": {
|
||||
+ "l4": "test-string"
|
||||
+ },
|
||||
"type_module:containerWithMandatoryChild": {
|
||||
"leafWithMandatoryTrue": "test-string"
|
||||
},
|
||||
@@ -180,6 +183,10 @@ TEST_CASE("SchemaNode")
|
||||
"/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",
|
||||
@@ -417,6 +424,71 @@ TEST_CASE("SchemaNode")
|
||||
REQUIRE(!ctx->findPath("/type_module:anyxmlBasic").asAnyDataAnyXML().isMandatory());
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("Choice and Case")
|
||||
+ {
|
||||
+ std::string xpath;
|
||||
+ bool isMandatory = false;
|
||||
+ std::optional<std::string> defaultCase;
|
||||
+ std::vector<std::string> caseNames;
|
||||
+ std::optional<libyang::SchemaNode> root;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("two cases with nothing fancy")
|
||||
+ {
|
||||
+ root = ctx->findPath("/type_module:choiceBasicContainer");
|
||||
+ caseNames = {"case1", "case2"};
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("mandatory choice") {
|
||||
+ root = ctx->findPath("/type_module:choiceWithMandatoryContainer");
|
||||
+ isMandatory = true;
|
||||
+ caseNames = {"case3", "case4"};
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("default choice") {
|
||||
+ root = ctx->findPath("/type_module:choiceWithDefaultContainer");
|
||||
+ defaultCase = "case5";
|
||||
+ caseNames = {"case5", "case6"};
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("implicit case") {
|
||||
+ root = ctx->findPath("/type_module:implicitCaseContainer");
|
||||
+ caseNames = {"implicitLeaf"};
|
||||
+ }
|
||||
+
|
||||
+ // For testing purposes, we have each choice in its own container. As choice and case are not directly instantiable,
|
||||
+ // we wrap them in a container to simplify the testing process. It allows us to simply address the choice by its
|
||||
+ // container and then get the choice from it. It also prevents polluting the test schema with unnecessary nodes
|
||||
+ // and isolates the choice from other nodes.
|
||||
+ auto container = root->asContainer();
|
||||
+ auto choice = container.immediateChildren().begin()->asChoice();
|
||||
+ REQUIRE(choice.isMandatory() == isMandatory);
|
||||
+ REQUIRE(!!choice.defaultCase() == !!defaultCase);
|
||||
+ if (defaultCase) {
|
||||
+ REQUIRE(choice.defaultCase()->name() == *defaultCase);
|
||||
+ }
|
||||
+ std::vector<std::string> actualCaseNames;
|
||||
+ for (const auto& case_ : choice.cases()) {
|
||||
+ actualCaseNames.push_back(case_.name());
|
||||
+ }
|
||||
+ REQUIRE(actualCaseNames == caseNames);
|
||||
+
|
||||
+ // Also test child node access for one arbitrary choice/case combination
|
||||
+ if (root->path() == "/type_module:choiceBasicContainer") {
|
||||
+ REQUIRE(choice.cases().size() == 2);
|
||||
+ auto case1 = choice.cases()[0];
|
||||
+ auto children = case1.immediateChildren();
|
||||
+ auto it = children.begin();
|
||||
+ REQUIRE(it->asLeaf().name() == "l");
|
||||
+ ++it;
|
||||
+ REQUIRE(it->asLeafList().name() == "ll");
|
||||
+
|
||||
+ auto case2 = choice.cases()[1];
|
||||
+ children = case2.immediateChildren();
|
||||
+ it = children.begin();
|
||||
+ REQUIRE(it->asLeaf().name() == "l2");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Container::isMandatory")
|
||||
{
|
||||
REQUIRE(ctx->findPath("/type_module:containerWithMandatoryChild").asContainer().isMandatory());
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
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 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: Wires
|
||||
|
||||
Previously, the code would require a newPath(...,
|
||||
libyang::CreationOptions::Update), which is quite a mouthful.
|
||||
|
||||
Change-Id: I8a908c0fdd3e48dda830819758522a511adedd3b
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/DataNode.hpp | 8 ++++++
|
||||
src/DataNode.cpp | 21 ++++++++++++++++
|
||||
tests/data_node.cpp | 42 ++++++++++++++++++++++++++------
|
||||
3 files changed, 63 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 2211415..851681b 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -212,6 +212,14 @@ public:
|
||||
Value value() const;
|
||||
types::Type valueType() const;
|
||||
|
||||
+ /** @brief Was the value changed? */
|
||||
+ enum class ValueChange {
|
||||
+ Changed, /**< Yes, this is an actual change of the stored value */
|
||||
+ ExplicitNonDefault, /**< It still holds the default value, but it's been set explicitly now */
|
||||
+ EqualValueNotChanged, /**< No change, the previous value is the same as the new one, and it isn't an implicit default */
|
||||
+ };
|
||||
+ ValueChange changeValue(const std::string value);
|
||||
+
|
||||
private:
|
||||
using DataNode::DataNode;
|
||||
};
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 2ef17f2..84591e5 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -903,6 +903,27 @@ types::Type DataNodeTerm::valueType() const
|
||||
return impl(reinterpret_cast<const lyd_node_term*>(m_node)->value);
|
||||
}
|
||||
|
||||
+/** @short Change the term's value
|
||||
+ *
|
||||
+ * Wraps `lyd_change_term`.
|
||||
+ * */
|
||||
+DataNodeTerm::ValueChange DataNodeTerm::changeValue(const std::string value)
|
||||
+{
|
||||
+ auto ret = lyd_change_term(m_node, value.c_str());
|
||||
+
|
||||
+ switch (ret) {
|
||||
+ case LY_SUCCESS:
|
||||
+ return ValueChange::Changed;
|
||||
+ case LY_EEXIST:
|
||||
+ return ValueChange::ExplicitNonDefault;
|
||||
+ case LY_ENOT:
|
||||
+ return ValueChange::EqualValueNotChanged;
|
||||
+ default:
|
||||
+ throwIfError(ret, "DataNodeTerm::changeValue failed");
|
||||
+ __builtin_unreachable();
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection for iterating depth-first over the subtree this instance points to.
|
||||
*
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 8a2610e..45fd6c1 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -456,15 +456,41 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(node.hasDefaultValue());
|
||||
REQUIRE(node.isImplicitDefault());
|
||||
|
||||
- data->newPath("/example-schema3:leafWithDefault", "not-default-value", libyang::CreationOptions::Update);
|
||||
- node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
|
||||
- REQUIRE(!node.hasDefaultValue());
|
||||
- REQUIRE(!node.isImplicitDefault());
|
||||
+ DOCTEST_SUBCASE("newPath")
|
||||
+ {
|
||||
+ data->newPath("/example-schema3:leafWithDefault", "not-default-value", libyang::CreationOptions::Update);
|
||||
+ node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
|
||||
+ REQUIRE(!node.hasDefaultValue());
|
||||
+ REQUIRE(!node.isImplicitDefault());
|
||||
|
||||
- data->newPath("/example-schema3:leafWithDefault", "AHOJ", libyang::CreationOptions::Update);
|
||||
- node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
|
||||
- REQUIRE(node.hasDefaultValue());
|
||||
- REQUIRE(!node.isImplicitDefault());
|
||||
+ data->newPath("/example-schema3:leafWithDefault", "AHOJ", libyang::CreationOptions::Update);
|
||||
+ node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
|
||||
+ REQUIRE(node.hasDefaultValue());
|
||||
+ REQUIRE(!node.isImplicitDefault());
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("changing values")
|
||||
+ {
|
||||
+ auto node = data->findPath("/example-schema3:leafWithDefault");
|
||||
+ REQUIRE(!!node);
|
||||
+ auto term = node->asTerm();
|
||||
+
|
||||
+ DOCTEST_SUBCASE("to an arbitrary value") {
|
||||
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("from an implicit default to an explicit default") {
|
||||
+ REQUIRE(term.changeValue("AHOJ") == libyang::DataNodeTerm::ValueChange::ExplicitNonDefault);
|
||||
+ REQUIRE(term.changeValue("AHOJ") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged);
|
||||
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed);
|
||||
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("from an implicit default to something else") {
|
||||
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed);
|
||||
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("isTerm")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
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 09/18] upstream stopped reporting schema-mounts node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
tests/context.cpp | 3 +--
|
||||
2 files changed, 2 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 3d86809..732f52b 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.4.2 IMPORTED_TARGET)
|
||||
+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
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 5929b75..9d38fea 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -509,8 +509,7 @@ TEST_CASE("context")
|
||||
"error-message": "hi"
|
||||
}
|
||||
]
|
||||
- },
|
||||
- "ietf-yang-schema-mount:schema-mounts": {}
|
||||
+ }
|
||||
}
|
||||
)");
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -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,3 +1,3 @@
|
||||
# Locally calculated
|
||||
sha256 82e3758011ec44c78e98d0777799d6e12aec5b8a64b32ebb20d0fe50e32488bb LICENSE
|
||||
sha256 70fd0df940026fb930d5407abe679e064caf4701bff35d79465b9ad2c0915808 libyang-cpp-v4.tar.gz
|
||||
sha256 43c84317ba13470f421a90bca74c062cf63a70e488d2e90c432b662c4638a14e libyang-cpp-v3.tar.gz
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# CPP bindings for libyang
|
||||
#
|
||||
################################################################################
|
||||
LIBYANG_CPP_VERSION = v4
|
||||
LIBYANG_CPP_VERSION = v3
|
||||
LIBYANG_CPP_SITE = $(call github,CESNET,libyang-cpp,$(LIBYANG_CPP_VERSION))
|
||||
LIBYANG_CPP_LICENSE = BSD-3-Clause
|
||||
LIBYANG_CPP_LICENSE_FILES = LICENSE
|
||||
|
||||
@@ -5,8 +5,3 @@ config BR2_PACKAGE_NGHTTP2_ASIO
|
||||
select BR2_PACKAGE_BOOST
|
||||
help
|
||||
C++ bindings for nghttp2
|
||||
|
||||
This is the CESNET fork of nghttp2-asio, introduced here:
|
||||
https://github.com/CESNET/rousette/commit/fff3429
|
||||
|
||||
https://github.com/CESNET/nghttp2-asio
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Locally calculated
|
||||
sha256 6b94f3abc1aabd0c72a7c7d92a77f79dda7c8a0cb3df839a97890b4116a2de2a COPYING
|
||||
sha256 883af672c1d210398170749a8ccda30103b2894bcd4eccd80f36771aad0b15c3 nghttp2-asio-2173b82e6caa85950c769eecc5da6809faadf61a-git4.tar.gz
|
||||
sha256 d971c538a31eae5714d2434d90f86794f267615e85e8d2bb0c383e83c300e1ce nghttp2-asio-e877868abe06a83ed0a6ac6e245c07f6f20866b5-git4.tar.gz
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
################################################################################
|
||||
|
||||
NGHTTP2_ASIO_VERSION = 2173b82e6caa85950c769eecc5da6809faadf61a
|
||||
NGHTTP2_ASIO_SITE = https://github.com/CESNET/nghttp2-asio
|
||||
NGHTTP2_ASIO_VERSION = e877868abe06a83ed0a6ac6e245c07f6f20866b5
|
||||
NGHTTP2_ASIO_SITE = https://github.com/kernelkit/nghttp2-asio.git
|
||||
NGHTTP2_ASIO_SITE_METHOD = git
|
||||
NGHTTP2_ASIO_LICENSE = MIT
|
||||
NGHTTP2_ASIO_LICENSE_FILES = COPYING
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
From c0d13b8e426608a30b1eea382f908fb6447fd01d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 12 Jun 2025 10:33:42 +0100
|
||||
Subject: [PATCH 01/13] Log HTTP headers and the input data payload
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...so that we can debug the results of a nasty lockup that Michal
|
||||
Hažlinský just found.
|
||||
|
||||
Change-Id: I2a930a02c7d30c051390fe73e6af9849edd580b4
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 10 ++++++++--
|
||||
1 file changed, 8 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 272ecc1..9821495 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -44,6 +44,9 @@ void logRequest(const auto& request)
|
||||
{
|
||||
const auto& peer = http::peer_from_request(request);
|
||||
spdlog::info("{}: {} {}", peer, request.method(), request.uri().raw_path);
|
||||
+ for (const auto& hdr: request.header()) {
|
||||
+ spdlog::trace("{}: header: {}: {}", peer, hdr.first, hdr.second.sensitive ? "<sensitive>"s : hdr.second.value);
|
||||
+ }
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -1079,12 +1082,14 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
|
||||
auto requestCtx = std::make_shared<RequestContext>(req, res, dataFormat, sess, restconfRequest);
|
||||
|
||||
- req.on_data([requestCtx, restconfRequest /* intentional copy */, timeout](const uint8_t* data, std::size_t length) {
|
||||
+ req.on_data([requestCtx, restconfRequest /* intentional copy */, timeout, peer=http::peer_from_request(req)](const uint8_t* data, std::size_t length) {
|
||||
if (length > 0) { // there are still some data to be read
|
||||
requestCtx->payload.append(reinterpret_cast<const char*>(data), length);
|
||||
return;
|
||||
}
|
||||
|
||||
+ spdlog::trace("{}: HTTP payload: {}", peer, requestCtx->payload);
|
||||
+
|
||||
if (restconfRequest.type == RestconfRequest::Type::CreateChildren) {
|
||||
WITH_RESTCONF_EXCEPTIONS(processPost, rejectWithError)(requestCtx, timeout);
|
||||
} else if (restconfRequest.type == RestconfRequest::Type::MergeData && isYangPatch(requestCtx->req)) {
|
||||
@@ -1143,10 +1148,11 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
case RestconfRequest::Type::ExecuteInternal: {
|
||||
auto requestCtx = std::make_shared<RequestContext>(req, res, dataFormat, sess, restconfRequest);
|
||||
|
||||
- req.on_data([requestCtx, timeout](const uint8_t* data, std::size_t length) {
|
||||
+ req.on_data([requestCtx, timeout, peer=http::peer_from_request(req)](const uint8_t* data, std::size_t length) {
|
||||
if (length > 0) {
|
||||
requestCtx->payload.append(reinterpret_cast<const char*>(data), length);
|
||||
} else {
|
||||
+ spdlog::trace("{}: HTTP payload: {}", peer, requestCtx->payload);
|
||||
WITH_RESTCONF_EXCEPTIONS(processActionOrRPC, rejectWithError)(requestCtx, timeout);
|
||||
}
|
||||
});
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
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/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: 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.
|
||||
Those values must be the same. In case they are not, it is an error.
|
||||
We reported that the values mismatch in case this happens, but the error
|
||||
message did not report what the values were.
|
||||
Knowing that might be beneficial when one is about to insert key values
|
||||
that can be namespace qualified (like identityrefs) and that are
|
||||
sometimes manipulated by libyang (e.g., when the identity belongs to the
|
||||
same namespace as the list node, it is not necessary for it to be
|
||||
namespace qualified by the client, but libyang adds the namespace
|
||||
internally).
|
||||
|
||||
Change-Id: Ie0d42511bde01ab4c39d61370b6601f8808e40c5
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 29 +++++++++++++++++++++--------
|
||||
tests/restconf-plain-patch.cpp | 2 +-
|
||||
tests/restconf-writing.cpp | 14 +++++++-------
|
||||
tests/restconf-yang-patch.cpp | 2 +-
|
||||
4 files changed, 30 insertions(+), 17 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 53d6625..5f560ed 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -154,9 +154,22 @@ auto rejectYangPatch(const std::string& patchId, const std::string& editId)
|
||||
};
|
||||
}
|
||||
|
||||
+struct KeyMismatch {
|
||||
+ libyang::DataNode offendingNode;
|
||||
+ std::optional<std::string> uriKeyValue;
|
||||
+
|
||||
+ std::string message() const {
|
||||
+ if (uriKeyValue) {
|
||||
+ return "List key mismatch between URI path ('"s + *uriKeyValue + "') and data ('" + offendingNode.asTerm().valueStr() + "').";
|
||||
+ } else {
|
||||
+ return "List key mismatch (key missing in the data).";
|
||||
+ }
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
/** @brief In case node is a (leaf-)list check if the key values are the same as the keys specified in the lastPathSegment.
|
||||
* @return The node where the mismatch occurs */
|
||||
-std::optional<libyang::DataNode> checkKeysMismatch(const libyang::DataNode& node, const PathSegment& lastPathSegment)
|
||||
+std::optional<KeyMismatch> checkKeysMismatch(const libyang::DataNode& node, const PathSegment& lastPathSegment)
|
||||
{
|
||||
if (node.schema().nodeType() == libyang::NodeType::List) {
|
||||
const auto& listKeys = node.schema().asList().keys();
|
||||
@@ -164,18 +177,18 @@ std::optional<libyang::DataNode> checkKeysMismatch(const libyang::DataNode& node
|
||||
const auto& keyValueURI = lastPathSegment.keys[i];
|
||||
auto keyNodeData = node.findPath(listKeys[i].module().name() + ':' + listKeys[i].name());
|
||||
if (!keyNodeData) {
|
||||
- return node;
|
||||
+ return KeyMismatch{node, std::nullopt};
|
||||
}
|
||||
|
||||
const auto& keyValueData = keyNodeData->asTerm().valueStr();
|
||||
|
||||
if (keyValueURI != keyValueData) {
|
||||
- return keyNodeData;
|
||||
+ return KeyMismatch{*keyNodeData, keyValueURI};
|
||||
}
|
||||
}
|
||||
} else if (node.schema().nodeType() == libyang::NodeType::Leaflist) {
|
||||
if (lastPathSegment.keys[0] != node.asTerm().valueStr()) {
|
||||
- return node;
|
||||
+ return KeyMismatch{node, lastPathSegment.keys[0]};
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
@@ -350,8 +363,8 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
if (isSameNode(child, lastPathSegment)) {
|
||||
// 1) a single child that is created by parseSubtree(), its name is the same as `lastPathSegment`.
|
||||
// It could be a list; then we need to check if the keys in provided data match the keys in URI.
|
||||
- if (auto offendingNode = checkKeysMismatch(child, lastPathSegment)) {
|
||||
- throw ErrorResponse(400, "protocol", "invalid-value", "List key mismatch between URI path and data.", offendingNode->path());
|
||||
+ if (auto keyMismatch = checkKeysMismatch(child, lastPathSegment)) {
|
||||
+ throw ErrorResponse(400, "protocol", "invalid-value", keyMismatch->message(), keyMismatch->offendingNode.path());
|
||||
}
|
||||
replacementNode = child;
|
||||
} else if (isKeyNode(*node, child)) {
|
||||
@@ -373,8 +386,8 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
if (!isSameNode(*replacementNode, lastPathSegment)) {
|
||||
throw ErrorResponse(400, "protocol", "invalid-value", "Data contains invalid node.", replacementNode->path());
|
||||
}
|
||||
- if (auto offendingNode = checkKeysMismatch(*parent, lastPathSegment)) {
|
||||
- throw ErrorResponse(400, "protocol", "invalid-value", "List key mismatch between URI path and data.", offendingNode->path());
|
||||
+ if (auto keyMismatch = checkKeysMismatch(*parent, lastPathSegment)) {
|
||||
+ throw ErrorResponse(400, "protocol", "invalid-value", keyMismatch->message(), keyMismatch->offendingNode.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/tests/restconf-plain-patch.cpp b/tests/restconf-plain-patch.cpp
|
||||
index 10d653a..d4f3952 100644
|
||||
--- a/tests/restconf-plain-patch.cpp
|
||||
+++ b/tests/restconf-plain-patch.cpp
|
||||
@@ -72,7 +72,7 @@ TEST_CASE("Plain patch")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='blabla']/name",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('libyang') and data ('blabla')."
|
||||
}
|
||||
]
|
||||
}
|
||||
diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index d46952f..96dbb25 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -432,7 +432,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='ahoj']/name",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('netconf') and data ('ahoj')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -447,7 +447,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:top-level-list[name='ahoj']/name",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('netconf') and data ('ahoj')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -505,7 +505,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='netconf']/collection[.='666']",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('667') and data ('666')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -520,7 +520,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:top-level-leaf-list[.='666']",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('667') and data ('666')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -535,7 +535,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='sysrepo']/name",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('netconf') and data ('sysrepo')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -550,7 +550,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='sysrepo']/name",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('netconf') and data ('sysrepo')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -565,7 +565,7 @@ TEST_CASE("writing data")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='libyang']/collection[.='42']",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('5') and data ('42')."
|
||||
}
|
||||
]
|
||||
}
|
||||
diff --git a/tests/restconf-yang-patch.cpp b/tests/restconf-yang-patch.cpp
|
||||
index 9d70912..7cc8946 100644
|
||||
--- a/tests/restconf-yang-patch.cpp
|
||||
+++ b/tests/restconf-yang-patch.cpp
|
||||
@@ -436,7 +436,7 @@ TEST_CASE("YANG patch")
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
"error-path": "/example:tlc/list[name='asdasdauisbdhaijbsdad']/name",
|
||||
- "error-message": "List key mismatch between URI path and data."
|
||||
+ "error-message": "List key mismatch between URI path ('libyang') and data ('asdasdauisbdhaijbsdad')."
|
||||
}
|
||||
]
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
From 027a47ac3ff6c369caab7c007a5a171a68270829 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Fri, 13 Jun 2025 10:47:55 +0200
|
||||
Subject: [PATCH 02/13] restconf: prevent throwing exception in
|
||||
withRestconfExceptions wrapper
|
||||
Organization: Wires
|
||||
|
||||
In case the catch block of withRestconfExceptions is reached and an
|
||||
exception is thrown, rousette will start behaving in a strange way
|
||||
where no new connections are accepted and already connected clients are
|
||||
not receiving the content [1].
|
||||
|
||||
This patch prevents throwing the exception that would slip through our
|
||||
catch handlers.
|
||||
An uncaught exception is a problem though, so expect a followup patch to
|
||||
handle such situations.
|
||||
|
||||
[1] https://github.com/CESNET/rousette/issues/19
|
||||
|
||||
Bug: https://github.com/CESNET/rousette/issues/19
|
||||
Change-Id: Ifbd74b9bdc0ca66c4e5449a7673ef2f12ae9215e
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
tests/restconf-plain-patch.cpp | 24 ++++++++++++++++++++++++
|
||||
tests/restconf-reading.cpp | 3 +++
|
||||
tests/yang/example.yang | 19 +++++++++++++++++++
|
||||
4 files changed, 47 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 9821495..1c2123e 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -350,7 +350,7 @@ constexpr auto withRestconfExceptions(T func, U rejectWithError)
|
||||
} else if (e.code() == sysrepo::ErrorCode::ItemAlreadyExists) {
|
||||
rejectWithError(requestCtx->sess.getContext(), requestCtx->dataFormat.response, requestCtx->req, requestCtx->res, 409, "application", "resource-denied", "Resource already exists.", std::nullopt);
|
||||
} else if (e.code() == sysrepo::ErrorCode::ValidationFailed) {
|
||||
- bool isAction = requestCtx->sess.getContext().findPath(requestCtx->restconfRequest.path).nodeType() == libyang::NodeType::Action;
|
||||
+ bool isAction = requestCtx->restconfRequest.path != "/" && requestCtx->sess.getContext().findPath(requestCtx->restconfRequest.path).nodeType() == libyang::NodeType::Action;
|
||||
/*
|
||||
* FIXME: This happens on invalid input data (e.g., missing mandatory nodes) or missing action data node.
|
||||
* The former (invalid input data) should probably be validated by libyang's parseOp but it only parses.
|
||||
diff --git a/tests/restconf-plain-patch.cpp b/tests/restconf-plain-patch.cpp
|
||||
index b550f54..34813d1 100644
|
||||
--- a/tests/restconf-plain-patch.cpp
|
||||
+++ b/tests/restconf-plain-patch.cpp
|
||||
@@ -179,5 +179,29 @@ TEST_CASE("Plain patch")
|
||||
]
|
||||
}
|
||||
}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(patch(RESTCONF_ROOT_DS("running"), {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({
|
||||
+ "example:channel-plan": {
|
||||
+ "channel": [
|
||||
+ {
|
||||
+ "name": "coriant",
|
||||
+ "lower-frequency": 199999999,
|
||||
+ "upper-frequency": 191500000
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)") == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "operation-failed",
|
||||
+ "error-message": "Validation failed. Invalid input data."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
)"});
|
||||
}
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index f87c4f5..4f0d2ee 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -62,6 +62,7 @@ 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:channel-plan": {},
|
||||
"example:tlc": {},
|
||||
"example:a": {
|
||||
"b": {
|
||||
@@ -119,6 +120,7 @@ TEST_CASE("reading data")
|
||||
|
||||
REQUIRE(get(RESTCONF_ROOT_DS("operational"), {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:channel-plan": {},
|
||||
"example:tlc": {},
|
||||
"example:a": {
|
||||
"b": {
|
||||
@@ -176,6 +178,7 @@ TEST_CASE("reading data")
|
||||
|
||||
REQUIRE(get(RESTCONF_ROOT_DS("running"), {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:channel-plan": {},
|
||||
"example:tlc": {},
|
||||
"example:a": {
|
||||
"b": {
|
||||
diff --git a/tests/yang/example.yang b/tests/yang/example.yang
|
||||
index 5d586a0..1cd12e3 100644
|
||||
--- a/tests/yang/example.yang
|
||||
+++ b/tests/yang/example.yang
|
||||
@@ -27,6 +27,25 @@ module example {
|
||||
}
|
||||
leaf-list top-level-leaf-list { type int32; }
|
||||
|
||||
+ container channel-plan {
|
||||
+ list channel {
|
||||
+ key "name";
|
||||
+ leaf name { type string; }
|
||||
+ leaf lower-frequency {
|
||||
+ type int32;
|
||||
+ units "Hz";
|
||||
+ }
|
||||
+ leaf upper-frequency {
|
||||
+ type int32;
|
||||
+ units "Hz";
|
||||
+ }
|
||||
+
|
||||
+ must "lower-frequency < upper-frequency" {
|
||||
+ description "The lower frequency must be less than the upper frequency.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
container tlc {
|
||||
if-feature f1;
|
||||
list list {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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/44] uri: rename url-encoding to percent-encoding
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
RFC 3986 [1] calls it the percent-encoding, let's be consistent.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/html/rfc3986
|
||||
|
||||
Change-Id: Iee8b76c980b2694b6643e627b462f8bfc2c21c45
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/uri.cpp | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index 6e27168..b144d92 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -29,12 +29,12 @@ auto add = [](auto& ctx) {
|
||||
char c = std::tolower(_attr(ctx));
|
||||
_val(ctx) = _val(ctx) * 16 + (c >= 'a' ? c - 'a' + 10 : c - '0');
|
||||
};
|
||||
-const auto urlEncodedChar = x3::rule<class urlEncodedChar, unsigned>{"urlEncodedChar"} = x3::lit('%')[set_zero] >> x3::xdigit[add] >> x3::xdigit[add];
|
||||
+const auto percentEncodedChar = x3::rule<class percentEncodedChar, unsigned>{"percentEncodedChar"} = x3::lit('%')[set_zero] >> x3::xdigit[add] >> x3::xdigit[add];
|
||||
|
||||
/* reserved characters according to RFC 3986, sec. 2.2 with '%' added. The '%' character is not specified as reserved but it effectively is because
|
||||
* "Percent sign serves as the indicator for percent-encoded octets, it must be percent-encoded (...)" [RFC 3986, sec. 2.4]. */
|
||||
const auto reservedChars = x3::lit(':') | '/' | '?' | '#' | '[' | ']' | '@' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ',' | ';' | '=' | '%';
|
||||
-const auto keyValue = x3::rule<class keyValue, std::string>{"keyValue"} = *(urlEncodedChar | (x3::char_ - reservedChars));
|
||||
+const auto keyValue = x3::rule<class keyValue, std::string>{"keyValue"} = *(percentEncodedChar | (x3::char_ - reservedChars));
|
||||
|
||||
const auto keyList = x3::rule<class keyList, std::vector<std::string>>{"keyList"} = keyValue % ',';
|
||||
const auto identifier = x3::rule<class apiIdentifier, std::string>{"identifier"} = (x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_') | x3::char_('-') | x3::char_('.'));
|
||||
@@ -117,7 +117,7 @@ const auto dateAndTime = x3::rule<class dateAndTime, std::string>{"dateAndTime"}
|
||||
x3::repeat(4)[x3::digit] >> x3::char_('-') >> x3::repeat(2)[x3::digit] >> x3::char_('-') >> x3::repeat(2)[x3::digit] >> x3::char_('T') >>
|
||||
x3::repeat(2)[x3::digit] >> x3::char_(':') >> x3::repeat(2)[x3::digit] >> x3::char_(':') >> x3::repeat(2)[x3::digit] >> -(x3::char_('.') >> +x3::digit) >>
|
||||
(x3::char_('Z') | (-(x3::char_('+')|x3::char_('-')) >> x3::repeat(2)[x3::digit] >> x3::char_(':') >> x3::repeat(2)[x3::digit]));
|
||||
-const auto filter = x3::rule<class filter, std::string>{"filter"} = +(urlEncodedChar | (x3::char_ - '&'));
|
||||
+const auto filter = x3::rule<class filter, std::string>{"filter"} = +(percentEncodedChar | (x3::char_ - '&'));
|
||||
const auto depthParam = x3::rule<class depthParam, queryParams::QueryParamValue>{"depthParam"} = x3::uint_[validDepthValues] | (x3::string("unbounded") >> x3::attr(queryParams::UnboundedDepth{}));
|
||||
const auto queryParamPair = x3::rule<class queryParamPair, std::pair<std::string, queryParams::QueryParamValue>>{"queryParamPair"} =
|
||||
(x3::string("depth") >> "=" >> depthParam) |
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
From d1c56f14b54b113df8fba759924bb47cfa27366c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Fri, 13 Jun 2025 10:47:55 +0200
|
||||
Subject: [PATCH 03/13] restconf: crash instead of a deadlock when the handler
|
||||
throws
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
In case there is an exception thrown in the handler function and it is
|
||||
not caught by our code, rousette starts to behave in a strange way where
|
||||
no new connections are accepted and already connected clients are not
|
||||
receiving the content [1].
|
||||
|
||||
It turns out, that nghttp2-asio starts the boost::asio::io_service
|
||||
wrapped inside std::async [2]. This means the thread terminated, and is
|
||||
waiting for somebody to get the result of the future [3]. But nobody
|
||||
did that in our code.
|
||||
|
||||
Luckily, the (hot-)fix proposed by Jan is quite simple. Rousette uses
|
||||
only one io_service, so we can just call server.join() (which itself
|
||||
calls future::get) instead of the pause() in the main function. If the
|
||||
handler throws, we pick the result right away and rousette crashes.
|
||||
(The crash is intentional and in line with the behaviour of our other
|
||||
daemons in our systems. Daemon crashes, we log it and systemd restarts
|
||||
it).
|
||||
|
||||
Note that to enable graceful shutdown, it was necessary to guard against
|
||||
double call to server->join(), because future::get does not like being
|
||||
called twice [3].
|
||||
|
||||
[1] https://github.com/CESNET/rousette/issues/19
|
||||
[2] https://github.com/nghttp2/nghttp2-asio/blob/a057ffaa207fbe9584565ed503c2ee5c7e609747/lib/asio_io_service_pool.cc#L57
|
||||
[3] https://en.cppreference.com/w/cpp/thread/future/get
|
||||
|
||||
Signed-off-by: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
Bug: https://github.com/CESNET/rousette/issues/19
|
||||
Change-Id: I2c090b9a76b062101ba422a7d50e8e699779e203
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 28 ++++++++++++++++++++++++++++
|
||||
src/restconf/Server.h | 4 ++++
|
||||
src/restconf/main.cpp | 15 ++++++++++-----
|
||||
3 files changed, 42 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 1c2123e..4255540 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -826,6 +826,15 @@ bool isYangPatch(const nghttp2::asio_http2::server::request& req)
|
||||
}
|
||||
|
||||
Server::~Server()
|
||||
+{
|
||||
+ stop();
|
||||
+
|
||||
+ if (!joined) {
|
||||
+ server->join();
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+void Server::stop()
|
||||
{
|
||||
// notification to stop has to go through the asio io_context
|
||||
for (const auto& service : server->io_services()) {
|
||||
@@ -836,8 +845,25 @@ Server::~Server()
|
||||
});
|
||||
t.cancel();
|
||||
}
|
||||
+}
|
||||
+
|
||||
+void Server::join()
|
||||
+{
|
||||
+ /* FIXME: nghttp2-asio is calling io.run() wrapped in std::async.
|
||||
+ * In case the handler function throws, the exception is not propagated to the main thread *until* someone calls server.join() which calls future.get() on all io.run() wrappers.
|
||||
+ * Thankfully, we have only one thread, so we can just call join() right away. Underlying future.get() blocks until io.run() finishes, either gracefully or upon uncaught exception.
|
||||
+ *
|
||||
+ * !!! Will not work server uses multiple threads !!!
|
||||
+ */
|
||||
|
||||
+ // main thread waits here
|
||||
server->join();
|
||||
+ joined = true;
|
||||
+}
|
||||
+
|
||||
+std::vector<std::shared_ptr<boost::asio::io_context>> Server::io_services() const
|
||||
+{
|
||||
+ return server->io_services();
|
||||
}
|
||||
|
||||
Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout)
|
||||
@@ -846,6 +872,8 @@ 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())}
|
||||
{
|
||||
+ server->num_threads(1); // we only use one thread for the server, so we can call join() right away
|
||||
+
|
||||
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", {}},
|
||||
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||
index d1b2187..ad53d4c 100644
|
||||
--- a/src/restconf/Server.h
|
||||
+++ b/src/restconf/Server.h
|
||||
@@ -30,6 +30,9 @@ class Server {
|
||||
public:
|
||||
explicit Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
~Server();
|
||||
+ void join();
|
||||
+ void stop();
|
||||
+ std::vector<std::shared_ptr<boost::asio::io_context>> io_services() const;
|
||||
|
||||
private:
|
||||
sysrepo::Session m_monitoringSession;
|
||||
@@ -39,6 +42,7 @@ private:
|
||||
std::unique_ptr<sr::OpticalEvents> dwdmEvents;
|
||||
using JsonDiffSignal = boost::signals2::signal<void(const std::string& json)>;
|
||||
JsonDiffSignal opticsChange;
|
||||
+ bool joined = false; // true if the server has been joined, join twice is an error
|
||||
};
|
||||
}
|
||||
}
|
||||
diff --git a/src/restconf/main.cpp b/src/restconf/main.cpp
|
||||
index 6029e08..477e846 100644
|
||||
--- a/src/restconf/main.cpp
|
||||
+++ b/src/restconf/main.cpp
|
||||
@@ -5,13 +5,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
-#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "configure.cmake.h" /* Expose HAVE_SYSTEMD */
|
||||
|
||||
+#include <boost/asio.hpp>
|
||||
#ifdef HAVE_SYSTEMD
|
||||
#include <spdlog/sinks/systemd_sink.h>
|
||||
#endif
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <spdlog/sinks/ansicolor_sink.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
-#include <unistd.h>
|
||||
#include <docopt.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sysrepo-cpp/Session.hpp>
|
||||
@@ -106,9 +105,15 @@ int main(int argc, char* argv [])
|
||||
|
||||
auto conn = sysrepo::Connection{};
|
||||
auto server = rousette::restconf::Server{conn, "::1", "10080", timeout};
|
||||
- signal(SIGTERM, [](int) {});
|
||||
- signal(SIGINT, [](int) {});
|
||||
- pause();
|
||||
+
|
||||
+ // allow graceful shutdown
|
||||
+ boost::asio::signal_set signals(*server.io_services()[0], SIGTERM, SIGINT);
|
||||
+ signals.async_wait([&](const boost::system::error_code& ec, int) {
|
||||
+ if (!ec) {
|
||||
+ server.stop();
|
||||
+ }
|
||||
+ });
|
||||
+ server.join();
|
||||
|
||||
return 0;
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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/44] uri: correct x3 rule class names
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Fixes: e06d5bf ("server: parser for data resources in URI paths")
|
||||
Change-Id: Ic953e568d841032113ede1c0e896574361c0ebe2
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/uri.cpp | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index b144d92..8e8dc23 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -37,8 +37,8 @@ const auto reservedChars = x3::lit(':') | '/' | '?' | '#' | '[' | ']' | '@' | '!
|
||||
const auto keyValue = x3::rule<class keyValue, std::string>{"keyValue"} = *(percentEncodedChar | (x3::char_ - reservedChars));
|
||||
|
||||
const auto keyList = x3::rule<class keyList, std::vector<std::string>>{"keyList"} = keyValue % ',';
|
||||
-const auto identifier = x3::rule<class apiIdentifier, std::string>{"identifier"} = (x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_') | x3::char_('-') | x3::char_('.'));
|
||||
-const auto apiIdentifier = x3::rule<class identifier, ApiIdentifier>{"apiIdentifier"} = -(identifier >> ':') >> identifier;
|
||||
+const auto identifier = x3::rule<class identifier, std::string>{"identifier"} = (x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_') | x3::char_('-') | x3::char_('.'));
|
||||
+const auto apiIdentifier = x3::rule<class apiIdentifier, ApiIdentifier>{"apiIdentifier"} = -(identifier >> ':') >> identifier;
|
||||
const auto listInstance = x3::rule<class keyList, PathSegment>{"listInstance"} = apiIdentifier >> -('=' >> keyList);
|
||||
const auto fullyQualifiedApiIdentifier = x3::rule<class identifier, ApiIdentifier>{"apiIdentifier"} = identifier >> ':' >> identifier;
|
||||
const auto fullyQualifiedListInstance = x3::rule<class keyList, PathSegment>{"listInstance"} = fullyQualifiedApiIdentifier >> -('=' >> keyList);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
From db64d7b3fc0a0373067e6c5ac9e41b93351778a1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 7 Aug 2024 19:07:35 +0200
|
||||
Subject: [PATCH 04/13] close long-lived connections on SIGTERM
|
||||
Organization: Wires
|
||||
|
||||
Without this patch, all the ongoing SSE streams would be left alive for
|
||||
about 50s, and only then killed. Fix that by a proper signalling -- once
|
||||
the intention to shutdown is known, all the EventStreams are
|
||||
(asynchronously) switched to a new state, and the HTTP2 state machine is
|
||||
restarted. Once the event loop gets around to processing them, the
|
||||
callback will just say EOF.
|
||||
|
||||
The clock demo is different because it doesn't wrap the nghttp2 server
|
||||
like the restconf/Server.cpp does.
|
||||
|
||||
I have no idea how well this works against a Slowloris attack, but I
|
||||
think that stuff like that should be solved at the HTTP library level (I
|
||||
hope it is).
|
||||
|
||||
Change-Id: If442134783ba1d699de47c51a9068378f53e8339
|
||||
Co-authored-by: Tomas Pecka <tomas.pecka@cesnet.cz>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/clock.cpp | 7 +--
|
||||
src/http/EventStream.cpp | 30 +++++++++++--
|
||||
src/http/EventStream.h | 10 +++--
|
||||
src/restconf/NotificationStream.cpp | 7 +--
|
||||
src/restconf/NotificationStream.h | 5 ++-
|
||||
src/restconf/Server.cpp | 5 ++-
|
||||
src/restconf/Server.h | 1 +
|
||||
tests/restconf-eventstream.cpp | 70 +++++++++++++++++++++++++++++
|
||||
tests/restconf_utils.cpp | 6 +++
|
||||
10 files changed, 124 insertions(+), 18 deletions(-)
|
||||
create mode 100644 tests/restconf-eventstream.cpp
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index b99c577..c0304af 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -227,6 +227,7 @@ if(BUILD_TESTING)
|
||||
rousette_test(NAME restconf-notifications LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
rousette_test(NAME restconf-plain-patch LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
rousette_test(NAME restconf-yang-patch LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
+ rousette_test(NAME restconf-eventstream LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
set(nested-models
|
||||
${common-models}
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/root-mod.yang)
|
||||
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||
index 7ac6c3e..c85be19 100644
|
||||
--- a/src/clock.cpp
|
||||
+++ b/src/clock.cpp
|
||||
@@ -18,7 +18,8 @@ int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
|
||||
- rousette::http::EventStream::Signal sig;
|
||||
+ rousette::http::EventStream::Termination shutdown;
|
||||
+ rousette::http::EventStream::EventSignal sig;
|
||||
std::jthread timer{[&sig]() {
|
||||
for (int i = 0; /* forever */; ++i) {
|
||||
std::this_thread::sleep_for(666ms);
|
||||
@@ -30,8 +31,8 @@ int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||
nghttp2::asio_http2::server::http2 server;
|
||||
server.num_threads(4);
|
||||
|
||||
- server.handle("/events", [&sig](const auto& req, const auto& res) {
|
||||
- auto client = std::make_shared<rousette::http::EventStream>(req, res, sig);
|
||||
+ server.handle("/events", [&shutdown, &sig](const auto& req, const auto& res) {
|
||||
+ auto client = std::make_shared<rousette::http::EventStream>(req, res, shutdown, sig);
|
||||
client->activate();
|
||||
});
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 25dbf3f..77c282d 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -18,7 +18,7 @@ using namespace nghttp2::asio_http2;
|
||||
namespace rousette::http {
|
||||
|
||||
/** @short After constructing, make sure to call activate() immediately. */
|
||||
-EventStream::EventStream(const server::request& req, const server::response& res, Signal& signal, const std::optional<std::string>& initialEvent)
|
||||
+EventStream::EventStream(const server::request& req, const server::response& res, Termination& termination, EventSignal& signal, const std::optional<std::string>& initialEvent)
|
||||
: res{res}
|
||||
, peer{peer_from_request(req)}
|
||||
{
|
||||
@@ -26,9 +26,27 @@ EventStream::EventStream(const server::request& req, const server::response& res
|
||||
enqueue(*initialEvent);
|
||||
}
|
||||
|
||||
- subscription = signal.connect([this](const auto& msg) {
|
||||
+ eventSub = signal.connect([this](const auto& msg) {
|
||||
enqueue(msg);
|
||||
});
|
||||
+
|
||||
+ terminateSub = termination.connect([this]() {
|
||||
+ spdlog::trace("{}: will terminate", peer);
|
||||
+ std::lock_guard lock{mtx};
|
||||
+ if (state == Closed) { // we are late to the party, res is already gone
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ state = WantToClose;
|
||||
+ boost::asio::post(this->res.io_service(), [maybeClient = std::weak_ptr<EventStream>{shared_from_this()}]() {
|
||||
+ if (auto client = maybeClient.lock()) {
|
||||
+ std::lock_guard lock{client->mtx};
|
||||
+ if (client->state == WantToClose) { // resume unless somebody closed it before this was picked up by the event loop
|
||||
+ client->res.resume();
|
||||
+ }
|
||||
+ }
|
||||
+ });
|
||||
+ });
|
||||
}
|
||||
|
||||
/** @short Start event processing and data delivery
|
||||
@@ -47,7 +65,8 @@ void EventStream::activate()
|
||||
res.on_close([client](const auto ec) {
|
||||
spdlog::debug("{}: closed ({})", client->peer, nghttp2_http2_strerror(ec));
|
||||
std::lock_guard lock{client->mtx};
|
||||
- client->subscription.disconnect();
|
||||
+ client->eventSub.disconnect();
|
||||
+ client->terminateSub.disconnect();
|
||||
client->state = Closed;
|
||||
});
|
||||
|
||||
@@ -85,6 +104,9 @@ ssize_t EventStream::process(uint8_t* destination, std::size_t len, uint32_t* da
|
||||
case WaitingForEvents:
|
||||
spdlog::trace("{}: sleeping", peer);
|
||||
return NGHTTP2_ERR_DEFERRED;
|
||||
+ case WantToClose:
|
||||
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
||||
+ return 0;
|
||||
case Closed:
|
||||
throw std::logic_error{"response already closed"};
|
||||
}
|
||||
@@ -105,7 +127,7 @@ void EventStream::enqueue(const std::string& what)
|
||||
buf += '\n';
|
||||
|
||||
std::lock_guard lock{mtx};
|
||||
- if (state == Closed) {
|
||||
+ if (state == Closed || state == WantToClose) {
|
||||
spdlog::trace("{}: enqueue: already disconnected", peer);
|
||||
return;
|
||||
}
|
||||
diff --git a/src/http/EventStream.h b/src/http/EventStream.h
|
||||
index 735b69a..b427fb3 100644
|
||||
--- a/src/http/EventStream.h
|
||||
+++ b/src/http/EventStream.h
|
||||
@@ -23,13 +23,14 @@ namespace rousette::http {
|
||||
|
||||
/** @short Event delivery via text/event-stream
|
||||
|
||||
-Recieve data from a Signal, and deliver them to an HTTP client via a text/event-stream streamed response.
|
||||
+Recieve data from an EventSignal, and deliver them to an HTTP client via a text/event-stream streamed response.
|
||||
*/
|
||||
class EventStream : public std::enable_shared_from_this<EventStream> {
|
||||
public:
|
||||
- using Signal = boost::signals2::signal<void(const std::string& message)>;
|
||||
+ using EventSignal = boost::signals2::signal<void(const std::string& message)>;
|
||||
+ using Termination = boost::signals2::signal<void()>;
|
||||
|
||||
- EventStream(const nghttp2::asio_http2::server::request& req, const nghttp2::asio_http2::server::response& res, Signal& signal, const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
+ EventStream(const nghttp2::asio_http2::server::request& req, const nghttp2::asio_http2::server::response& res, Termination& terminate, EventSignal& signal, const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
void activate();
|
||||
|
||||
private:
|
||||
@@ -37,13 +38,14 @@ private:
|
||||
enum State {
|
||||
HasEvents,
|
||||
WaitingForEvents,
|
||||
+ WantToClose,
|
||||
Closed,
|
||||
};
|
||||
|
||||
State state = WaitingForEvents;
|
||||
std::list<std::string> queue;
|
||||
mutable std::mutex mtx; // for `state` and `queue`
|
||||
- boost::signals2::scoped_connection subscription;
|
||||
+ boost::signals2::scoped_connection eventSub, terminateSub;
|
||||
const std::string peer;
|
||||
|
||||
size_t send_chunk(uint8_t* destination, std::size_t len, uint32_t* data_flags);
|
||||
diff --git a/src/restconf/NotificationStream.cpp b/src/restconf/NotificationStream.cpp
|
||||
index e00c39d..d0ba938 100644
|
||||
--- a/src/restconf/NotificationStream.cpp
|
||||
+++ b/src/restconf/NotificationStream.cpp
|
||||
@@ -24,7 +24,7 @@ void subscribe(
|
||||
std::optional<sysrepo::Subscription>& sub,
|
||||
sysrepo::Session& session,
|
||||
const std::string& moduleName,
|
||||
- rousette::http::EventStream::Signal& signal,
|
||||
+ rousette::http::EventStream::EventSignal& signal,
|
||||
libyang::DataFormat dataFormat,
|
||||
const std::optional<std::string>& filter,
|
||||
const std::optional<sysrepo::NotificationTimeStamp>& startTime,
|
||||
@@ -87,13 +87,14 @@ namespace rousette::restconf {
|
||||
NotificationStream::NotificationStream(
|
||||
const nghttp2::asio_http2::server::request& req,
|
||||
const nghttp2::asio_http2::server::response& res,
|
||||
- std::shared_ptr<rousette::http::EventStream::Signal> signal,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||
sysrepo::Session session,
|
||||
libyang::DataFormat dataFormat,
|
||||
const std::optional<std::string>& filter,
|
||||
const std::optional<sysrepo::NotificationTimeStamp>& startTime,
|
||||
const std::optional<sysrepo::NotificationTimeStamp>& stopTime)
|
||||
- : EventStream(req, res, *signal)
|
||||
+ : EventStream(req, res, termination, *signal)
|
||||
, m_notificationSignal(signal)
|
||||
, m_session(std::move(session))
|
||||
, m_dataFormat(dataFormat)
|
||||
diff --git a/src/restconf/NotificationStream.h b/src/restconf/NotificationStream.h
|
||||
index 3029a0d..46f8416 100644
|
||||
--- a/src/restconf/NotificationStream.h
|
||||
+++ b/src/restconf/NotificationStream.h
|
||||
@@ -30,7 +30,7 @@ namespace rousette::restconf {
|
||||
* @see rousette::http::EventStream
|
||||
* */
|
||||
class NotificationStream : public rousette::http::EventStream {
|
||||
- std::shared_ptr<rousette::http::EventStream::Signal> m_notificationSignal;
|
||||
+ std::shared_ptr<rousette::http::EventStream::EventSignal> m_notificationSignal;
|
||||
sysrepo::Session m_session;
|
||||
libyang::DataFormat m_dataFormat;
|
||||
std::optional<std::string> m_filter;
|
||||
@@ -42,7 +42,8 @@ public:
|
||||
NotificationStream(
|
||||
const nghttp2::asio_http2::server::request& req,
|
||||
const nghttp2::asio_http2::server::response& res,
|
||||
- std::shared_ptr<rousette::http::EventStream::Signal> signal,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||
sysrepo::Session sess,
|
||||
libyang::DataFormat dataFormat,
|
||||
const std::optional<std::string>& filter,
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 4255540..2f495f9 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -845,6 +845,7 @@ void Server::stop()
|
||||
});
|
||||
t.cancel();
|
||||
}
|
||||
+ shutdownRequested();
|
||||
}
|
||||
|
||||
void Server::join()
|
||||
@@ -935,7 +936,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
server->handle("/telemetry/optics", [this](const auto& req, const auto& res) {
|
||||
logRequest(req);
|
||||
|
||||
- auto client = std::make_shared<http::EventStream>(req, res, opticsChange, as_restconf_push_update(dwdmEvents->currentData(), std::chrono::system_clock::now()));
|
||||
+ auto client = std::make_shared<http::EventStream>(req, res, shutdownRequested, opticsChange, as_restconf_push_update(dwdmEvents->currentData(), std::chrono::system_clock::now()));
|
||||
client->activate();
|
||||
});
|
||||
|
||||
@@ -972,7 +973,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, streamRequest.type.encoding, xpathFilter, startTime, stopTime);
|
||||
+ auto client = std::make_shared<NotificationStream>(req, res, shutdownRequested, std::make_shared<rousette::http::EventStream::EventSignal>(), sess, streamRequest.type.encoding, xpathFilter, startTime, stopTime);
|
||||
client->activate();
|
||||
} catch (const auth::Error& e) {
|
||||
processAuthError(req, res, e, [&res]() {
|
||||
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||
index ad53d4c..112c943 100644
|
||||
--- a/src/restconf/Server.h
|
||||
+++ b/src/restconf/Server.h
|
||||
@@ -43,6 +43,7 @@ private:
|
||||
using JsonDiffSignal = boost::signals2::signal<void(const std::string& json)>;
|
||||
JsonDiffSignal opticsChange;
|
||||
bool joined = false; // true if the server has been joined, join twice is an error
|
||||
+ boost::signals2::signal<void()> shutdownRequested;
|
||||
};
|
||||
}
|
||||
}
|
||||
diff --git a/tests/restconf-eventstream.cpp b/tests/restconf-eventstream.cpp
|
||||
new file mode 100644
|
||||
index 0000000..3d7fc15
|
||||
--- /dev/null
|
||||
+++ b/tests/restconf-eventstream.cpp
|
||||
@@ -0,0 +1,70 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ *
|
||||
+ */
|
||||
+
|
||||
+#include "trompeloeil_doctest.h"
|
||||
+static const auto SERVER_PORT = "10091";
|
||||
+#include <latch>
|
||||
+#include <libyang-cpp/Time.hpp>
|
||||
+#include <nghttp2/asio_http2.h>
|
||||
+#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
+#include "restconf/Server.h"
|
||||
+#include "tests/aux-utils.h"
|
||||
+#include "tests/event_watchers.h"
|
||||
+#include "tests/pretty_printers.h"
|
||||
+
|
||||
+#define SEND_NOTIFICATION(DATA) notifSession.sendNotification(*ctx.parseOp(DATA, libyang::DataFormat::JSON, libyang::OperationType::NotificationYang).op, sysrepo::Wait::No);
|
||||
+
|
||||
+using namespace std::chrono_literals;
|
||||
+
|
||||
+TEST_CASE("Termination on server shutdown")
|
||||
+{
|
||||
+ trompeloeil::sequence seqMod1;
|
||||
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||
+ spdlog::set_level(spdlog::level::trace);
|
||||
+
|
||||
+ std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
+
|
||||
+ auto srConn = sysrepo::Connection{};
|
||||
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
+
|
||||
+ auto nacmGuard = manageNacm(srSess);
|
||||
+ auto server = std::make_unique<rousette::restconf::Server>(srConn, SERVER_ADDRESS, SERVER_PORT);
|
||||
+ setupRealNacm(srSess);
|
||||
+
|
||||
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
+
|
||||
+ const std::string notification(R"({"example:eventA":{"message":"blabla","progress":11}})");
|
||||
+ EXPECT_NOTIFICATION(notification, seqMod1);
|
||||
+ EXPECT_NOTIFICATION(notification, seqMod1);
|
||||
+ EXPECT_NOTIFICATION(notification, seqMod1);
|
||||
+
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
+ auto notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ waitForCompletionAndBitMore(seqMod1);
|
||||
+
|
||||
+ auto beforeShutdown = std::chrono::system_clock::now();
|
||||
+ server.reset();
|
||||
+ auto shutdownDuration = std::chrono::system_clock::now() - beforeShutdown;
|
||||
+ REQUIRE(shutdownDuration < 5s);
|
||||
+ }));
|
||||
+
|
||||
+ SSEClient client(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, "/streams/NETCONF/JSON", std::map<std::string, std::string>{AUTH_ROOT});
|
||||
+
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+}
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index a137463..cbfce2c 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -205,6 +205,12 @@ SSEClient::SSEClient(
|
||||
t.expires_from_now(silenceTimeout);
|
||||
});
|
||||
});
|
||||
+
|
||||
+ req->on_close([maybeClient = std::weak_ptr<ng_client::session>{client}](auto) {
|
||||
+ if (auto client = maybeClient.lock()) {
|
||||
+ client->shutdown();
|
||||
+ }
|
||||
+ });
|
||||
});
|
||||
|
||||
client->on_error([&](const boost::system::error_code& ec) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
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/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: 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
|
||||
encoded chars to their "normal" form.
|
||||
|
||||
Our parser expects some parts of the URI to be percent encoded so we
|
||||
have to use raw_paths everywhere.
|
||||
|
||||
I thought about rewriting the parser not to expect percent encoded
|
||||
characters but that would bring some complications. For instance when
|
||||
querying lists, the RESTCONF RFC specifies that every key value is
|
||||
percent encoded and individual key values are delimited by commas [1].
|
||||
So, when somebody sends a request like /restconf/data/list=a%2Cb,c the
|
||||
"normal" form is /restconf/data/list=a,b,c and in that case we obtain
|
||||
three keys, but the client sent only two, where the first one contained
|
||||
comma.
|
||||
|
||||
I am adding few tests to check for percent encoded values.
|
||||
|
||||
We realized this while working on one of the reported bugs [1]. The
|
||||
query sent by the client there is wrong, the ':' char should be
|
||||
percent-encoded.
|
||||
|
||||
[1] https://github.com/CESNET/rousette/issues/12
|
||||
|
||||
Bug: https://github.com/CESNET/rousette/issues/12
|
||||
Change-Id: I473501cef3c8eae9af0c5d0751393cdad647e23c
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 8 ++++----
|
||||
src/restconf/uri.cpp | 4 ++--
|
||||
tests/restconf-reading.cpp | 15 +++++++++++++++
|
||||
tests/uri-parser.cpp | 5 +++--
|
||||
4 files changed, 24 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 5f560ed..79d8ff6 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -416,7 +416,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
* - The data node exists but might get deleted right after this check: Sysrepo throws an error when this happens.
|
||||
* - The data node does not exist but might get created right after this check: The node was not there when the request was issues so it should not be a problem
|
||||
*/
|
||||
- auto [pathToParent, pathSegment] = asLibyangPathSplit(ctx, requestCtx->req.uri().path);
|
||||
+ auto [pathToParent, pathSegment] = asLibyangPathSplit(ctx, requestCtx->req.uri().raw_path);
|
||||
if (!requestCtx->sess.getData(pathToParent, 0, sysrepo::GetOptions::Default, timeout)) {
|
||||
throw ErrorResponse(400, "application", "operation-failed", "Action data node '" + requestCtx->restconfRequest.path + "' does not exist.");
|
||||
}
|
||||
@@ -539,7 +539,7 @@ void processYangPatchEdit(const std::shared_ptr<RequestContext>& requestCtx, con
|
||||
auto target = childLeafValue(editContainer, "target");
|
||||
auto operation = childLeafValue(editContainer, "operation");
|
||||
|
||||
- auto [singleEdit, replacementNode] = createEditForPutAndPatch(ctx, requestCtx->req.uri().path + target, yangPatchValueAsJSON(editContainer), libyang::DataFormat::JSON);
|
||||
+ auto [singleEdit, replacementNode] = createEditForPutAndPatch(ctx, requestCtx->req.uri().raw_path + target, yangPatchValueAsJSON(editContainer), libyang::DataFormat::JSON);
|
||||
validateInputMetaAttributes(ctx, *singleEdit);
|
||||
|
||||
// insert and move are not defined in RFC6241. sec 7.3 and sysrepo does not support them directly
|
||||
@@ -658,7 +658,7 @@ void processPutOrPlainPatch(std::shared_ptr<RequestContext> requestCtx, const st
|
||||
throw ErrorResponse(400, "protocol", "invalid-value", "Target resource does not exist");
|
||||
}
|
||||
|
||||
- auto [edit, replacementNode] = createEditForPutAndPatch(ctx, requestCtx->req.uri().path, requestCtx->payload, *requestCtx->dataFormat.request /* caller checks if the dataFormat.request is present */);
|
||||
+ auto [edit, replacementNode] = createEditForPutAndPatch(ctx, requestCtx->req.uri().raw_path, requestCtx->payload, *requestCtx->dataFormat.request /* caller checks if the dataFormat.request is present */);
|
||||
validateInputMetaAttributes(ctx, *edit);
|
||||
|
||||
if (requestCtx->req.method() == "PUT") {
|
||||
@@ -954,7 +954,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
dataFormat = chooseDataEncoding(req.header());
|
||||
authorizeRequest(nacm, sess, req);
|
||||
|
||||
- auto restconfRequest = asRestconfRequest(sess.getContext(), req.method(), req.uri().path, req.uri().raw_query);
|
||||
+ auto restconfRequest = asRestconfRequest(sess.getContext(), req.method(), req.uri().raw_path, req.uri().raw_query);
|
||||
|
||||
switch (restconfRequest.type) {
|
||||
case RestconfRequest::Type::RestconfRoot:
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index 8e8dc23..ac399b7 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -34,9 +34,9 @@ const auto percentEncodedChar = x3::rule<class percentEncodedChar, unsigned>{"pe
|
||||
/* reserved characters according to RFC 3986, sec. 2.2 with '%' added. The '%' character is not specified as reserved but it effectively is because
|
||||
* "Percent sign serves as the indicator for percent-encoded octets, it must be percent-encoded (...)" [RFC 3986, sec. 2.4]. */
|
||||
const auto reservedChars = x3::lit(':') | '/' | '?' | '#' | '[' | ']' | '@' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ',' | ';' | '=' | '%';
|
||||
-const auto keyValue = x3::rule<class keyValue, std::string>{"keyValue"} = *(percentEncodedChar | (x3::char_ - reservedChars));
|
||||
+const auto percentEncodedString = x3::rule<class percentEncodedString, std::string>{"percentEncodedString"} = *(percentEncodedChar | (x3::char_ - reservedChars));
|
||||
|
||||
-const auto keyList = x3::rule<class keyList, std::vector<std::string>>{"keyList"} = keyValue % ',';
|
||||
+const auto keyList = x3::rule<class keyList, std::vector<std::string>>{"keyList"} = percentEncodedString % ',';
|
||||
const auto identifier = x3::rule<class identifier, std::string>{"identifier"} = (x3::alpha | x3::char_('_')) >> *(x3::alnum | x3::char_('_') | x3::char_('-') | x3::char_('.'));
|
||||
const auto apiIdentifier = x3::rule<class apiIdentifier, ApiIdentifier>{"apiIdentifier"} = -(identifier >> ':') >> identifier;
|
||||
const auto listInstance = x3::rule<class keyList, PathSegment>{"listInstance"} = apiIdentifier >> -('=' >> keyList);
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index fa1cbcc..2898839 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -261,6 +261,21 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
|
||||
+ // percent-encoded comma is a part of the key value, it is not a delimiter
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius/server=a%2Cb", {AUTH_DWDM}) == Response{404, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-message": "No data from sysrepo."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ // comma is a delimiter of list key values
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius/server=a,b", {AUTH_DWDM}) == Response{400, jsonHeaders, R"({
|
||||
"ietf-restconf:errors": {
|
||||
"error": [
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index a748e09..5977afc 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -158,9 +158,9 @@ TEST_CASE("URI path parser")
|
||||
{{"prefix", "lst"}, {"key1"}},
|
||||
{{"prefix", "leaf"}},
|
||||
}}},
|
||||
- {"/restconf/data/foo:bar/lst=key1,,key3", {{
|
||||
+ {"/restconf/data/foo:bar/lst=module%3Akey1,,key3", {{
|
||||
{{"foo", "bar"}},
|
||||
- {{"lst"}, {"key1", "", "key3"}},
|
||||
+ {{"lst"}, {"module:key1", "", "key3"}},
|
||||
}}},
|
||||
{"/restconf/data/foo:bar/lst=key%2CWithCommas,,key2C", {{
|
||||
{{"foo", "bar"}},
|
||||
@@ -240,6 +240,7 @@ TEST_CASE("URI path parser")
|
||||
"/restconf/data/foo:list=A%2",
|
||||
"/restconf/data/foo:list=A%2,",
|
||||
"/restconf/data/foo:bar/list1=%%",
|
||||
+ "/restconf/data/foo:bar/list1=module:smth",
|
||||
"/restconf/data/foo:bar/",
|
||||
"/restconf/data/ foo : bar",
|
||||
"/rest conf/data / foo:bar",
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
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/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: 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
|
||||
the input when someone used identityref with module prefix in the URI,
|
||||
but also, our internal code for creating/modifying list entries was
|
||||
wrong.
|
||||
|
||||
In PUT requests we have to check if user entered the same key value in
|
||||
URI and yang-data payload. However, some values are canonicalized by
|
||||
libyang (e.g. decimal64 type with fraction-digits=2 or even the
|
||||
identityrefs) and so if the client entered two different but
|
||||
canonically equivalent values, we would reject such request.
|
||||
|
||||
Bug: https://github.com/CESNET/rousette/issues/12
|
||||
Change-Id: I44245d831e8de6d0e6f991fcd18319c095b49b1d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 +-
|
||||
src/restconf/Server.cpp | 49 +++++++++++++++++----
|
||||
src/restconf/utils/yang.cpp | 5 +++
|
||||
src/restconf/utils/yang.h | 1 +
|
||||
tests/restconf-reading.cpp | 59 +++++++++++++++++++++++++
|
||||
tests/restconf-writing.cpp | 82 +++++++++++++++++++++++++++++++++++
|
||||
tests/uri-parser.cpp | 2 +
|
||||
tests/yang/example-types.yang | 13 ++++++
|
||||
tests/yang/example.yang | 25 +++++++++++
|
||||
9 files changed, 229 insertions(+), 10 deletions(-)
|
||||
create mode 100644 tests/yang/example-types.yang
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c563dcf..465bef9 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -204,7 +204,8 @@ if(BUILD_TESTING)
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-augment.yang
|
||||
- --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-notif.yang)
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-notif.yang
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-types.yang)
|
||||
rousette_test(NAME restconf-reading LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
rousette_test(NAME restconf-writing LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
rousette_test(NAME restconf-delete LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 79d8ff6..b515d66 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -154,6 +154,15 @@ auto rejectYangPatch(const std::string& patchId, const std::string& editId)
|
||||
};
|
||||
}
|
||||
|
||||
+/** @brief Check if these two paths compare the same after path canonicalization */
|
||||
+bool compareKeyValue(const libyang::Context& ctx, const std::string& pathA, const std::string& pathB)
|
||||
+{
|
||||
+ auto [parentA, nodeA] = ctx.newPath2(pathA, std::nullopt);
|
||||
+ auto [parentB, nodeB] = ctx.newPath2(pathB, std::nullopt);
|
||||
+
|
||||
+ return nodeA->asTerm().valueStr() == nodeB->asTerm().valueStr();
|
||||
+}
|
||||
+
|
||||
struct KeyMismatch {
|
||||
libyang::DataNode offendingNode;
|
||||
std::optional<std::string> uriKeyValue;
|
||||
@@ -169,25 +178,46 @@ struct KeyMismatch {
|
||||
|
||||
/** @brief In case node is a (leaf-)list check if the key values are the same as the keys specified in the lastPathSegment.
|
||||
* @return The node where the mismatch occurs */
|
||||
-std::optional<KeyMismatch> checkKeysMismatch(const libyang::DataNode& node, const PathSegment& lastPathSegment)
|
||||
+std::optional<KeyMismatch> checkKeysMismatch(libyang::Context& ctx, const libyang::DataNode& node, const std::string& lyParentPath, const PathSegment& lastPathSegment)
|
||||
{
|
||||
+ const auto pathPrefix = (lyParentPath.empty() ? "" : lyParentPath) + "/" + lastPathSegment.apiIdent.name();
|
||||
+
|
||||
if (node.schema().nodeType() == libyang::NodeType::List) {
|
||||
const auto& listKeys = node.schema().asList().keys();
|
||||
for (size_t i = 0; i < listKeys.size(); ++i) {
|
||||
- const auto& keyValueURI = lastPathSegment.keys[i];
|
||||
auto keyNodeData = node.findPath(listKeys[i].module().name() + ':' + listKeys[i].name());
|
||||
if (!keyNodeData) {
|
||||
return KeyMismatch{node, std::nullopt};
|
||||
}
|
||||
|
||||
- const auto& keyValueData = keyNodeData->asTerm().valueStr();
|
||||
-
|
||||
- if (keyValueURI != keyValueData) {
|
||||
- return KeyMismatch{*keyNodeData, keyValueURI};
|
||||
+ /*
|
||||
+ * If the key's value has a canonical form then libyang makes the value canonical
|
||||
+ * but there is no guarantee that the client provided the value in the canonical form.
|
||||
+ *
|
||||
+ * Let libyang do the work. Create two data nodes, one with the key value from the data and the other
|
||||
+ * with the key value from the URI. Then compare the values from the two nodes. If they are different,
|
||||
+ * they certainly mismatch.
|
||||
+ *
|
||||
+ * This can happen in cases like
|
||||
+ * * The key's type is identityref and the client provided the key value as a string without the module name. Libyang will canonicalize the value by adding the module name.
|
||||
+ * * The key's type is decimal64 with fractional-digits 2; then the client can provide the value as 1.0 or 1.00 and they should be the same. Libyang will canonicalize the value.
|
||||
+ */
|
||||
+
|
||||
+ auto keysWithValueFromData = lastPathSegment.keys;
|
||||
+ keysWithValueFromData[i] = keyNodeData->asTerm().valueStr();
|
||||
+
|
||||
+ const auto suffix = "/" + listKeys[i].name();
|
||||
+ const auto pathFromData = pathPrefix + listKeyPredicate(listKeys, keysWithValueFromData) + suffix;
|
||||
+ const auto pathFromURI = pathPrefix + listKeyPredicate(listKeys, lastPathSegment.keys) + suffix;
|
||||
+
|
||||
+ if (!compareKeyValue(ctx, pathFromData, pathFromURI)) {
|
||||
+ return KeyMismatch{*keyNodeData, lastPathSegment.keys[i]};
|
||||
}
|
||||
}
|
||||
} else if (node.schema().nodeType() == libyang::NodeType::Leaflist) {
|
||||
- if (lastPathSegment.keys[0] != node.asTerm().valueStr()) {
|
||||
+ const auto pathFromData = pathPrefix + leaflistKeyPredicate(node.asTerm().valueStr());
|
||||
+ const auto pathFromURI = pathPrefix + leaflistKeyPredicate(lastPathSegment.keys[0]);
|
||||
+ if (!compareKeyValue(ctx, pathFromData, pathFromURI)) {
|
||||
return KeyMismatch{node, lastPathSegment.keys[0]};
|
||||
}
|
||||
}
|
||||
@@ -363,7 +393,7 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
if (isSameNode(child, lastPathSegment)) {
|
||||
// 1) a single child that is created by parseSubtree(), its name is the same as `lastPathSegment`.
|
||||
// It could be a list; then we need to check if the keys in provided data match the keys in URI.
|
||||
- if (auto keyMismatch = checkKeysMismatch(child, lastPathSegment)) {
|
||||
+ if (auto keyMismatch = checkKeysMismatch(ctx, child, lyParentPath, lastPathSegment)) {
|
||||
throw ErrorResponse(400, "protocol", "invalid-value", keyMismatch->message(), keyMismatch->offendingNode.path());
|
||||
}
|
||||
replacementNode = child;
|
||||
@@ -386,7 +416,8 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
if (!isSameNode(*replacementNode, lastPathSegment)) {
|
||||
throw ErrorResponse(400, "protocol", "invalid-value", "Data contains invalid node.", replacementNode->path());
|
||||
}
|
||||
- if (auto keyMismatch = checkKeysMismatch(*parent, lastPathSegment)) {
|
||||
+
|
||||
+ if (auto keyMismatch = checkKeysMismatch(ctx, *parent, lyParentPath, lastPathSegment)) {
|
||||
throw ErrorResponse(400, "protocol", "invalid-value", keyMismatch->message(), keyMismatch->offendingNode.path());
|
||||
}
|
||||
}
|
||||
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||
index 15cceb6..4c4d619 100644
|
||||
--- a/src/restconf/utils/yang.cpp
|
||||
+++ b/src/restconf/utils/yang.cpp
|
||||
@@ -50,6 +50,11 @@ std::string listKeyPredicate(const std::vector<libyang::Leaf>& listKeyLeafs, con
|
||||
return res;
|
||||
}
|
||||
|
||||
+std::string leaflistKeyPredicate(const std::string& keyValue)
|
||||
+{
|
||||
+ return "[.=" + escapeListKey(keyValue) + ']';
|
||||
+}
|
||||
+
|
||||
bool isUserOrderedList(const libyang::DataNode& node)
|
||||
{
|
||||
if (node.schema().nodeType() == libyang::NodeType::List) {
|
||||
diff --git a/src/restconf/utils/yang.h b/src/restconf/utils/yang.h
|
||||
index 677d049..e91ba8a 100644
|
||||
--- a/src/restconf/utils/yang.h
|
||||
+++ b/src/restconf/utils/yang.h
|
||||
@@ -16,6 +16,7 @@ namespace rousette::restconf {
|
||||
|
||||
std::string escapeListKey(const std::string& str);
|
||||
std::string listKeyPredicate(const std::vector<libyang::Leaf>& listKeyLeafs, const std::vector<std::string>& keyValues);
|
||||
+std::string leaflistKeyPredicate(const std::string& keyValue);
|
||||
bool isUserOrderedList(const libyang::DataNode& node);
|
||||
bool isKeyNode(const libyang::DataNode& maybeList, const libyang::DataNode& node);
|
||||
}
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index 2898839..96c38ab 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -287,6 +287,65 @@ TEST_CASE("reading data")
|
||||
]
|
||||
}
|
||||
}
|
||||
+)"});
|
||||
+
|
||||
+ srSess.setItem("/example:list-with-identity-key[type='example:derived-identity'][name='name']", std::nullopt);
|
||||
+ srSess.setItem("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']", std::nullopt);
|
||||
+ srSess.setItem("/example:tlc/decimal-list[.='1.00']", std::nullopt);
|
||||
+ srSess.applyChanges();
|
||||
+
|
||||
+ // dealing with keys which can have prefixes (YANG identities)
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=derived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-identity-key": [
|
||||
+ {
|
||||
+ "type": "derived-identity",
|
||||
+ "name": "name"
|
||||
+ }
|
||||
+ ]
|
||||
+}
|
||||
+)"});
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example%3Aderived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-identity-key": [
|
||||
+ {
|
||||
+ "type": "derived-identity",
|
||||
+ "name": "name"
|
||||
+ }
|
||||
+ ]
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ // an identity from another module must be namespace-qualified
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=another-derived-identity,name", {AUTH_ROOT}) == Response{404, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-message": "No data from sysrepo."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example-types%3Aanother-derived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-identity-key": [
|
||||
+ {
|
||||
+ "type": "example-types:another-derived-identity",
|
||||
+ "name": "name"
|
||||
+ }
|
||||
+ ]
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ // test canonicalization of list key values; the key value was inserted as "1.00"
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/decimal-list=1", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "decimal-list": [
|
||||
+ "1.0"
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
)"});
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index 96dbb25..8418554 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -389,6 +389,88 @@ TEST_CASE("writing data")
|
||||
REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/list=libyang/nested=11,12,13", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:nested": [{"first": "11", "second": 12, "third": "13"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
}
|
||||
|
||||
+ SECTION("Test canonicalization of keys")
|
||||
+ {
|
||||
+ EXPECT_CHANGE(
|
||||
+ CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/type", "example:derived-identity"),
|
||||
+ CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/name", "name"),
|
||||
+ CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/text", "blabla"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ // prefixed in the URI, not prefixed in the data
|
||||
+ EXPECT_CHANGE(
|
||||
+ MODIFIED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/text", "hehe"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example%3Aderived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "derived-identity", "text": "hehe"}]}]})") == Response{204, noContentTypeHeaders, ""});
|
||||
+
|
||||
+
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=another-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "another-derived-identity", "text": "blabla"}]}]})") == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "protocol",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-message": "Validation failure: Can't parse data: LY_EVALID"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ EXPECT_CHANGE(
|
||||
+ CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']/type", "example-types:another-derived-identity"),
|
||||
+ CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']/name", "name"),
|
||||
+ CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']/text", "blabla"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example-types%3Aanother-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "example-types:another-derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ // missing namespace in the data
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example-types%3Aanother-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "another-derived-identity", "text": "blabla"}]}]})") == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "protocol",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-message": "Validation failure: Can't parse data: LY_EVALID"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ EXPECT_CHANGE(CREATED("/example:leaf-list-with-identity-key[.='example-types:another-derived-identity']", "example-types:another-derived-identity"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:leaf-list-with-identity-key=example-types%3Aanother-derived-identity", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:leaf-list-with-identity-key": ["example-types:another-derived-identity"]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ // missing namespace in the URI
|
||||
+ EXPECT_CHANGE(CREATED("/example:leaf-list-with-identity-key[.='example:derived-identity']", "example:derived-identity"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:leaf-list-with-identity-key=derived-identity", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:leaf-list-with-identity-key": ["example:derived-identity"]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:leaf-list-with-identity-key=example-types%3Aanother-derived-identity", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:leaf-list-with-identity-key": ["example:derived-identity"]})") == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "protocol",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-path": "/example:leaf-list-with-identity-key[.='example:derived-identity']",
|
||||
+ "error-message": "List key mismatch between URI path ('example-types:another-derived-identity') and data ('example:derived-identity')."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ // value in the URI and in the data have the same canonical form
|
||||
+ EXPECT_CHANGE(CREATED("/example:tlc/decimal-list[.='1.0']", "1.0"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/decimal-list=1.00", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:decimal-list": ["1.0"]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ // nothing is changed, still the same value
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/decimal-list=1.000", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:decimal-list": ["1"]})") == Response{204, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ // different value
|
||||
+ EXPECT_CHANGE(CREATED("/example:tlc/decimal-list[.='1.01']", "1.01"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/decimal-list=1.010", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:decimal-list": ["1.01"]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+ }
|
||||
+
|
||||
SECTION("Modify a leaf in a list entry")
|
||||
{
|
||||
EXPECT_CHANGE(MODIFIED("/example:tlc/list[name='libyang']/choice1", "restconf"));
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index 5977afc..7320c3c 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -293,6 +293,7 @@ TEST_CASE("URI path parser")
|
||||
{"/restconf/data/example:tlc/list=eth0/choice1", "/example:tlc/list[name='eth0']/choice1", std::nullopt},
|
||||
{"/restconf/data/example:tlc/list=eth0/choice2", "/example:tlc/list[name='eth0']/choice2", std::nullopt},
|
||||
{"/restconf/data/example:tlc/list=eth0/collection=val", "/example:tlc/list[name='eth0']/collection[.='val']", std::nullopt},
|
||||
+ {"/restconf/data/example:list-with-identity-key=example-types%3Aanother-derived-identity,aaa", "/example:list-with-identity-key[type='example-types:another-derived-identity'][name='aaa']", std::nullopt},
|
||||
{"/restconf/data/example:tlc/status", "/example:tlc/status", std::nullopt},
|
||||
// container example:a has a container b inserted locally and also via an augment. Check that we return the correct one
|
||||
{"/restconf/data/example:a/b", "/example:a/b", std::nullopt},
|
||||
@@ -327,6 +328,7 @@ TEST_CASE("URI path parser")
|
||||
{"/restconf/data/example:tlc/status", "/example:tlc", {{"example", "status"}}},
|
||||
{"/restconf/data/example:a/example-augment:b/c", "/example:a/example-augment:b", {{"example-augment", "c"}}},
|
||||
{"/restconf/ds/ietf-datastores:startup/example:a/example-augment:b/c", "/example:a/example-augment:b", {{"example-augment", "c"}}},
|
||||
+ {"/restconf/data/example:list-with-identity-key=example-types%3Aanother-derived-identity,aaa", "", {{"example", "list-with-identity-key"}, {"example-types:another-derived-identity", "aaa"}}},
|
||||
}) {
|
||||
CAPTURE(httpMethod);
|
||||
CAPTURE(expectedRequestType);
|
||||
diff --git a/tests/yang/example-types.yang b/tests/yang/example-types.yang
|
||||
new file mode 100644
|
||||
index 0000000..5bc2fb0
|
||||
--- /dev/null
|
||||
+++ b/tests/yang/example-types.yang
|
||||
@@ -0,0 +1,13 @@
|
||||
+module example-types {
|
||||
+ yang-version 1.1;
|
||||
+ namespace "http://example.tld/example-types";
|
||||
+ prefix ex-types;
|
||||
+
|
||||
+ import example {
|
||||
+ prefix ex;
|
||||
+ }
|
||||
+
|
||||
+ identity another-derived-identity {
|
||||
+ base ex:base-identity;
|
||||
+ }
|
||||
+}
|
||||
diff --git a/tests/yang/example.yang b/tests/yang/example.yang
|
||||
index df1301f..c46273c 100644
|
||||
--- a/tests/yang/example.yang
|
||||
+++ b/tests/yang/example.yang
|
||||
@@ -6,6 +6,13 @@ module example {
|
||||
feature f1 { }
|
||||
feature f2 { }
|
||||
|
||||
+ identity base-identity {
|
||||
+ }
|
||||
+
|
||||
+ identity derived-identity {
|
||||
+ base base-identity;
|
||||
+ }
|
||||
+
|
||||
leaf top-level-leaf { type string; }
|
||||
leaf top-level-leaf2 { type string; default "x"; }
|
||||
|
||||
@@ -50,6 +57,11 @@ module example {
|
||||
config false;
|
||||
leaf name { type string; }
|
||||
}
|
||||
+ leaf-list decimal-list {
|
||||
+ type decimal64 {
|
||||
+ fraction-digits 2;
|
||||
+ }
|
||||
+ }
|
||||
leaf status {
|
||||
type enumeration {
|
||||
enum on { }
|
||||
@@ -109,6 +121,19 @@ module example {
|
||||
}
|
||||
}
|
||||
|
||||
+ list list-with-identity-key {
|
||||
+ key "type name";
|
||||
+ leaf type {
|
||||
+ type identityref { base base-identity; }
|
||||
+ }
|
||||
+ leaf name { type string; }
|
||||
+ leaf text { type string; }
|
||||
+ }
|
||||
+
|
||||
+ leaf-list leaf-list-with-identity-key {
|
||||
+ type identityref { base base-identity; }
|
||||
+ }
|
||||
+
|
||||
rpc test-rpc {
|
||||
input {
|
||||
leaf i {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
From 5c8a139d04d9b3dfb28b8c0e1a4aa7b945a1e8fc Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 30 Jun 2025 15:38:02 +0200
|
||||
Subject: [PATCH 05/13] restconf: refactor server stop
|
||||
Organization: Wires
|
||||
|
||||
In 94b24795 ("server: simplify listening/stopping") we needed to call
|
||||
server->stop() through event loop and it was proposed to use the
|
||||
approach introduced in [1], i.e., construct a timer, then cancel it
|
||||
right away which would invoke the timer handler.
|
||||
I find that quite complicated, why not just simply call post()? [2]
|
||||
|
||||
(Also, by looking at the http2::asio_http2::server::http2::stop(), it
|
||||
does not seem like it should be called from *every* io_service.)
|
||||
|
||||
[1] https://stackoverflow.com/questions/17005258/why-does-boost-asio-not-support-an-event-based-interface/17029022#17029022
|
||||
[2] https://www.boost.org/doc/libs/1_85_0/doc/html/boost_asio/reference/post.html
|
||||
|
||||
Change-Id: I2f33c38a78dce4081a03326c9a9bb25817fc9d2f
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 13 +++++--------
|
||||
1 file changed, 5 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 2f495f9..bd2ce0d 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -837,14 +837,11 @@ Server::~Server()
|
||||
void Server::stop()
|
||||
{
|
||||
// notification to stop has to go through the asio io_context
|
||||
- for (const auto& service : server->io_services()) {
|
||||
- boost::asio::deadline_timer t{*service, boost::posix_time::pos_infin};
|
||||
- t.async_wait([server = this->server.get()](const boost::system::error_code&) {
|
||||
- spdlog::trace("Stoping HTTP/2 server");
|
||||
- server->stop();
|
||||
- });
|
||||
- t.cancel();
|
||||
- }
|
||||
+ boost::asio::post(*server->io_services().front(), [server = this->server.get()]() {
|
||||
+ spdlog::trace("Stoping HTTP/2 server");
|
||||
+ server->stop();
|
||||
+ });
|
||||
+
|
||||
shutdownRequested();
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
From 086cfe1847e406df90852dc3cb52ece3d0cbe22b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 12 May 2025 14:56:54 +0200
|
||||
Subject: [PATCH 06/13] http: send keep-alive pings from EventStream
|
||||
Organization: Wires
|
||||
|
||||
The nghttp2-asio server disconnects client after 60 seconds of no
|
||||
activity in the stream [1]. In our use-case I can imagine it can be
|
||||
quite common that no data will flow for a long period of time. If no
|
||||
data are updated (i.e., no notifications, or no YANG data changes in
|
||||
sysrepo when we have YANG push), then nothing flows forth and back.
|
||||
|
||||
The 60 second value is configurable [2], but I am not sure about the
|
||||
value that should be used. 5 minutes? hours? days?
|
||||
|
||||
Let's approach this differently. We can instead send a comment into the
|
||||
stream every few seconds which will make sure that the connection will
|
||||
not be dropped by nghttp2-asio. This approach is described in the
|
||||
HTML Living Standard [3].
|
||||
For now, let's resort to sending a ping-alive every 55 seconds and
|
||||
hardcode 60 seconds no activity timeout.
|
||||
|
||||
I have verified that using this method a curl client keeps connected
|
||||
to the server when the server sends these "keep-alive comments".
|
||||
|
||||
[1] https://github.com/nghttp2/nghttp2/issues/555
|
||||
[2] https://github.com/nghttp2/nghttp2-asio/blob/e877868abe06a83ed0a6ac6e245c07f6f20866b5/lib/asio_server_http2_impl.h#L51
|
||||
[3] https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes
|
||||
|
||||
Change-Id: I57e510d0b61ac7ed032c582779780c64768b7d53
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/clock.cpp | 4 +-
|
||||
src/http/EventStream.cpp | 44 +++++++++++++--
|
||||
src/http/EventStream.h | 13 ++++-
|
||||
src/restconf/NotificationStream.cpp | 3 +-
|
||||
src/restconf/NotificationStream.h | 1 +
|
||||
src/restconf/Server.cpp | 21 +++++--
|
||||
src/restconf/Server.h | 6 +-
|
||||
tests/restconf-eventstream.cpp | 85 +++++++++++++++++++++--------
|
||||
8 files changed, 139 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||
index c85be19..24e9efb 100644
|
||||
--- a/src/clock.cpp
|
||||
+++ b/src/clock.cpp
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
+constexpr auto keepAlivePingInterval = std::chrono::seconds{55};
|
||||
+
|
||||
int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
@@ -32,7 +34,7 @@ int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||
server.num_threads(4);
|
||||
|
||||
server.handle("/events", [&shutdown, &sig](const auto& req, const auto& res) {
|
||||
- auto client = std::make_shared<rousette::http::EventStream>(req, res, shutdown, sig);
|
||||
+ auto client = std::make_shared<rousette::http::EventStream>(req, res, shutdown, sig, keepAlivePingInterval);
|
||||
client->activate();
|
||||
});
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 77c282d..82f8b38 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -17,17 +17,26 @@ using namespace nghttp2::asio_http2;
|
||||
|
||||
namespace rousette::http {
|
||||
|
||||
+constexpr auto FIELD_DATA = "data";
|
||||
+
|
||||
/** @short After constructing, make sure to call activate() immediately. */
|
||||
-EventStream::EventStream(const server::request& req, const server::response& res, Termination& termination, EventSignal& signal, const std::optional<std::string>& initialEvent)
|
||||
+EventStream::EventStream(const server::request& req,
|
||||
+ const server::response& res,
|
||||
+ Termination& termination,
|
||||
+ EventSignal& signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::optional<std::string>& initialEvent)
|
||||
: res{res}
|
||||
+ , ping{res.io_service()}
|
||||
, peer{peer_from_request(req)}
|
||||
+ , m_keepAlivePingInterval(keepAlivePingInterval)
|
||||
{
|
||||
if (initialEvent) {
|
||||
- enqueue(*initialEvent);
|
||||
+ enqueue(FIELD_DATA, *initialEvent);
|
||||
}
|
||||
|
||||
eventSub = signal.connect([this](const auto& msg) {
|
||||
- enqueue(msg);
|
||||
+ enqueue(FIELD_DATA, msg);
|
||||
});
|
||||
|
||||
terminateSub = termination.connect([this]() {
|
||||
@@ -56,6 +65,8 @@ shared_from_this() throws bad_weak_ptr, so we need a two-phase construction.
|
||||
*/
|
||||
void EventStream::activate()
|
||||
{
|
||||
+ start_ping();
|
||||
+
|
||||
auto client = shared_from_this();
|
||||
res.write_head(200, {
|
||||
{"content-type", {"text/event-stream", false}},
|
||||
@@ -65,6 +76,7 @@ void EventStream::activate()
|
||||
res.on_close([client](const auto ec) {
|
||||
spdlog::debug("{}: closed ({})", client->peer, nghttp2_http2_strerror(ec));
|
||||
std::lock_guard lock{client->mtx};
|
||||
+ client->ping.cancel();
|
||||
client->eventSub.disconnect();
|
||||
client->terminateSub.disconnect();
|
||||
client->state = Closed;
|
||||
@@ -113,14 +125,15 @@ ssize_t EventStream::process(uint8_t* destination, std::size_t len, uint32_t* da
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
-void EventStream::enqueue(const std::string& what)
|
||||
+void EventStream::enqueue(const std::string& fieldName, const std::string& what)
|
||||
{
|
||||
std::string buf;
|
||||
buf.reserve(what.size());
|
||||
const std::regex newline{"\n"};
|
||||
for (auto it = std::sregex_token_iterator{what.begin(), what.end(), newline, -1};
|
||||
it != std::sregex_token_iterator{}; ++it) {
|
||||
- buf += "data: ";
|
||||
+ buf += fieldName;
|
||||
+ buf += ": ";
|
||||
buf += *it;
|
||||
buf += '\n';
|
||||
}
|
||||
@@ -139,4 +152,25 @@ void EventStream::enqueue(const std::string& what)
|
||||
state = HasEvents;
|
||||
boost::asio::post(res.io_service(), [&res = this->res]() { res.resume(); });
|
||||
}
|
||||
+
|
||||
+void EventStream::start_ping()
|
||||
+{
|
||||
+ ping.expires_from_now(boost::posix_time::seconds(m_keepAlivePingInterval.count()));
|
||||
+ ping.async_wait([maybeClient = weak_from_this()](const boost::system::error_code& ec) {
|
||||
+ auto client = maybeClient.lock();
|
||||
+ if (!client) {
|
||||
+ spdlog::trace("ping: client already gone");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (ec == boost::asio::error::operation_aborted) {
|
||||
+ spdlog::trace("{}: ping scheduler cancelled", client->peer);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ client->enqueue("", "\n");
|
||||
+ spdlog::trace("{}: keep-alive ping enqueued", client->peer);
|
||||
+ client->start_ping();
|
||||
+ });
|
||||
+}
|
||||
}
|
||||
diff --git a/src/http/EventStream.h b/src/http/EventStream.h
|
||||
index b427fb3..4b3578a 100644
|
||||
--- a/src/http/EventStream.h
|
||||
+++ b/src/http/EventStream.h
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
+#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
@@ -30,7 +31,12 @@ public:
|
||||
using EventSignal = boost::signals2::signal<void(const std::string& message)>;
|
||||
using Termination = boost::signals2::signal<void()>;
|
||||
|
||||
- EventStream(const nghttp2::asio_http2::server::request& req, const nghttp2::asio_http2::server::response& res, Termination& terminate, EventSignal& signal, const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
+ EventStream(const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ Termination& terminate,
|
||||
+ EventSignal& signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
void activate();
|
||||
|
||||
private:
|
||||
@@ -43,13 +49,16 @@ private:
|
||||
};
|
||||
|
||||
State state = WaitingForEvents;
|
||||
+ boost::asio::deadline_timer ping;
|
||||
std::list<std::string> queue;
|
||||
mutable std::mutex mtx; // for `state` and `queue`
|
||||
boost::signals2::scoped_connection eventSub, terminateSub;
|
||||
const std::string peer;
|
||||
+ const std::chrono::seconds m_keepAlivePingInterval;
|
||||
|
||||
size_t send_chunk(uint8_t* destination, std::size_t len, uint32_t* data_flags);
|
||||
ssize_t process(uint8_t* destination, std::size_t len, uint32_t* data_flags);
|
||||
- void enqueue(const std::string& what);
|
||||
+ void enqueue(const std::string& fieldName, const std::string& what);
|
||||
+ void start_ping();
|
||||
};
|
||||
}
|
||||
diff --git a/src/restconf/NotificationStream.cpp b/src/restconf/NotificationStream.cpp
|
||||
index d0ba938..5017f3e 100644
|
||||
--- a/src/restconf/NotificationStream.cpp
|
||||
+++ b/src/restconf/NotificationStream.cpp
|
||||
@@ -89,12 +89,13 @@ NotificationStream::NotificationStream(
|
||||
const nghttp2::asio_http2::server::response& res,
|
||||
rousette::http::EventStream::Termination& termination,
|
||||
std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
sysrepo::Session session,
|
||||
libyang::DataFormat dataFormat,
|
||||
const std::optional<std::string>& filter,
|
||||
const std::optional<sysrepo::NotificationTimeStamp>& startTime,
|
||||
const std::optional<sysrepo::NotificationTimeStamp>& stopTime)
|
||||
- : EventStream(req, res, termination, *signal)
|
||||
+ : EventStream(req, res, termination, *signal, keepAlivePingInterval)
|
||||
, m_notificationSignal(signal)
|
||||
, m_session(std::move(session))
|
||||
, m_dataFormat(dataFormat)
|
||||
diff --git a/src/restconf/NotificationStream.h b/src/restconf/NotificationStream.h
|
||||
index 46f8416..daa79d6 100644
|
||||
--- a/src/restconf/NotificationStream.h
|
||||
+++ b/src/restconf/NotificationStream.h
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
const nghttp2::asio_http2::server::response& res,
|
||||
rousette::http::EventStream::Termination& termination,
|
||||
std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
sysrepo::Session sess,
|
||||
libyang::DataFormat dataFormat,
|
||||
const std::optional<std::string>& filter,
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index bd2ce0d..31b3d0f 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -864,13 +864,14 @@ std::vector<std::shared_ptr<boost::asio::io_context>> Server::io_services() cons
|
||||
return server->io_services();
|
||||
}
|
||||
|
||||
-Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout)
|
||||
+Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout, const std::chrono::seconds keepAlivePingInterval)
|
||||
: m_monitoringSession(conn.sessionStart(sysrepo::Datastore::Operational))
|
||||
, nacm(conn)
|
||||
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||
{
|
||||
server->num_threads(1); // we only use one thread for the server, so we can call join() right away
|
||||
+ server->read_timeout(boost::posix_time::seconds{60}); // terminate connection after 60 seconds of inactivity (this is explicitly setting the default value)
|
||||
|
||||
for (const auto& [module, version, features] : {
|
||||
std::tuple<std::string, std::string, std::vector<std::string>>{"ietf-restconf", "2017-01-26", {}},
|
||||
@@ -930,14 +931,14 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
res.end("<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'><Link rel='restconf' href='"s + restconfRoot + "'></XRD>"s);
|
||||
});
|
||||
|
||||
- server->handle("/telemetry/optics", [this](const auto& req, const auto& res) {
|
||||
+ server->handle("/telemetry/optics", [this, keepAlivePingInterval](const auto& req, const auto& res) {
|
||||
logRequest(req);
|
||||
|
||||
- auto client = std::make_shared<http::EventStream>(req, res, shutdownRequested, opticsChange, as_restconf_push_update(dwdmEvents->currentData(), std::chrono::system_clock::now()));
|
||||
+ auto client = std::make_shared<http::EventStream>(req, res, shutdownRequested, opticsChange, keepAlivePingInterval, as_restconf_push_update(dwdmEvents->currentData(), std::chrono::system_clock::now()));
|
||||
client->activate();
|
||||
});
|
||||
|
||||
- server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
+ server->handle(netconfStreamRoot, [this, conn, keepAlivePingInterval](const auto& req, const auto& res) mutable {
|
||||
logRequest(req);
|
||||
|
||||
std::optional<std::string> xpathFilter;
|
||||
@@ -970,7 +971,17 @@ 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, shutdownRequested, std::make_shared<rousette::http::EventStream::EventSignal>(), sess, streamRequest.type.encoding, xpathFilter, startTime, stopTime);
|
||||
+ auto client = std::make_shared<NotificationStream>(
|
||||
+ req,
|
||||
+ res,
|
||||
+ shutdownRequested,
|
||||
+ std::make_shared<rousette::http::EventStream::EventSignal>(),
|
||||
+ keepAlivePingInterval,
|
||||
+ sess,
|
||||
+ streamRequest.type.encoding,
|
||||
+ xpathFilter,
|
||||
+ startTime,
|
||||
+ stopTime);
|
||||
client->activate();
|
||||
} catch (const auth::Error& e) {
|
||||
processAuthError(req, res, e, [&res]() {
|
||||
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||
index 112c943..0720c8d 100644
|
||||
--- a/src/restconf/Server.h
|
||||
+++ b/src/restconf/Server.h
|
||||
@@ -28,7 +28,11 @@ std::optional<std::string> as_subtree_path(const std::string& path);
|
||||
/** @short A RESTCONF-ish server */
|
||||
class Server {
|
||||
public:
|
||||
- explicit Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
+ explicit Server(sysrepo::Connection conn,
|
||||
+ const std::string& address,
|
||||
+ const std::string& port,
|
||||
+ const std::chrono::milliseconds timeout = std::chrono::milliseconds{0},
|
||||
+ const std::chrono::seconds keepAlivePingInterval = std::chrono::seconds{55});
|
||||
~Server();
|
||||
void join();
|
||||
void stop();
|
||||
diff --git a/tests/restconf-eventstream.cpp b/tests/restconf-eventstream.cpp
|
||||
index 3d7fc15..ad1040d 100644
|
||||
--- a/tests/restconf-eventstream.cpp
|
||||
+++ b/tests/restconf-eventstream.cpp
|
||||
@@ -21,9 +21,9 @@ static const auto SERVER_PORT = "10091";
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
-TEST_CASE("Termination on server shutdown")
|
||||
+TEST_CASE("Event stream tests")
|
||||
{
|
||||
- trompeloeil::sequence seqMod1;
|
||||
+ trompeloeil::sequence seq1, seq2;
|
||||
sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
|
||||
@@ -34,37 +34,76 @@ TEST_CASE("Termination on server shutdown")
|
||||
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
- auto server = std::make_unique<rousette::restconf::Server>(srConn, SERVER_ADDRESS, SERVER_PORT);
|
||||
setupRealNacm(srSess);
|
||||
|
||||
+ const std::string notification(R"({"example:eventA":{"message":"blabla","progress":11}})");
|
||||
RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
|
||||
- const std::string notification(R"({"example:eventA":{"message":"blabla","progress":11}})");
|
||||
- EXPECT_NOTIFICATION(notification, seqMod1);
|
||||
- EXPECT_NOTIFICATION(notification, seqMod1);
|
||||
- EXPECT_NOTIFICATION(notification, seqMod1);
|
||||
+ SECTION("Termination on server shutdown")
|
||||
+ {
|
||||
+ auto server = std::make_unique<rousette::restconf::Server>(srConn, SERVER_ADDRESS, SERVER_PORT);
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(notification, seq1);
|
||||
+ EXPECT_NOTIFICATION(notification, seq1);
|
||||
+ EXPECT_NOTIFICATION(notification, seq1);
|
||||
+
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
+ auto notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ waitForCompletionAndBitMore(seq1);
|
||||
+
|
||||
+ auto beforeShutdown = std::chrono::system_clock::now();
|
||||
+ server.reset();
|
||||
+ auto shutdownDuration = std::chrono::system_clock::now() - beforeShutdown;
|
||||
+ REQUIRE(shutdownDuration < 5s);
|
||||
+ }));
|
||||
+
|
||||
+ SSEClient client(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, "/streams/NETCONF/JSON", std::map<std::string, std::string>{AUTH_ROOT});
|
||||
+
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Keep-alive pings")
|
||||
+ {
|
||||
+ constexpr auto pingInterval = 1s;
|
||||
|
||||
- auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
- auto ctx = notifSession.getContext();
|
||||
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
+ auto server = std::make_unique<rousette::restconf::Server>(srConn, SERVER_ADDRESS, SERVER_PORT, std::chrono::milliseconds{0}, pingInterval);
|
||||
|
||||
- PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
- auto notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
auto ctx = notifSession.getContext();
|
||||
|
||||
- WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
- SEND_NOTIFICATION(notification);
|
||||
- SEND_NOTIFICATION(notification);
|
||||
- SEND_NOTIFICATION(notification);
|
||||
- waitForCompletionAndBitMore(seqMod1);
|
||||
+ EXPECT_NOTIFICATION(notification, seq1);
|
||||
+ expectations.emplace_back(NAMED_REQUIRE_CALL(netconfWatcher, comment(": ")).IN_SEQUENCE(seq2).TIMES(AT_LEAST(1)));
|
||||
|
||||
- auto beforeShutdown = std::chrono::system_clock::now();
|
||||
- server.reset();
|
||||
- auto shutdownDuration = std::chrono::system_clock::now() - beforeShutdown;
|
||||
- REQUIRE(shutdownDuration < 5s);
|
||||
- }));
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
+ auto notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+ SEND_NOTIFICATION(notification);
|
||||
+ std::this_thread::sleep_for(3s); // Wait for the server to send at least one keep-alive ping
|
||||
+ server.reset();
|
||||
+ }));
|
||||
|
||||
- SSEClient client(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, "/streams/NETCONF/JSON", std::map<std::string, std::string>{AUTH_ROOT});
|
||||
+ SSEClient client(
|
||||
+ io,
|
||||
+ SERVER_ADDRESS,
|
||||
+ SERVER_PORT,
|
||||
+ requestSent,
|
||||
+ netconfWatcher,
|
||||
+ "/streams/NETCONF/JSON",
|
||||
+ std::map<std::string, std::string>{AUTH_ROOT},
|
||||
+ boost::posix_time::seconds{5},
|
||||
+ SSEClient::ReportIgnoredLines::Yes);
|
||||
|
||||
- RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+ }
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
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/44] tests: test querying lists with union keys
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
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.
|
||||
|
||||
By the way, enum values are not supposed to have a namespace prefix,
|
||||
good to know [1].
|
||||
|
||||
[1] https://www.rfc-editor.org/rfc/rfc7951#section-6.4
|
||||
|
||||
Change-Id: I5a70f18117bb453330b4bb2ce0d2fb47d35b6ea6
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-reading.cpp | 53 ++++++++++++++++++++++++-----
|
||||
tests/restconf-writing.cpp | 68 ++++++++++++++++++++++++++++----------
|
||||
tests/uri-parser.cpp | 2 +-
|
||||
tests/yang/example.yang | 26 +++++++++++++--
|
||||
4 files changed, 120 insertions(+), 29 deletions(-)
|
||||
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index 96c38ab..2ded3f0 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -289,14 +289,16 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
|
||||
- srSess.setItem("/example:list-with-identity-key[type='example:derived-identity'][name='name']", std::nullopt);
|
||||
- srSess.setItem("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']", std::nullopt);
|
||||
+ srSess.setItem("/example:list-with-union-keys[type='example:derived-identity'][name='name']", std::nullopt);
|
||||
+ srSess.setItem("/example:list-with-union-keys[type='example-types:another-derived-identity'][name='name']", std::nullopt);
|
||||
+ srSess.setItem("/example:list-with-union-keys[type='fiii'][name='name']", std::nullopt);
|
||||
+ srSess.setItem("/example:list-with-union-keys[type='zero'][name='name']", std::nullopt); // enum value
|
||||
srSess.setItem("/example:tlc/decimal-list[.='1.00']", std::nullopt);
|
||||
srSess.applyChanges();
|
||||
|
||||
// dealing with keys which can have prefixes (YANG identities)
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=derived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
- "example:list-with-identity-key": [
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=derived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-union-keys": [
|
||||
{
|
||||
"type": "derived-identity",
|
||||
"name": "name"
|
||||
@@ -304,8 +306,8 @@ TEST_CASE("reading data")
|
||||
]
|
||||
}
|
||||
)"});
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example%3Aderived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
- "example:list-with-identity-key": [
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example%3Aderived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-union-keys": [
|
||||
{
|
||||
"type": "derived-identity",
|
||||
"name": "name"
|
||||
@@ -315,7 +317,7 @@ TEST_CASE("reading data")
|
||||
)"});
|
||||
|
||||
// an identity from another module must be namespace-qualified
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=another-derived-identity,name", {AUTH_ROOT}) == Response{404, jsonHeaders, R"({
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=another-derived-identity,name", {AUTH_ROOT}) == Response{404, jsonHeaders, R"({
|
||||
"ietf-restconf:errors": {
|
||||
"error": [
|
||||
{
|
||||
@@ -328,8 +330,8 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example-types%3Aanother-derived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
- "example:list-with-identity-key": [
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example-types%3Aanother-derived-identity,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-union-keys": [
|
||||
{
|
||||
"type": "example-types:another-derived-identity",
|
||||
"name": "name"
|
||||
@@ -346,6 +348,39 @@ TEST_CASE("reading data")
|
||||
]
|
||||
}
|
||||
}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=zero,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-union-keys": [
|
||||
+ {
|
||||
+ "type": "zero",
|
||||
+ "name": "name"
|
||||
+ }
|
||||
+ ]
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example%3Azero,name", {AUTH_ROOT}) == Response{404, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-message": "No data from sysrepo."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:list-with-union-keys=fiii,name", {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:list-with-union-keys": [
|
||||
+ {
|
||||
+ "type": "fiii",
|
||||
+ "name": "name"
|
||||
+ }
|
||||
+ ]
|
||||
+}
|
||||
)"});
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index 8418554..c1a9515 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -392,46 +392,69 @@ TEST_CASE("writing data")
|
||||
SECTION("Test canonicalization of keys")
|
||||
{
|
||||
EXPECT_CHANGE(
|
||||
- CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']", std::nullopt),
|
||||
- CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/type", "example:derived-identity"),
|
||||
- CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/name", "name"),
|
||||
- CREATED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/text", "blabla"));
|
||||
- REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+ CREATED("/example:list-with-union-keys[type='example:derived-identity'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:derived-identity'][name='name']/type", "example:derived-identity"),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:derived-identity'][name='name']/name", "name"),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:derived-identity'][name='name']/text", "blabla"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
|
||||
// prefixed in the URI, not prefixed in the data
|
||||
EXPECT_CHANGE(
|
||||
- MODIFIED("/example:list-with-identity-key[type='example:derived-identity'][name='name']/text", "hehe"));
|
||||
- REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example%3Aderived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "derived-identity", "text": "hehe"}]}]})") == Response{204, noContentTypeHeaders, ""});
|
||||
+ MODIFIED("/example:list-with-union-keys[type='example:derived-identity'][name='name']/text", "hehe"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example%3Aderived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "derived-identity", "text": "hehe"}]}]})") == Response{204, noContentTypeHeaders, ""});
|
||||
|
||||
+ // 'another-derived-identity' comes from a different module than the list itself, so this parses as string
|
||||
+ EXPECT_CHANGE(
|
||||
+ CREATED("/example:list-with-union-keys[type='another-derived-identity'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-union-keys[type='another-derived-identity'][name='name']/type", "another-derived-identity"),
|
||||
+ CREATED("/example:list-with-union-keys[type='another-derived-identity'][name='name']/name", "name"),
|
||||
+ CREATED("/example:list-with-union-keys[type='another-derived-identity'][name='name']/text", "blabla"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=another-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "another-derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ EXPECT_CHANGE(
|
||||
+ CREATED("/example:list-with-union-keys[type='example-types:another-derived-identity'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-union-keys[type='example-types:another-derived-identity'][name='name']/type", "example-types:another-derived-identity"),
|
||||
+ CREATED("/example:list-with-union-keys[type='example-types:another-derived-identity'][name='name']/name", "name"),
|
||||
+ CREATED("/example:list-with-union-keys[type='example-types:another-derived-identity'][name='name']/text", "blabla"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example-types%3Aanother-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "example-types:another-derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
|
||||
- REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=another-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "another-derived-identity", "text": "blabla"}]}]})") == Response{400, jsonHeaders, R"({
|
||||
+ // missing namespace in the data
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example-types%3Aanother-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "another-derived-identity", "text": "blabla"}]}]})") == Response{400, jsonHeaders, R"({
|
||||
"ietf-restconf:errors": {
|
||||
"error": [
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
- "error-message": "Validation failure: Can't parse data: LY_EVALID"
|
||||
+ "error-path": "/example:list-with-union-keys[type='another-derived-identity'][name='name']/type",
|
||||
+ "error-message": "List key mismatch between URI path ('example-types:another-derived-identity') and data ('another-derived-identity')."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
)"});
|
||||
|
||||
+ // zero is enum value
|
||||
EXPECT_CHANGE(
|
||||
- CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']", std::nullopt),
|
||||
- CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']/type", "example-types:another-derived-identity"),
|
||||
- CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']/name", "name"),
|
||||
- CREATED("/example:list-with-identity-key[type='example-types:another-derived-identity'][name='name']/text", "blabla"));
|
||||
- REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example-types%3Aanother-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "example-types:another-derived-identity", "text": "blabla"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+ CREATED("/example:list-with-union-keys[type='zero'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-union-keys[type='zero'][name='name']/type", "zero"),
|
||||
+ CREATED("/example:list-with-union-keys[type='zero'][name='name']/name", "name"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=zero,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "zero"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
|
||||
- // missing namespace in the data
|
||||
- REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-identity-key=example-types%3Aanother-derived-identity,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-identity-key": [{"name": "name", "type": "another-derived-identity", "text": "blabla"}]}]})") == Response{400, jsonHeaders, R"({
|
||||
+ // example:zero is string, enum values are not namespace-prefixed
|
||||
+ EXPECT_CHANGE(
|
||||
+ CREATED("/example:list-with-union-keys[type='example:zero'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:zero'][name='name']/type", "example:zero"),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:zero'][name='name']/name", "name"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example%3Azero,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "example:zero"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=zero,name", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list-with-union-keys": [{"name": "name", "type": "example:zero"}]}]})") == Response{400, jsonHeaders, R"({
|
||||
"ietf-restconf:errors": {
|
||||
"error": [
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
- "error-message": "Validation failure: Can't parse data: LY_EVALID"
|
||||
+ "error-path": "/example:list-with-union-keys[type='example:zero'][name='name']/type",
|
||||
+ "error-message": "List key mismatch between URI path ('zero') and data ('example:zero')."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -459,6 +482,17 @@ TEST_CASE("writing data")
|
||||
}
|
||||
)"});
|
||||
|
||||
+ EXPECT_CHANGE(CREATED("/example:fruit-list[.='example:apple']", "example:apple"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:fruit-list=example%3Aapple", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:fruit-list": ["apple"]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
+ // leafref
|
||||
+ EXPECT_CHANGE(
|
||||
+ CREATED("/example:list-with-union-keys[type='example:apple'][name='name']", std::nullopt),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:apple'][name='name']/type", "example:apple"),
|
||||
+ CREATED("/example:list-with-union-keys[type='example:apple'][name='name']/name", "name"));
|
||||
+ REQUIRE(put(RESTCONF_DATA_ROOT "/example:list-with-union-keys=example%3Aapple,name", {AUTH_ROOT, CONTENT_TYPE_JSON},
|
||||
+ R"({"example:list-with-union-keys": [{"name": "name", "type": "apple"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
+
|
||||
// value in the URI and in the data have the same canonical form
|
||||
EXPECT_CHANGE(CREATED("/example:tlc/decimal-list[.='1.0']", "1.0"));
|
||||
REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/decimal-list=1.00", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:decimal-list": ["1.0"]})") == Response{201, noContentTypeHeaders, ""});
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index 7320c3c..000fe5d 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -293,7 +293,7 @@ TEST_CASE("URI path parser")
|
||||
{"/restconf/data/example:tlc/list=eth0/choice1", "/example:tlc/list[name='eth0']/choice1", std::nullopt},
|
||||
{"/restconf/data/example:tlc/list=eth0/choice2", "/example:tlc/list[name='eth0']/choice2", std::nullopt},
|
||||
{"/restconf/data/example:tlc/list=eth0/collection=val", "/example:tlc/list[name='eth0']/collection[.='val']", std::nullopt},
|
||||
- {"/restconf/data/example:list-with-identity-key=example-types%3Aanother-derived-identity,aaa", "/example:list-with-identity-key[type='example-types:another-derived-identity'][name='aaa']", std::nullopt},
|
||||
+ {"/restconf/data/example:list-with-union-keys=example-types%3Aanother-derived-identity,aaa", "/example:list-with-union-keys[type='example-types:another-derived-identity'][name='aaa']", std::nullopt},
|
||||
{"/restconf/data/example:tlc/status", "/example:tlc/status", std::nullopt},
|
||||
// container example:a has a container b inserted locally and also via an augment. Check that we return the correct one
|
||||
{"/restconf/data/example:a/b", "/example:a/b", std::nullopt},
|
||||
diff --git a/tests/yang/example.yang b/tests/yang/example.yang
|
||||
index c46273c..75cd7a6 100644
|
||||
--- a/tests/yang/example.yang
|
||||
+++ b/tests/yang/example.yang
|
||||
@@ -13,6 +13,11 @@ module example {
|
||||
base base-identity;
|
||||
}
|
||||
|
||||
+ identity fruit { }
|
||||
+ identity apple {
|
||||
+ base fruit;
|
||||
+ }
|
||||
+
|
||||
leaf top-level-leaf { type string; }
|
||||
leaf top-level-leaf2 { type string; default "x"; }
|
||||
|
||||
@@ -121,10 +126,23 @@ module example {
|
||||
}
|
||||
}
|
||||
|
||||
- list list-with-identity-key {
|
||||
+ list list-with-union-keys {
|
||||
key "type name";
|
||||
leaf type {
|
||||
- type identityref { base base-identity; }
|
||||
+ type union {
|
||||
+ type identityref {
|
||||
+ base base-identity;
|
||||
+ }
|
||||
+ type enumeration {
|
||||
+ enum zero;
|
||||
+ enum one;
|
||||
+ }
|
||||
+ type leafref {
|
||||
+ require-instance true;
|
||||
+ path "/fruit-list";
|
||||
+ }
|
||||
+ type string;
|
||||
+ }
|
||||
}
|
||||
leaf name { type string; }
|
||||
leaf text { type string; }
|
||||
@@ -134,6 +152,10 @@ module example {
|
||||
type identityref { base base-identity; }
|
||||
}
|
||||
|
||||
+ leaf-list fruit-list {
|
||||
+ type identityref { base fruit; }
|
||||
+ }
|
||||
+
|
||||
rpc test-rpc {
|
||||
input {
|
||||
leaf i {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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/44] error handling in sysrepo has changed
|
||||
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/+/7969
|
||||
Change-Id: Id028806ed49114cba4c55e2874bcf3fc98308bdc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-rpc.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp
|
||||
index 4f66f10..9bc1dbc 100644
|
||||
--- a/tests/restconf-rpc.cpp
|
||||
+++ b/tests/restconf-rpc.cpp
|
||||
@@ -301,7 +301,7 @@ TEST_CASE("invoking actions and rpcs")
|
||||
{
|
||||
"error-type": "application",
|
||||
"error-tag": "operation-failed",
|
||||
- "error-message": "Internal server error due to sysrepo exception: Couldn't send RPC: SR_ERR_CALLBACK_FAILED\u000A Operation failed (SR_ERR_OPERATION_FAILED)\u000A User callback failed. (SR_ERR_CALLBACK_FAILED)"
|
||||
+ "error-message": "Internal server error due to sysrepo exception: Couldn't send RPC: SR_ERR_OPERATION_FAILED\u000A Operation failed (SR_ERR_OPERATION_FAILED)"
|
||||
}
|
||||
]
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
From 06e2751e4da052195db96aaa90549fba398208bd Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 23 Jul 2025 14:27:26 +0200
|
||||
Subject: [PATCH 07/13] refactor: event streams use named constructors
|
||||
Organization: Wires
|
||||
|
||||
This commit refactors the EventStream and NotificationStream to use
|
||||
named constructors.
|
||||
Both of these classes are required to be initialized with invoking the
|
||||
constructor and then the activate method. The NotificationStream class
|
||||
also required to be passed with a newly constructed Event object.
|
||||
I find this pattern a bit confusing, so I refactored the code to use
|
||||
named constructors which perform the double initialization themselves.
|
||||
This way, the code is more readable and the intention should be clearer.
|
||||
|
||||
Change-Id: Iac96c49c20670dfe924d7c8db33328ed9c2fc9dd
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/clock.cpp | 3 +--
|
||||
src/http/EventStream.cpp | 20 ++++++++++++++++++++
|
||||
src/http/EventStream.h | 22 +++++++++++++++-------
|
||||
src/restconf/NotificationStream.cpp | 22 ++++++++++++++++++++++
|
||||
src/restconf/NotificationStream.h | 12 ++++++++++++
|
||||
src/restconf/Server.cpp | 10 ++--------
|
||||
6 files changed, 72 insertions(+), 17 deletions(-)
|
||||
|
||||
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||
index 24e9efb..aa72849 100644
|
||||
--- a/src/clock.cpp
|
||||
+++ b/src/clock.cpp
|
||||
@@ -34,8 +34,7 @@ int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||
server.num_threads(4);
|
||||
|
||||
server.handle("/events", [&shutdown, &sig](const auto& req, const auto& res) {
|
||||
- auto client = std::make_shared<rousette::http::EventStream>(req, res, shutdown, sig, keepAlivePingInterval);
|
||||
- client->activate();
|
||||
+ rousette::http::EventStream::create(req, res, shutdown, sig, keepAlivePingInterval);
|
||||
});
|
||||
|
||||
server.handle("/", [](const auto& req, const auto& resp) {
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 82f8b38..0893653 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -173,4 +173,24 @@ void EventStream::start_ping()
|
||||
client->start_ping();
|
||||
});
|
||||
}
|
||||
+
|
||||
+/** @brief Create a new EventStream instance and activate it immediately.
|
||||
+ *
|
||||
+ * The stream is created with the given parameters and activated as if the activate() method was called.
|
||||
+ * ```
|
||||
+ * auto a = make_shared<EventStream>(...);
|
||||
+ * a->activate();
|
||||
+ * ```
|
||||
+ */
|
||||
+std::shared_ptr<EventStream> EventStream::create(const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ Termination& terminate,
|
||||
+ EventSignal& signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::optional<std::string>& initialEvent)
|
||||
+{
|
||||
+ auto stream = std::shared_ptr<EventStream>(new EventStream(req, res, terminate, signal, keepAlivePingInterval, initialEvent));
|
||||
+ stream->activate();
|
||||
+ return stream;
|
||||
+}
|
||||
}
|
||||
diff --git a/src/http/EventStream.h b/src/http/EventStream.h
|
||||
index 4b3578a..ef0a002 100644
|
||||
--- a/src/http/EventStream.h
|
||||
+++ b/src/http/EventStream.h
|
||||
@@ -31,13 +31,12 @@ public:
|
||||
using EventSignal = boost::signals2::signal<void(const std::string& message)>;
|
||||
using Termination = boost::signals2::signal<void()>;
|
||||
|
||||
- EventStream(const nghttp2::asio_http2::server::request& req,
|
||||
- const nghttp2::asio_http2::server::response& res,
|
||||
- Termination& terminate,
|
||||
- EventSignal& signal,
|
||||
- const std::chrono::seconds keepAlivePingInterval,
|
||||
- const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
- void activate();
|
||||
+ static std::shared_ptr<EventStream> create(const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ Termination& terminate,
|
||||
+ EventSignal& signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
|
||||
private:
|
||||
const nghttp2::asio_http2::server::response& res;
|
||||
@@ -60,5 +59,14 @@ private:
|
||||
ssize_t process(uint8_t* destination, std::size_t len, uint32_t* data_flags);
|
||||
void enqueue(const std::string& fieldName, const std::string& what);
|
||||
void start_ping();
|
||||
+
|
||||
+protected:
|
||||
+ EventStream(const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ Termination& terminate,
|
||||
+ EventSignal& signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
+ void activate();
|
||||
};
|
||||
}
|
||||
diff --git a/src/restconf/NotificationStream.cpp b/src/restconf/NotificationStream.cpp
|
||||
index 5017f3e..fe21809 100644
|
||||
--- a/src/restconf/NotificationStream.cpp
|
||||
+++ b/src/restconf/NotificationStream.cpp
|
||||
@@ -183,4 +183,26 @@ libyang::DataNode replaceStreamLocations(const std::optional<std::string>& schem
|
||||
|
||||
return node;
|
||||
}
|
||||
+
|
||||
+/** @brief Create a new NotificationStream instance and activate it immediately.
|
||||
+ *
|
||||
+ * The stream is created with the given parameters and activated, which means it starts listening for
|
||||
+ * NETCONF notifications and sending them to the client.
|
||||
+ */
|
||||
+std::shared_ptr<NotificationStream> NotificationStream::create(
|
||||
+ const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ sysrepo::Session sess,
|
||||
+ libyang::DataFormat dataFormat,
|
||||
+ const std::optional<std::string>& filter,
|
||||
+ const std::optional<sysrepo::NotificationTimeStamp>& startTime,
|
||||
+ const std::optional<sysrepo::NotificationTimeStamp>& stopTime)
|
||||
+{
|
||||
+ auto signal = std::make_shared<rousette::http::EventStream::EventSignal>();
|
||||
+ auto stream = std::shared_ptr<NotificationStream>(new NotificationStream(req, res, termination, signal, keepAlivePingInterval, std::move(sess), dataFormat, filter, startTime, stopTime));
|
||||
+ stream->activate();
|
||||
+ return stream;
|
||||
+}
|
||||
}
|
||||
diff --git a/src/restconf/NotificationStream.h b/src/restconf/NotificationStream.h
|
||||
index daa79d6..91c5dde 100644
|
||||
--- a/src/restconf/NotificationStream.h
|
||||
+++ b/src/restconf/NotificationStream.h
|
||||
@@ -39,6 +39,18 @@ class NotificationStream : public rousette::http::EventStream {
|
||||
std::optional<sysrepo::Subscription> m_notifSubs;
|
||||
|
||||
public:
|
||||
+ static std::shared_ptr<NotificationStream> create(
|
||||
+ const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ sysrepo::Session sess,
|
||||
+ libyang::DataFormat dataFormat,
|
||||
+ const std::optional<std::string>& filter,
|
||||
+ const std::optional<sysrepo::NotificationTimeStamp>& startTime,
|
||||
+ const std::optional<sysrepo::NotificationTimeStamp>& stopTime);
|
||||
+
|
||||
+protected:
|
||||
NotificationStream(
|
||||
const nghttp2::asio_http2::server::request& req,
|
||||
const nghttp2::asio_http2::server::response& res,
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 31b3d0f..8544210 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -934,8 +934,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
server->handle("/telemetry/optics", [this, keepAlivePingInterval](const auto& req, const auto& res) {
|
||||
logRequest(req);
|
||||
|
||||
- auto client = std::make_shared<http::EventStream>(req, res, shutdownRequested, opticsChange, keepAlivePingInterval, as_restconf_push_update(dwdmEvents->currentData(), std::chrono::system_clock::now()));
|
||||
- client->activate();
|
||||
+ http::EventStream::create(req, res, shutdownRequested, opticsChange, keepAlivePingInterval, as_restconf_push_update(dwdmEvents->currentData(), std::chrono::system_clock::now()));
|
||||
});
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn, keepAlivePingInterval](const auto& req, const auto& res) mutable {
|
||||
@@ -968,21 +967,16 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
stopTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||
}
|
||||
|
||||
- // 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>(
|
||||
+ NotificationStream::create(
|
||||
req,
|
||||
res,
|
||||
shutdownRequested,
|
||||
- std::make_shared<rousette::http::EventStream::EventSignal>(),
|
||||
keepAlivePingInterval,
|
||||
sess,
|
||||
streamRequest.type.encoding,
|
||||
xpathFilter,
|
||||
startTime,
|
||||
stopTime);
|
||||
- client->activate();
|
||||
} catch (const auth::Error& e) {
|
||||
processAuthError(req, res, e, [&res]() {
|
||||
res.write_head(401, {TEXT_PLAIN, CORS});
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
From e98e079d0fdedf48facbfcf187950946c1ffed7a Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 7 Aug 2025 12:14:02 +0200
|
||||
Subject: [PATCH 08/13] refactor: a better convention for weak_from_this->lock
|
||||
Organization: Wires
|
||||
|
||||
It is not a "client", so let's stop calling it a "client". My bad.
|
||||
|
||||
Change-Id: Id8dc4d92c3ade8d86697366d0102e84bd466f504
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 44 ++++++++++++++++++++--------------------
|
||||
1 file changed, 22 insertions(+), 22 deletions(-)
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 0893653..3bdd55e 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -47,11 +47,11 @@ EventStream::EventStream(const server::request& req,
|
||||
}
|
||||
|
||||
state = WantToClose;
|
||||
- boost::asio::post(this->res.io_service(), [maybeClient = std::weak_ptr<EventStream>{shared_from_this()}]() {
|
||||
- if (auto client = maybeClient.lock()) {
|
||||
- std::lock_guard lock{client->mtx};
|
||||
- if (client->state == WantToClose) { // resume unless somebody closed it before this was picked up by the event loop
|
||||
- client->res.resume();
|
||||
+ boost::asio::post(this->res.io_service(), [weak = std::weak_ptr<EventStream>{shared_from_this()}]() {
|
||||
+ if (auto myself = weak.lock()) {
|
||||
+ std::lock_guard lock{myself->mtx};
|
||||
+ if (myself->state == WantToClose) { // resume unless somebody closed it before this was picked up by the event loop
|
||||
+ myself->res.resume();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -67,23 +67,23 @@ void EventStream::activate()
|
||||
{
|
||||
start_ping();
|
||||
|
||||
- auto client = shared_from_this();
|
||||
+ auto myself = shared_from_this();
|
||||
res.write_head(200, {
|
||||
{"content-type", {"text/event-stream", false}},
|
||||
{"access-control-allow-origin", {"*", false}},
|
||||
});
|
||||
|
||||
- res.on_close([client](const auto ec) {
|
||||
- spdlog::debug("{}: closed ({})", client->peer, nghttp2_http2_strerror(ec));
|
||||
- std::lock_guard lock{client->mtx};
|
||||
- client->ping.cancel();
|
||||
- client->eventSub.disconnect();
|
||||
- client->terminateSub.disconnect();
|
||||
- client->state = Closed;
|
||||
+ res.on_close([myself](const auto ec) {
|
||||
+ spdlog::debug("{}: closed ({})", myself->peer, nghttp2_http2_strerror(ec));
|
||||
+ std::lock_guard lock{myself->mtx};
|
||||
+ myself->ping.cancel();
|
||||
+ myself->eventSub.disconnect();
|
||||
+ myself->terminateSub.disconnect();
|
||||
+ myself->state = Closed;
|
||||
});
|
||||
|
||||
- res.end([client](uint8_t* destination, std::size_t len, uint32_t* data_flags) {
|
||||
- return client->process(destination, len, data_flags);
|
||||
+ res.end([myself](uint8_t* destination, std::size_t len, uint32_t* data_flags) {
|
||||
+ return myself->process(destination, len, data_flags);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,21 +156,21 @@ void EventStream::enqueue(const std::string& fieldName, const std::string& what)
|
||||
void EventStream::start_ping()
|
||||
{
|
||||
ping.expires_from_now(boost::posix_time::seconds(m_keepAlivePingInterval.count()));
|
||||
- ping.async_wait([maybeClient = weak_from_this()](const boost::system::error_code& ec) {
|
||||
- auto client = maybeClient.lock();
|
||||
- if (!client) {
|
||||
+ ping.async_wait([weak = weak_from_this()](const boost::system::error_code& ec) {
|
||||
+ auto myself = weak.lock();
|
||||
+ if (!myself) {
|
||||
spdlog::trace("ping: client already gone");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec == boost::asio::error::operation_aborted) {
|
||||
- spdlog::trace("{}: ping scheduler cancelled", client->peer);
|
||||
+ spdlog::trace("{}: ping scheduler cancelled", myself->peer);
|
||||
return;
|
||||
}
|
||||
|
||||
- client->enqueue("", "\n");
|
||||
- spdlog::trace("{}: keep-alive ping enqueued", client->peer);
|
||||
- client->start_ping();
|
||||
+ myself->enqueue("", "\n");
|
||||
+ spdlog::trace("{}: keep-alive ping enqueued", myself->peer);
|
||||
+ myself->start_ping();
|
||||
});
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,721 @@
|
||||
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/44] restconf: support fields query parameter
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This patch adds support for fields query parameter [1].
|
||||
|
||||
I had to modify the original grammar for fields parameter a bit to
|
||||
allow for the lowest precedence while parsing `;` expression. Also we
|
||||
allow for more strings than the original grammar specifies to make the
|
||||
syntax more user friendly.
|
||||
|
||||
The fields expression is parsed into an AST which corresponds 1:1
|
||||
to the parse tree. The tree representing the expression could be
|
||||
simplified but I chose not to as it would complicate the code even
|
||||
more (although the translation to XPath would be simpler).
|
||||
The tree is then transformed into a valid XPath 1.0 expression.
|
||||
|
||||
The XPath 1.0 expressions are limited and I could not find a way how to
|
||||
transform the fields string into a valid XPath. I realized that the
|
||||
easiest way will be to "unwrap" the expression into individual paths
|
||||
and join them via union operator.
|
||||
For example, input `a(b;c/d)` would result into `a/b | a/c/d` XPath.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/html/rfc8040#section-4.8.3
|
||||
[2] https://www.w3.org/TR/1999/REC-xpath-19991116/#section-Expressions
|
||||
|
||||
Change-Id: I3c96bbcf49b38ecf08f56912afd3a8f50c15cd44
|
||||
Signed-off-by: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
README.md | 1 -
|
||||
src/restconf/Server.cpp | 9 ++-
|
||||
src/restconf/uri.cpp | 95 +++++++++++++++++++++++-
|
||||
src/restconf/uri.h | 57 ++++++++++++++-
|
||||
src/restconf/uri_impl.h | 4 ++
|
||||
tests/pretty_printers.h | 23 ++++++
|
||||
tests/restconf-reading.cpp | 144 ++++++++++++++++++++++++++++++++++++-
|
||||
tests/restconf-writing.cpp | 12 +++-
|
||||
tests/uri-parser.cpp | 93 ++++++++++++++++++++++++
|
||||
tests/yang/example.yang | 10 +++
|
||||
10 files changed, 438 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
index 1689584..3fbfd21 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -22,7 +22,6 @@ This is a [RESTCONF](https://datatracker.ietf.org/doc/html/rfc8040.html) server
|
||||
- TLS certificate authentication (see [Access control model](#access-control-model) below)
|
||||
- the [`Last-Modified`](https://datatracker.ietf.org/doc/html/rfc8040.html#section-3.4.1.1) and [`ETag`](https://datatracker.ietf.org/doc/html/rfc8040.html#section-3.4.1.2) headers for [edit collision prevention](https://datatracker.ietf.org/doc/html/rfc8040.html#section-3.4.1) in the datastore resource
|
||||
- the [`Last-Modified`](https://datatracker.ietf.org/doc/html/rfc8040.html#section-3.5.1) and [`ETag`](https://datatracker.ietf.org/doc/html/rfc8040.html#section-3.5.2) headers in the data resource
|
||||
- - The [`fields`](https://datatracker.ietf.org/doc/html/rfc8040.html#section-4.8.3) query parameter
|
||||
- [NMDA](https://datatracker.ietf.org/doc/html/rfc8527.html) datastore access
|
||||
- no [`with-operational-default`](https://datatracker.ietf.org/doc/html/rfc8527#section-3.2.1) capability
|
||||
- no [`with-origin`](https://datatracker.ietf.org/doc/html/rfc8527#section-3.2.2) capability
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index b515d66..7c66ea4 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -834,6 +834,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
m_monitoringSession.setItem("/ietf-restconf-monitoring:restconf-state/capabilities/capability[2]", "urn:ietf:params:restconf:capability:depth:1.0");
|
||||
m_monitoringSession.setItem("/ietf-restconf-monitoring:restconf-state/capabilities/capability[3]", "urn:ietf:params:restconf:capability:with-defaults:1.0");
|
||||
m_monitoringSession.setItem("/ietf-restconf-monitoring:restconf-state/capabilities/capability[4]", "urn:ietf:params:restconf:capability:filter:1.0");
|
||||
+ m_monitoringSession.setItem("/ietf-restconf-monitoring:restconf-state/capabilities/capability[5]", "urn:ietf:params:restconf:capability:fields:1.0");
|
||||
m_monitoringSession.applyChanges();
|
||||
|
||||
m_monitoringOperSub = m_monitoringSession.onOperGet(
|
||||
@@ -1017,7 +1018,13 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
}
|
||||
}
|
||||
|
||||
- if (auto data = sess.getData(restconfRequest.path, maxDepth, getOptions, timeout); data) {
|
||||
+ auto xpath = restconfRequest.path;
|
||||
+ if (auto it = restconfRequest.queryParams.find("fields"); it != restconfRequest.queryParams.end()) {
|
||||
+ auto fields = std::get<queryParams::fields::Expr>(it->second);
|
||||
+ xpath = fieldsToXPath(sess.getContext(), xpath == "/*" ? "" : xpath, fields);
|
||||
+ }
|
||||
+
|
||||
+ if (auto data = sess.getData(xpath, maxDepth, getOptions, timeout); data) {
|
||||
res.write_head(
|
||||
200,
|
||||
{
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index ac399b7..da1e3a5 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -4,7 +4,10 @@
|
||||
* Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
*/
|
||||
|
||||
+#include <boost/algorithm/string/join.hpp>
|
||||
+#include <boost/fusion/adapted/struct/adapt_struct.hpp>
|
||||
#include <boost/fusion/include/std_pair.hpp>
|
||||
+#include <boost/spirit/home/x3/support/ast/variant.hpp>
|
||||
#include <experimental/iterator>
|
||||
#include <libyang-cpp/Enum.hpp>
|
||||
#include <map>
|
||||
@@ -112,6 +115,28 @@ struct insertTable: x3::symbols<queryParams::QueryParamValue> {
|
||||
}
|
||||
} const insertParam;
|
||||
|
||||
+/* This grammar is implemented a little bit differently than the RFC states. The ABNF from RFC is:
|
||||
+ *
|
||||
+ * fields-expr = path "(" fields-expr ")" / path ";" fields-expr / path
|
||||
+ * path = api-identifier [ "/" path ]
|
||||
+ *
|
||||
+ * Firstly, the grammar from the RFC doesn't allow for expression like `a(b);c` but allows for `c;a(b)`.
|
||||
+ * I think both should be valid (as user I would expect that the order of such expression does not matter).
|
||||
+ * Hence our grammar allows for more strings than the grammar from RFC.
|
||||
+ * This issue was already raised on IETF mailing list: https://mailarchive.ietf.org/arch/msg/netconf/TYBpTE_ELzzMOe6amrw6fQF07nE/
|
||||
+ * but neither a formal errata was issued nor there was a resolution on the mailing list.
|
||||
+ */
|
||||
+const auto fieldsExpr = x3::rule<class fieldsExpr, queryParams::fields::Expr>{"fieldsExpr"};
|
||||
+const auto fieldsSemi = x3::rule<class fieldsSemiExpr, queryParams::fields::SemiExpr>{"fieldsSemi"};
|
||||
+const auto fieldsSlash = x3::rule<class fieldsSlashExpr, queryParams::fields::SlashExpr>{"fieldsSlash"};
|
||||
+const auto fieldsParen = x3::rule<class fieldsParen, queryParams::fields::ParenExpr>{"fieldsParen"};
|
||||
+
|
||||
+const auto fieldsSemi_def = fieldsParen >> -(x3::lit(";") >> fieldsSemi);
|
||||
+const auto fieldsParen_def = fieldsSlash >> -(x3::lit("(") >> fieldsExpr >> x3::lit(")"));
|
||||
+const auto fieldsSlash_def = apiIdentifier >> -(x3::lit("/") >> fieldsSlash);
|
||||
+const auto fieldsExpr_def = fieldsSemi;
|
||||
+BOOST_SPIRIT_DEFINE(fieldsParen, fieldsExpr, fieldsSlash, fieldsSemi);
|
||||
+
|
||||
// early sanity check, this timestamp will be parsed by libyang::fromYangTimeFormat anyways
|
||||
const auto dateAndTime = x3::rule<class dateAndTime, std::string>{"dateAndTime"} =
|
||||
x3::repeat(4)[x3::digit] >> x3::char_('-') >> x3::repeat(2)[x3::digit] >> x3::char_('-') >> x3::repeat(2)[x3::digit] >> x3::char_('T') >>
|
||||
@@ -127,7 +152,8 @@ const auto queryParamPair = x3::rule<class queryParamPair, std::pair<std::string
|
||||
(x3::string("point") >> "=" >> uriPath) |
|
||||
(x3::string("filter") >> "=" >> filter) |
|
||||
(x3::string("start-time") >> "=" >> dateAndTime) |
|
||||
- (x3::string("stop-time") >> "=" >> dateAndTime);
|
||||
+ (x3::string("stop-time") >> "=" >> dateAndTime) |
|
||||
+ (x3::string("fields") >> "=" >> fieldsExpr);
|
||||
|
||||
const auto queryParamGrammar = x3::rule<class grammar, queryParams::QueryParams>{"queryParamGrammar"} = queryParamPair % "&" | x3::eps;
|
||||
|
||||
@@ -384,7 +410,7 @@ void validateQueryParameters(const std::multimap<std::string, queryParams::Query
|
||||
}
|
||||
}
|
||||
|
||||
- for (const auto& param : {"depth", "with-defaults", "content"}) {
|
||||
+ for (const auto& param : {"depth", "with-defaults", "content", "fields"}) {
|
||||
if (auto it = params.find(param); it != params.end() && httpMethod != "GET" && httpMethod != "HEAD") {
|
||||
throw ErrorResponse(400, "protocol", "invalid-value", "Query parameter '"s + param + "' can be used only with GET and HEAD methods");
|
||||
}
|
||||
@@ -658,4 +684,69 @@ std::set<std::string> allowedHttpMethodsForUri(const libyang::Context& ctx, cons
|
||||
|
||||
return allowedHttpMethods;
|
||||
}
|
||||
+
|
||||
+/** @brief Traverses the AST of the fields input expression and collects all the possible paths
|
||||
+ *
|
||||
+ * @param expr The fields expressions
|
||||
+ * @param currentPath The current path in the AST, it serves as a stack for the DFS
|
||||
+ * @param output The collection of all collected paths
|
||||
+ * @param end If this is the terminal node, i.e., the last node in the expression. This is needed for the correct handling of the leafs under paren expression, which does not "split" the paths but rather concatenates.
|
||||
+ * */
|
||||
+void fieldsToXPath(const queryParams::fields::Expr& expr, std::vector<std::string>& currentPath, std::vector<std::string>& output, bool end = false)
|
||||
+{
|
||||
+ boost::apply_visitor([&](auto&& node) {
|
||||
+ using T = std::decay_t<decltype(node)>;
|
||||
+
|
||||
+ if constexpr (std::is_same_v<T, queryParams::fields::ParenExpr>) {
|
||||
+ // the paths from left and right subtree are concatenated, i.e., the nodes we collect in the left tree
|
||||
+ // are joined together with the nodes from the right tree
|
||||
+ fieldsToXPath(node.lhs, currentPath, output, !node.rhs.has_value());
|
||||
+ if (node.rhs) {
|
||||
+ fieldsToXPath(*node.rhs, currentPath, output, end);
|
||||
+ }
|
||||
+ } else if constexpr (std::is_same_v<T, queryParams::fields::SemiExpr>) {
|
||||
+ // the two paths are now independent and nodes from left subtree do not affect the right subtree
|
||||
+ // hence we need to copy the current path
|
||||
+ auto pathCopy = currentPath;
|
||||
+ fieldsToXPath(node.lhs, currentPath, output, !node.rhs.has_value());
|
||||
+ if (node.rhs) {
|
||||
+ fieldsToXPath(*node.rhs, pathCopy, output, false);
|
||||
+ }
|
||||
+ } else if constexpr (std::is_same_v<T, queryParams::fields::SlashExpr>) {
|
||||
+ // the paths from left and right subtree are concatenated, i.e., the the nodes we collect in the left tree
|
||||
+ // are joined together with the nodes from the right tree, but if this is the terminal node, we need to
|
||||
+ // add it to the collection of all the gathered paths
|
||||
+ currentPath.push_back(node.lhs.name());
|
||||
+
|
||||
+ if (node.rhs) {
|
||||
+ fieldsToXPath(*node.rhs, currentPath, output, end);
|
||||
+ } else if (end) {
|
||||
+ output.emplace_back(boost::algorithm::join(currentPath, "/"));
|
||||
+ }
|
||||
+ }
|
||||
+ }, expr);
|
||||
+}
|
||||
+
|
||||
+/** @brief Translates the fields expression into a XPath expression and checks for schema validity of the resulting nodes
|
||||
+ *
|
||||
+ * The expressions are "unwrapped" into a linear structure and then a union of such paths is made.
|
||||
+ * E.g., the expression "a(b;c)" is translated into "a/b | a/c".
|
||||
+ * */
|
||||
+std::string fieldsToXPath(const libyang::Context& ctx, const std::string& prefix, const queryParams::fields::Expr& expr)
|
||||
+{
|
||||
+ std::vector<std::string> currentPath{prefix};
|
||||
+ std::vector<std::string> paths;
|
||||
+
|
||||
+ fieldsToXPath(expr, currentPath, paths);
|
||||
+
|
||||
+ for (auto& xpath : paths) {
|
||||
+ try {
|
||||
+ validateMethodForNode("GET", impl::URIPrefix{impl::URIPrefix::Type::BasicRestconfData}, ctx.findPath(xpath));
|
||||
+ } catch (const libyang::Error& e) {
|
||||
+ throw ErrorResponse(400, "application", "operation-failed", "Can't find schema node for '" + xpath + "'");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return boost::algorithm::join(paths, " | ");
|
||||
+}
|
||||
}
|
||||
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||
index 5e079ef..f6df724 100644
|
||||
--- a/src/restconf/uri.h
|
||||
+++ b/src/restconf/uri.h
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <boost/optional.hpp>
|
||||
+#include <boost/variant.hpp>
|
||||
#include <libyang-cpp/Module.hpp>
|
||||
#include <libyang-cpp/SchemaNode.hpp>
|
||||
#include <map>
|
||||
@@ -101,6 +102,57 @@ struct After {
|
||||
using PointParsed = std::vector<PathSegment>;
|
||||
}
|
||||
|
||||
+namespace fields {
|
||||
+struct ParenExpr;
|
||||
+struct SemiExpr;
|
||||
+struct SlashExpr;
|
||||
+
|
||||
+using Expr = boost::variant<boost::recursive_wrapper<SlashExpr>, boost::recursive_wrapper<ParenExpr>, boost::recursive_wrapper<SemiExpr>>;
|
||||
+
|
||||
+struct ParenExpr {
|
||||
+ Expr lhs;
|
||||
+ boost::optional<Expr> rhs;
|
||||
+
|
||||
+ ParenExpr() = default;
|
||||
+ ParenExpr(const Expr& lhs, const Expr& rhs) : ParenExpr(lhs, boost::optional<Expr>(rhs)) {}
|
||||
+ ParenExpr(const Expr& lhs, const boost::optional<Expr>& rhs = boost::none)
|
||||
+ : lhs(lhs)
|
||||
+ , rhs(rhs)
|
||||
+ {
|
||||
+ }
|
||||
+
|
||||
+ bool operator==(const ParenExpr&) const = default;
|
||||
+};
|
||||
+struct SemiExpr {
|
||||
+ Expr lhs;
|
||||
+ boost::optional<Expr> rhs;
|
||||
+
|
||||
+ SemiExpr() = default;
|
||||
+ SemiExpr(const Expr& lhs, const Expr& rhs) : SemiExpr(lhs, boost::optional<Expr>(rhs)) {}
|
||||
+ SemiExpr(const Expr& lhs, const boost::optional<Expr>& rhs = boost::none)
|
||||
+ : lhs(lhs)
|
||||
+ , rhs(rhs)
|
||||
+ {
|
||||
+ }
|
||||
+
|
||||
+ bool operator==(const SemiExpr&) const = default;
|
||||
+};
|
||||
+struct SlashExpr {
|
||||
+ ApiIdentifier lhs;
|
||||
+ boost::optional<Expr> rhs;
|
||||
+
|
||||
+ SlashExpr() = default;
|
||||
+ SlashExpr(const ApiIdentifier& lhs, const Expr& rhs) : SlashExpr(lhs, boost::optional<Expr>(rhs)) {}
|
||||
+ SlashExpr(const ApiIdentifier& lhs, const boost::optional<Expr>& rhs = boost::none)
|
||||
+ : lhs(lhs)
|
||||
+ , rhs(rhs)
|
||||
+ {
|
||||
+ }
|
||||
+
|
||||
+ bool operator==(const SlashExpr&) const = default;
|
||||
+};
|
||||
+}
|
||||
+
|
||||
using QueryParamValue = std::variant<
|
||||
UnboundedDepth,
|
||||
unsigned int,
|
||||
@@ -116,7 +168,8 @@ using QueryParamValue = std::variant<
|
||||
insert::Last,
|
||||
insert::Before,
|
||||
insert::After,
|
||||
- insert::PointParsed>;
|
||||
+ insert::PointParsed,
|
||||
+ fields::Expr>;
|
||||
using QueryParams = std::multimap<std::string, QueryParamValue>;
|
||||
}
|
||||
|
||||
@@ -159,4 +212,6 @@ std::vector<PathSegment> asPathSegments(const std::string& uriPath);
|
||||
std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> asYangModule(const libyang::Context& ctx, const std::string& uriPath);
|
||||
RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString);
|
||||
std::set<std::string> allowedHttpMethodsForUri(const libyang::Context& ctx, const std::string& uriPath);
|
||||
+
|
||||
+std::string fieldsToXPath(const libyang::Context& ctx, const std::string& prefix, const queryParams::fields::Expr& expr);
|
||||
}
|
||||
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||
index 8a2e166..2bcdb3f 100644
|
||||
--- a/src/restconf/uri_impl.h
|
||||
+++ b/src/restconf/uri_impl.h
|
||||
@@ -65,3 +65,7 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::URIPath, prefix, segments);
|
||||
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::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/pretty_printers.h b/tests/pretty_printers.h
|
||||
index ec87250..a2befeb 100644
|
||||
--- a/tests/pretty_printers.h
|
||||
+++ b/tests/pretty_printers.h
|
||||
@@ -8,6 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "trompeloeil_doctest.h"
|
||||
+#include <boost/variant.hpp>
|
||||
#include <experimental/iterator>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
@@ -159,6 +160,28 @@ struct StringMaker<rousette::restconf::queryParams::QueryParamValue> {
|
||||
[](const rousette::restconf::queryParams::insert::PointParsed& p) -> std::string {
|
||||
return ("PointParsed{" + StringMaker<decltype(p)>::convert(p) + "}").c_str();
|
||||
},
|
||||
+ [](const rousette::restconf::queryParams::fields::Expr& expr) -> std::string {
|
||||
+ return boost::apply_visitor([&](auto&& next) {
|
||||
+ using T = std::decay_t<decltype(next)>;
|
||||
+ std::string res;
|
||||
+
|
||||
+ if constexpr (std::is_same_v<T, rousette::restconf::queryParams::fields::ParenExpr> || std::is_same_v<T, rousette::restconf::queryParams::fields::SemiExpr>) {
|
||||
+ if constexpr (std::is_same_v<T, rousette::restconf::queryParams::fields::ParenExpr>) {
|
||||
+ res = "ParenExpr{";
|
||||
+ } else {
|
||||
+ res = "SemiExpr{";
|
||||
+ }
|
||||
+ res += StringMaker<rousette::restconf::queryParams::QueryParamValue>::convert(next.lhs).c_str();
|
||||
+ } else if constexpr (std::is_same_v<T, rousette::restconf::queryParams::fields::SlashExpr>) {
|
||||
+ res = "SlashExpr{" + next.lhs.name();
|
||||
+ }
|
||||
+
|
||||
+ if (next.rhs) {
|
||||
+ res += std::string(", ") + StringMaker<rousette::restconf::queryParams::QueryParamValue>::convert(*next.rhs).c_str();
|
||||
+ }
|
||||
+ return res += "}";
|
||||
+ }, expr);
|
||||
+ },
|
||||
}, obj).c_str();
|
||||
}
|
||||
};
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index 2ded3f0..d7d507b 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -72,7 +72,8 @@ TEST_CASE("reading data")
|
||||
"urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit",
|
||||
"urn:ietf:params:restconf:capability:depth:1.0",
|
||||
"urn:ietf:params:restconf:capability:with-defaults:1.0",
|
||||
- "urn:ietf:params:restconf:capability:filter:1.0"
|
||||
+ "urn:ietf:params:restconf:capability:filter:1.0",
|
||||
+ "urn:ietf:params:restconf:capability:fields:1.0"
|
||||
]
|
||||
},
|
||||
"streams": {
|
||||
@@ -116,7 +117,8 @@ TEST_CASE("reading data")
|
||||
"urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit",
|
||||
"urn:ietf:params:restconf:capability:depth:1.0",
|
||||
"urn:ietf:params:restconf:capability:with-defaults:1.0",
|
||||
- "urn:ietf:params:restconf:capability:filter:1.0"
|
||||
+ "urn:ietf:params:restconf:capability:filter:1.0",
|
||||
+ "urn:ietf:params:restconf:capability:fields:1.0"
|
||||
]
|
||||
},
|
||||
"streams": {
|
||||
@@ -672,7 +674,8 @@ TEST_CASE("reading data")
|
||||
"urn:ietf:params:restconf:capability:defaults:1.0?basic-mode=explicit",
|
||||
"urn:ietf:params:restconf:capability:depth:1.0",
|
||||
"urn:ietf:params:restconf:capability:with-defaults:1.0",
|
||||
- "urn:ietf:params:restconf:capability:filter:1.0"
|
||||
+ "urn:ietf:params:restconf:capability:filter:1.0",
|
||||
+ "urn:ietf:params:restconf:capability:fields:1.0"
|
||||
]
|
||||
},
|
||||
"streams": {
|
||||
@@ -894,6 +897,141 @@ TEST_CASE("reading data")
|
||||
)"});
|
||||
}
|
||||
|
||||
+ SECTION("fields filtering")
|
||||
+ {
|
||||
+ srSess.switchDatastore(sysrepo::Datastore::Running);
|
||||
+ srSess.setItem("/example:tlc/list[name='blabla']/choice1", "c1");
|
||||
+ srSess.setItem("/example:tlc/list[name='blabla']/collection[.='42']", std::nullopt);
|
||||
+ srSess.setItem("/example:tlc/list[name='blabla']/nested[first='1'][second='2'][third='3']/fourth", "4");
|
||||
+ srSess.setItem("/example:tlc/list[name='blabla']/nested[first='1'][second='2'][third='3']/data/a", "a");
|
||||
+ srSess.setItem("/example:tlc/list[name='blabla']/nested[first='1'][second='2'][third='3']/data/other-data/b", "b");
|
||||
+ srSess.setItem("/example:tlc/list[name='blabla']/nested[first='1'][second='2'][third='3']/data/other-data/c", "c");
|
||||
+ srSess.applyChanges();
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=choice1;collection", {}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "list": [
|
||||
+ {
|
||||
+ "name": "blabla",
|
||||
+ "collection": [
|
||||
+ 42
|
||||
+ ],
|
||||
+ "choice1": "c1"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=choice1;choice2;nested/data(a;other-data/b)", {}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "list": [
|
||||
+ {
|
||||
+ "name": "blabla",
|
||||
+ "nested": [
|
||||
+ {
|
||||
+ "first": "1",
|
||||
+ "second": 2,
|
||||
+ "third": "3",
|
||||
+ "data": {
|
||||
+ "a": "a",
|
||||
+ "other-data": {
|
||||
+ "b": "b"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ ],
|
||||
+ "choice1": "c1"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=hehe", {}) == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "operation-failed",
|
||||
+ "error-message": "Can't find schema node for '/example:tlc/list[name='blabla']/hehe'"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ 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": {
|
||||
+ "a": "a",
|
||||
+ "other-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": {
|
||||
+ "a": "a",
|
||||
+ "other-data": {}
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ // nonexistent schema node in fields: missing prefix in tlc because we query root node so libyang can't infer the prefix from the path
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "?fields=tlc", {}) == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "operation-failed",
|
||||
+ "error-message": "Can't find schema node for '/tlc'"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ // nonexistent schema node in fields
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/ob-la-di-ob-la-da", {}) == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "operation-failed",
|
||||
+ "error-message": "Can't find schema node for '/example:tlc/ob-la-di-ob-la-da'"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ }
|
||||
+
|
||||
SECTION("OPTIONS method")
|
||||
{
|
||||
// RPC node
|
||||
diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index c1a9515..582a262 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -375,6 +375,8 @@ TEST_CASE("writing data")
|
||||
CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/first", "1"),
|
||||
CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/second", "2"),
|
||||
CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/third", "3"),
|
||||
+ CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/data", std::nullopt),
|
||||
+ CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/data/other-data", std::nullopt),
|
||||
CREATED("/example:tlc/list[name='large']/choice2", "large"));
|
||||
REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/list=large", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list":[{"name": "large", "choice2": "large", "example:nested": [{"first": "1", "second": 2, "third": "3"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
}
|
||||
@@ -385,7 +387,9 @@ TEST_CASE("writing data")
|
||||
CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']", std::nullopt),
|
||||
CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/first", "11"),
|
||||
CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/second", "12"),
|
||||
- CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/third", "13"));
|
||||
+ CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/third", "13"),
|
||||
+ CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/data", std::nullopt),
|
||||
+ CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/data/other-data", std::nullopt));
|
||||
REQUIRE(put(RESTCONF_DATA_ROOT "/example:tlc/list=libyang/nested=11,12,13", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:nested": [{"first": "11", "second": 12, "third": "13"}]}]})") == Response{201, noContentTypeHeaders, ""});
|
||||
}
|
||||
|
||||
@@ -1339,6 +1343,8 @@ TEST_CASE("writing data")
|
||||
CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/first", "1"),
|
||||
CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/second", "2"),
|
||||
CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/third", "3"),
|
||||
+ CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/data", std::nullopt),
|
||||
+ CREATED("/example:tlc/list[name='large']/nested[first='1'][second='2'][third='3']/data/other-data", std::nullopt),
|
||||
CREATED("/example:tlc/list[name='large']/choice2", "large"));
|
||||
REQUIRE(post(RESTCONF_DATA_ROOT "/example:tlc", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:list":[{"name": "large", "choice2": "large", "example:nested": [{"first": "1", "second": 2, "third": "3"}]}]})") == Response{201, jsonHeaders, ""});
|
||||
}
|
||||
@@ -1349,7 +1355,9 @@ TEST_CASE("writing data")
|
||||
CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']", std::nullopt),
|
||||
CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/first", "11"),
|
||||
CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/second", "12"),
|
||||
- CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/third", "13"));
|
||||
+ CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/third", "13"),
|
||||
+ CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/data", std::nullopt),
|
||||
+ CREATED("/example:tlc/list[name='libyang']/nested[first='11'][second='12'][third='13']/data/other-data", std::nullopt));
|
||||
REQUIRE(post(RESTCONF_DATA_ROOT "/example:tlc/list=libyang", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"example:nested": [{"first": "11", "second": 12, "third": "13"}]}]})") == Response{201, jsonHeaders, ""});
|
||||
}
|
||||
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index 000fe5d..fd5dd8b 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -828,6 +828,65 @@ TEST_CASE("URI path parser")
|
||||
REQUIRE(parseQueryParams("stop-time=2023-05-20T18:30:00") == std::nullopt);
|
||||
REQUIRE(parseQueryParams("stop-time=20230520T18:30:00Z") == std::nullopt);
|
||||
REQUIRE(parseQueryParams("stop-time=2023-05-a0T18:30:00+05:30") == std::nullopt);
|
||||
+ REQUIRE(parseQueryParams("fields=mod:leaf") == QueryParams{{"fields", fields::SemiExpr{fields::ParenExpr{fields::SlashExpr{{"mod", "leaf"}}}}}});
|
||||
+ REQUIRE(parseQueryParams("fields=b(c;d);e(f)") == QueryParams{{"fields",
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"b"}},
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"c"}}
|
||||
+ },
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"d"}}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ },
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"e"}},
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"f"}}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }});
|
||||
+ REQUIRE(parseQueryParams("fields=(xyz)") == std::nullopt);
|
||||
+ REQUIRE(parseQueryParams("fields=a;(xyz)") == std::nullopt);
|
||||
+ REQUIRE(parseQueryParams("fields=") == std::nullopt);
|
||||
+
|
||||
+ for (const auto& [prefix, fields, xpath] : {
|
||||
+ std::tuple<std::string, std::string, std::string>{"/example:a", "b", "/example:a/b"},
|
||||
+ {"/example:a", "b/c", "/example:a/b/c"},
|
||||
+ {"/example:a/b", "c(enabled;blower)", "/example:a/b/c/enabled | /example:a/b/c/blower"},
|
||||
+ {"/example:a", "b(c(enabled;blower))", "/example:a/b/c/enabled | /example:a/b/c/blower"},
|
||||
+ {"/example:a", "b(c)", "/example:a/b/c"},
|
||||
+ {"/example:a", "example:b;something", "/example:a/example:b | /example:a/something"},
|
||||
+ {"/example:a", "something;b1;b(c/enabled;c/blower)", "/example:a/something | /example:a/b1 | /example:a/b/c/enabled | /example:a/b/c/blower"},
|
||||
+ {"/example:a", "b(c/enabled;c/blower);something;b1", "/example:a/b/c/enabled | /example:a/b/c/blower | /example:a/something | /example:a/b1"}, // not allowed by RFC 8040
|
||||
+ {"", "example:a(b;b1)", "/example:a/b | /example:a/b1"},
|
||||
+ }) {
|
||||
+ CAPTURE(fields);
|
||||
+ CAPTURE(xpath);
|
||||
+ auto qp = parseQueryParams("fields=" + fields);
|
||||
+ REQUIRE(qp);
|
||||
+ REQUIRE(qp->count("fields") == 1);
|
||||
+ auto fieldExpr = qp->find("fields")->second;
|
||||
+ REQUIRE(std::holds_alternative<fields::Expr>(fieldExpr));
|
||||
+ REQUIRE(rousette::restconf::fieldsToXPath(ctx, prefix, std::get<fields::Expr>(fieldExpr)) == xpath);
|
||||
+ }
|
||||
+
|
||||
+ auto qp = parseQueryParams("fields=xxx/xyz(a;b)");
|
||||
+ REQUIRE(qp);
|
||||
+ REQUIRE_THROWS_WITH_AS(
|
||||
+ rousette::restconf::fieldsToXPath(ctx, "/example:a", std::get<fields::Expr>(qp->find("fields")->second)),
|
||||
+ serializeErrorResponse(400, "application", "operation-failed", "Can't find schema node for '/example:a/xxx/xyz/a'").c_str(),
|
||||
+ rousette::restconf::ErrorResponse);
|
||||
}
|
||||
|
||||
SECTION("Full requests with validation")
|
||||
@@ -885,6 +944,40 @@ TEST_CASE("URI path parser")
|
||||
rousette::restconf::ErrorResponse);
|
||||
}
|
||||
|
||||
+ SECTION("fields")
|
||||
+ {
|
||||
+ auto resp = asRestconfRequest(ctx, "GET", "/restconf/data/example:a", "fields=b/c(enabled;blower)");
|
||||
+ REQUIRE(resp.queryParams == QueryParams({{"fields",
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{
|
||||
+ {"b"},
|
||||
+ fields::SlashExpr{{"c"}}
|
||||
+ },
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"enabled"}}
|
||||
+ },
|
||||
+ fields::SemiExpr{
|
||||
+ fields::ParenExpr{
|
||||
+ fields::SlashExpr{{"blower"}}
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }));
|
||||
+
|
||||
+ REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "POST", "/restconf/data/example:a", "fields=b/c(enabled;blower)"),
|
||||
+ serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'fields' can be used only with GET and HEAD methods").c_str(),
|
||||
+ rousette::restconf::ErrorResponse);
|
||||
+
|
||||
+ REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "fields=a"),
|
||||
+ serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'fields' can't be used with streams").c_str(),
|
||||
+ rousette::restconf::ErrorResponse);
|
||||
+ }
|
||||
+
|
||||
SECTION("insert first/last")
|
||||
{
|
||||
auto resp = asRestconfRequest(ctx, "PUT", "/restconf/data/example:tlc", "insert=first");
|
||||
diff --git a/tests/yang/example.yang b/tests/yang/example.yang
|
||||
index 75cd7a6..5d586a0 100644
|
||||
--- a/tests/yang/example.yang
|
||||
+++ b/tests/yang/example.yang
|
||||
@@ -38,6 +38,14 @@ module example {
|
||||
leaf first { type string; }
|
||||
leaf second { type int32; }
|
||||
leaf third { type string; }
|
||||
+ leaf fourth { type string; }
|
||||
+ container data {
|
||||
+ leaf a { type string; }
|
||||
+ container other-data {
|
||||
+ leaf b { type string; }
|
||||
+ leaf c { type string; }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
choice choose {
|
||||
mandatory true;
|
||||
@@ -92,6 +100,8 @@ module example {
|
||||
}
|
||||
}
|
||||
}
|
||||
+ container b1 { }
|
||||
+ leaf something { type string; }
|
||||
}
|
||||
|
||||
container two-leafs {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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/44] cmake: adhere to CMP0167
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
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] https://cmake.org/cmake/help/latest/policy/CMP0167.html
|
||||
|
||||
Change-Id: I0cfc6cd0077fac48723487a280daac5fe8218ebb
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 465bef9..01dd2c2 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -72,7 +72,7 @@ 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 COMPONENTS system thread)
|
||||
+find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=3)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
From de7c55a1e8d3dda20ee3dc408612f2ddb309e888 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 7 Aug 2025 12:21:54 +0200
|
||||
Subject: [PATCH 09/13] fix a possible bad_weak_ptr exception
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I8c7f7a943a1d848f15527988cb76c2a0a10089e6
|
||||
Fixes: 4eae6200 (close long-lived connections on SIGTERM)
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 3bdd55e..8703dad 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -47,7 +47,7 @@ EventStream::EventStream(const server::request& req,
|
||||
}
|
||||
|
||||
state = WantToClose;
|
||||
- boost::asio::post(this->res.io_service(), [weak = std::weak_ptr<EventStream>{shared_from_this()}]() {
|
||||
+ boost::asio::post(this->res.io_service(), [weak = weak_from_this()]() {
|
||||
if (auto myself = weak.lock()) {
|
||||
std::lock_guard lock{myself->mtx};
|
||||
if (myself->state == WantToClose) { // resume unless somebody closed it before this was picked up by the event loop
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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/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: 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
|
||||
check `UID_WRAPPER_DISABLE_DEEPBIND`. The upstream listened to me, and
|
||||
they fixed it [1]. Unfortunately, the old variable name is not read from
|
||||
as of pam_wrapper 1.1.6, so we require setting *both* variables for
|
||||
random version compatibility.
|
||||
|
||||
[1] https://git.samba.org/?p=pam_wrapper.git;a=commitdiff;h=9f0cccf7432dd9be1de953f9b13a7f9b06c40442
|
||||
|
||||
Change-Id: I2959f505f5325950606c68b0b324be7181dd6e4f
|
||||
Reported-by: Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 ++-
|
||||
1 file changed, 2 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 01dd2c2..731d7cb 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -179,10 +179,11 @@ if(BUILD_TESTING)
|
||||
endif()
|
||||
|
||||
if(TEST_WRAP_PAM)
|
||||
+ # FIXME: remove UID_WRAPPER_... (and keep PAM_WRAPPER_...) once we require pam_wrapper 1.1.6+
|
||||
set(TEST_COMMAND
|
||||
${UNSHARE_EXECUTABLE} -r -m sh -c "set -ex $<SEMICOLON>
|
||||
${MOUNT_EXECUTABLE} -t tmpfs none /tmp $<SEMICOLON>
|
||||
- export LD_PRELOAD=${pam_wrapper_LDFLAGS} PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/tests/pam PAM_WRAPPER=1 UID_WRAPPER_DISABLE_DEEPBIND=1 $<SEMICOLON>
|
||||
+ export LD_PRELOAD=${pam_wrapper_LDFLAGS} PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/tests/pam PAM_WRAPPER=1 UID_WRAPPER_DISABLE_DEEPBIND=1 PAM_WRAPPER_DISABLE_DEEPBIND=1 $<SEMICOLON>
|
||||
$<TARGET_FILE:test-${TEST_NAME}>")
|
||||
else()
|
||||
set(TEST_COMMAND test-${TEST_NAME})
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
From 0f034d6da3e6d0a91bbe09717cb33aaf3a3ed747 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 19 May 2025 12:11:09 +0200
|
||||
Subject: [PATCH 10/13] http: add optional callbacks to EventStream
|
||||
Organization: Wires
|
||||
|
||||
In some cases, it might be useful to have a callback that is called when
|
||||
the client disconnects or when the connection is lost.
|
||||
For us, this will be useful in the future subscribed notifications
|
||||
implementation where we would like to clean up stuff after client
|
||||
disconnects.
|
||||
|
||||
Change-Id: Icfc2959e38b812b7c18f45976415209b29151c7b
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 18 +++++++++++++++---
|
||||
src/http/EventStream.h | 10 ++++++++--
|
||||
2 files changed, 23 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 8703dad..0a974d4 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -25,11 +25,15 @@ EventStream::EventStream(const server::request& req,
|
||||
Termination& termination,
|
||||
EventSignal& signal,
|
||||
const std::chrono::seconds keepAlivePingInterval,
|
||||
- const std::optional<std::string>& initialEvent)
|
||||
+ const std::optional<std::string>& initialEvent,
|
||||
+ const std::function<void()>& onTerminationCb,
|
||||
+ const std::function<void()>& onClientDisconnectedCb)
|
||||
: res{res}
|
||||
, ping{res.io_service()}
|
||||
, peer{peer_from_request(req)}
|
||||
, m_keepAlivePingInterval(keepAlivePingInterval)
|
||||
+ , onTerminationCb(onTerminationCb)
|
||||
+ , onClientDisconnectedCb(onClientDisconnectedCb)
|
||||
{
|
||||
if (initialEvent) {
|
||||
enqueue(FIELD_DATA, *initialEvent);
|
||||
@@ -41,6 +45,9 @@ EventStream::EventStream(const server::request& req,
|
||||
|
||||
terminateSub = termination.connect([this]() {
|
||||
spdlog::trace("{}: will terminate", peer);
|
||||
+ if (this->onTerminationCb) {
|
||||
+ this->onTerminationCb();
|
||||
+ }
|
||||
std::lock_guard lock{mtx};
|
||||
if (state == Closed) { // we are late to the party, res is already gone
|
||||
return;
|
||||
@@ -80,6 +87,9 @@ void EventStream::activate()
|
||||
myself->eventSub.disconnect();
|
||||
myself->terminateSub.disconnect();
|
||||
myself->state = Closed;
|
||||
+ if (myself->onClientDisconnectedCb) {
|
||||
+ myself->onClientDisconnectedCb();
|
||||
+ }
|
||||
});
|
||||
|
||||
res.end([myself](uint8_t* destination, std::size_t len, uint32_t* data_flags) {
|
||||
@@ -187,9 +197,11 @@ std::shared_ptr<EventStream> EventStream::create(const nghttp2::asio_http2::serv
|
||||
Termination& terminate,
|
||||
EventSignal& signal,
|
||||
const std::chrono::seconds keepAlivePingInterval,
|
||||
- const std::optional<std::string>& initialEvent)
|
||||
+ const std::optional<std::string>& initialEvent,
|
||||
+ const std::function<void()>& onTerminationCb,
|
||||
+ const std::function<void()>& onClientDisconnectedCb)
|
||||
{
|
||||
- auto stream = std::shared_ptr<EventStream>(new EventStream(req, res, terminate, signal, keepAlivePingInterval, initialEvent));
|
||||
+ auto stream = std::shared_ptr<EventStream>(new EventStream(req, res, terminate, signal, keepAlivePingInterval, initialEvent, onTerminationCb, onClientDisconnectedCb));
|
||||
stream->activate();
|
||||
return stream;
|
||||
}
|
||||
diff --git a/src/http/EventStream.h b/src/http/EventStream.h
|
||||
index ef0a002..87d8c82 100644
|
||||
--- a/src/http/EventStream.h
|
||||
+++ b/src/http/EventStream.h
|
||||
@@ -36,7 +36,9 @@ public:
|
||||
Termination& terminate,
|
||||
EventSignal& signal,
|
||||
const std::chrono::seconds keepAlivePingInterval,
|
||||
- const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
+ const std::optional<std::string>& initialEvent = std::nullopt,
|
||||
+ const std::function<void()>& onTerminationCb = std::function<void()>(),
|
||||
+ const std::function<void()>& onClientDisconnectedCb = std::function<void()>());
|
||||
|
||||
private:
|
||||
const nghttp2::asio_http2::server::response& res;
|
||||
@@ -54,6 +56,8 @@ private:
|
||||
boost::signals2::scoped_connection eventSub, terminateSub;
|
||||
const std::string peer;
|
||||
const std::chrono::seconds m_keepAlivePingInterval;
|
||||
+ std::function<void()> onTerminationCb; ///< optional callback when the stream is terminated
|
||||
+ std::function<void()> onClientDisconnectedCb; ///< optional callback invoked in client.on_close()
|
||||
|
||||
size_t send_chunk(uint8_t* destination, std::size_t len, uint32_t* data_flags);
|
||||
ssize_t process(uint8_t* destination, std::size_t len, uint32_t* data_flags);
|
||||
@@ -66,7 +70,9 @@ protected:
|
||||
Termination& terminate,
|
||||
EventSignal& signal,
|
||||
const std::chrono::seconds keepAlivePingInterval,
|
||||
- const std::optional<std::string>& initialEvent = std::nullopt);
|
||||
+ const std::optional<std::string>& initialEvent = std::nullopt,
|
||||
+ const std::function<void()>& onTerminationCb = std::function<void()>(),
|
||||
+ const std::function<void()>& onClientDisconnectedCb = std::function<void()>());
|
||||
void activate();
|
||||
};
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
From d72adb3da31cb509e64c8f33174e375fa9468b0b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 19 May 2025 12:23:16 +0200
|
||||
Subject: [PATCH 11/13] restconf: add internal RPC handler dispatcher
|
||||
Organization: Wires
|
||||
|
||||
This code adds a new internal RPC handler dispatcher that checks if user
|
||||
is authorized to call the RPC (we are bypassing sysrepo, so we have to
|
||||
check this manually) and then calls the RPC handler.
|
||||
So far, there are no RPCs processed, but support for the
|
||||
ietf-subscribed-notifications:establish-subscription RPC is coming soon.
|
||||
|
||||
Change-Id: I99121a511011229e4098f95e91601b39d333444a
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 24 ++++++++++++++++++++----
|
||||
tests/restconf-rpc.cpp | 18 ++++++++++++++++++
|
||||
2 files changed, 38 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 8544210..74c06f7 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -438,10 +438,26 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
return {editNode, replacementNode};
|
||||
}
|
||||
|
||||
-std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session&, const libyang::DataNode&)
|
||||
+std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, const libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||
{
|
||||
- // TODO: Implement internal RPCs
|
||||
- throw ErrorResponse(501, "application", "operation-not-supported", "Internal RPCs are not yet supported.");
|
||||
+ using InternalRPCHandler = std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)>;
|
||||
+ const std::map<std::string, InternalRPCHandler> handlers;
|
||||
+
|
||||
+ const auto rpcPath = rpcInput.path();
|
||||
+
|
||||
+ // Is the user authorized to call the operation?
|
||||
+ auto [parent, rpcNode] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||
+ if (!sess.checkNacmOperation(*rpcNode)) {
|
||||
+ throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode->path());
|
||||
+ }
|
||||
+
|
||||
+ if (auto it = handlers.find(rpcPath); it != handlers.end()) {
|
||||
+ auto [parent, rpcOutput] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||
+ it->second(sess, requestEncoding, rpcInput, *rpcOutput);
|
||||
+ return *parent;
|
||||
+ }
|
||||
+
|
||||
+ throw ErrorResponse(501, "application", "operation-not-supported", "Unsupported RPC call to " + rpcPath, rpcPath);
|
||||
}
|
||||
|
||||
void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
@@ -478,7 +494,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
if (requestCtx->restconfRequest.type == RestconfRequest::Type::Execute) {
|
||||
rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout);
|
||||
} else if (requestCtx->restconfRequest.type == RestconfRequest::Type::ExecuteInternal) {
|
||||
- rpcReply = processInternalRPC(requestCtx->sess, *rpcNode);
|
||||
+ rpcReply = processInternalRPC(requestCtx->sess, *rpcNode, *requestCtx->dataFormat.request);
|
||||
}
|
||||
|
||||
if (!rpcReply || rpcReply->immediateChildren().empty()) {
|
||||
diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp
|
||||
index a3e9309..43aaed4 100644
|
||||
--- a/tests/restconf-rpc.cpp
|
||||
+++ b/tests/restconf-rpc.cpp
|
||||
@@ -420,4 +420,22 @@ TEST_CASE("invoking actions and rpcs")
|
||||
)"});
|
||||
}
|
||||
}
|
||||
+
|
||||
+ SECTION("Internal RPC handlers")
|
||||
+ {
|
||||
+ // check NACM access
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", {CONTENT_TYPE_JSON}, R"({"ietf-subscribed-notifications:input": {}})") == Response{403, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "access-denied",
|
||||
+ "error-path": "/ietf-subscribed-notifications:kill-subscription",
|
||||
+ "error-message": "Access denied."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ }
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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/44] tests: add missing pragma once
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I269c20e5a914aa8c7bc9431147dd9785ea1aedda
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/aux-utils.h | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/tests/aux-utils.h b/tests/aux-utils.h
|
||||
index a3923bb..7482945 100644
|
||||
--- a/tests/aux-utils.h
|
||||
+++ b/tests/aux-utils.h
|
||||
@@ -6,6 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
+#pragma once
|
||||
#include "trompeloeil_doctest.h"
|
||||
#include <iostream>
|
||||
#include <nghttp2/asio_http2_client.h>
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
From 86ea8a1fdc2cb8baa1edbd32bd4f8b45acdef816 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 8 Sep 2025 20:24:15 +0200
|
||||
Subject: [PATCH 12/13] EventStream: fix possible heap-use-after-free
|
||||
Organization: Wires
|
||||
|
||||
I have started getting heap-use-after-free errors from sanitizers (see
|
||||
below). The problem is that EventStream::enqueue posted res.resume()
|
||||
call to the asio event loop, but sometimes when the function was called,
|
||||
the response object was already destroyed.
|
||||
The fix is to check if the response is still valid (and not closed)
|
||||
before actually resuming the response.
|
||||
|
||||
WARNING: ThreadSanitizer: heap-use-after-free (pid=633338)
|
||||
Read of size 8 at 0x72080000f3d0 by thread T134:
|
||||
#0 std::__uniq_ptr_impl<nghttp2::asio_http2::server::response_impl, std::default_delete<nghttp2::asio_http2::server::response_impl>>::_M_ptr() const /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/unique_ptr.h:193:51 (libnghttp2_asio.so.0+0x205011) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#1 std::unique_ptr<nghttp2::asio_http2::server::response_impl, std::default_delete<nghttp2::asio_http2::server::response_impl>>::get() const /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/unique_ptr.h:473:21 (libnghttp2_asio.so.0+0x204fc5) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#2 std::unique_ptr<nghttp2::asio_http2::server::response_impl, std::default_delete<nghttp2::asio_http2::server::response_impl>>::operator->() const /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/unique_ptr.h:466:9 (libnghttp2_asio.so.0+0x2046c5) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#3 nghttp2::asio_http2::server::response::resume() const /home/tomas/zdrojaky/cesnet/nghttp2-asio/lib/asio_server_response.cc:63:33 (libnghttp2_asio.so.0+0x204425) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#4 rousette::http::EventStream::enqueue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_1::operator()() const /home/tomas/zdrojaky/cesnet/rousette/src/http/EventStream.cpp:167:68 (test-restconf-subscribed-notifications+0x2a084d) (BuildId: 9270c9f5d6da566b5b7f223994c283ecf35f0e43)
|
||||
#5 boost::asio::detail::binder0<rousette::http::EventStream::enqueue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_1>::operator()() /usr/include/boost/asio/detail/bind_handler.hpp:56:5 (test-restconf-subscribed-notifications+0x2a084d)
|
||||
#6 boost::asio::detail::executor_op<boost::asio::detail::binder0<rousette::http::EventStream::enqueue(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_1>, std::allocator<void>, boost::asio::detail::scheduler_operation>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) /usr/include/boost/asio/detail/executor_op.hpp:70:7 (test-restconf-subscribed-notifications+0x2a084d)
|
||||
#7 boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /usr/include/boost/asio/detail/scheduler_operation.hpp:40:5 (test-restconf-subscribed-notifications+0x165ce9) (BuildId: 9270c9f5d6da566b5b7f223994c283ecf35f0e43)
|
||||
#8 boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /usr/include/boost/asio/detail/impl/scheduler.ipp:492:12 (test-restconf-subscribed-notifications+0x165ce9)
|
||||
#9 boost::asio::detail::scheduler::run(boost::system::error_code&) /usr/include/boost/asio/detail/impl/scheduler.ipp:208:10 (test-restconf-subscribed-notifications+0x165292) (BuildId: 9270c9f5d6da566b5b7f223994c283ecf35f0e43)
|
||||
#10 boost::asio::io_context::run() /usr/include/boost/asio/impl/io_context.ipp:71:24 (libnghttp2_asio.so.0+0x1675bf) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
(...)
|
||||
|
||||
Previous write of size 8 at 0x72080000f3d0 by thread T134:
|
||||
#0 operator delete(void*, unsigned long) <null> (test-restconf-subscribed-notifications+0x13f7d3) (BuildId: 9270c9f5d6da566b5b7f223994c283ecf35f0e43)
|
||||
#1 std::default_delete<nghttp2::asio_http2::server::stream>::operator()(nghttp2::asio_http2::server::stream*) const /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/unique_ptr.h:93:2 (libnghttp2_asio.so.0+0x1fc71d) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#2 std::_Sp_counted_deleter<nghttp2::asio_http2::server::stream*, std::default_delete<nghttp2::asio_http2::server::stream>, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/shared_ptr_base.h:526:9 (libnghttp2_asio.so.0+0x1fddaf) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#3 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release_last_use() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/shared_ptr_base.h:174:2 (libnghttp2_asio.so.0+0x160e46) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#4 std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/shared_ptr_base.h:360:4 (libnghttp2_asio.so.0+0x160dfd) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#5 std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/shared_ptr_base.h:1069:11 (libnghttp2_asio.so.0+0x160d08) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#6 std::__shared_ptr<nghttp2::asio_http2::server::stream, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/shared_ptr_base.h:1531:31 (libnghttp2_asio.so.0+0x1f82e9) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#7 std::shared_ptr<nghttp2::asio_http2::server::stream>::~shared_ptr() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/shared_ptr.h:175:11 (libnghttp2_asio.so.0+0x1f82a5) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#8 std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>::~pair() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_pair.h:302:12 (libnghttp2_asio.so.0+0x1f8269) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#9 void std::__new_allocator<std::_Rb_tree_node<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::destroy<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>(std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/new_allocator.h:198:10 (libnghttp2_asio.so.0+0x1f815d) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#10 void std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>>::destroy<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>(std::allocator<std::_Rb_tree_node<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>&, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/alloc_traits.h:696:8 (libnghttp2_asio.so.0+0x1f815d)
|
||||
#11 std::_Rb_tree<int, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>, std::_Select1st<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::_M_destroy_node(std::_Rb_tree_node<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_tree.h:1265:2 (libnghttp2_asio.so.0+0x1f815d)
|
||||
#12 std::_Rb_tree<int, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>, std::_Select1st<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::_M_drop_node(std::_Rb_tree_node<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_tree.h:1273:2 (libnghttp2_asio.so.0+0x1f80b9) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#13 std::_Rb_tree<int, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>, std::_Select1st<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::_M_erase(std::_Rb_tree_node<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>*) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_tree.h:2590:4 (libnghttp2_asio.so.0+0x1f7e98) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#14 std::_Rb_tree<int, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>, std::_Select1st<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::clear() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_tree.h:1878:2 (libnghttp2_asio.so.0+0x1ff035) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#15 std::_Rb_tree<int, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>, std::_Select1st<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::_M_erase_aux(std::_Rb_tree_const_iterator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::_Rb_tree_const_iterator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_tree.h:3127:2 (libnghttp2_asio.so.0+0x1feca6) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#16 std::_Rb_tree<int, std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>, std::_Select1st<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::erase(int const&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_tree.h:3141:7 (libnghttp2_asio.so.0+0x1fe8a3) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#17 std::map<int, std::shared_ptr<nghttp2::asio_http2::server::stream>, std::less<int>, std::allocator<std::pair<int const, std::shared_ptr<nghttp2::asio_http2::server::stream>>>>::erase(int const&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.1.1/../../../../include/c++/15.1.1/bits/stl_map.h:1159:21 (libnghttp2_asio.so.0+0x1f68d5) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#18 nghttp2::asio_http2::server::http2_handler::close_stream(int) /home/tomas/zdrojaky/cesnet/nghttp2-asio/lib/asio_server_http2_handler.cc:315:12 (libnghttp2_asio.so.0+0x1f34b0) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#19 nghttp2::asio_http2::server::(anonymous namespace)::on_stream_close_callback(nghttp2_session*, int, unsigned int, void*) /home/tomas/zdrojaky/cesnet/nghttp2-asio/lib/asio_server_http2_handler.cc:193:12 (libnghttp2_asio.so.0+0x1f3074) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#20 nghttp2_session_close_stream /usr/src/debug/libnghttp2/nghttp2/lib/nghttp2_session.c:1289:9 (libnghttp2.so.14+0x62ac) (BuildId: 8a424dc80fadd83dfdd8c51eebe47046c626d95e)
|
||||
#21 nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write() /home/tomas/zdrojaky/cesnet/nghttp2-asio/lib/asio_server_connection.h:177:20 (libnghttp2_asio.so.0+0x1ebb1c) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#22 nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)::operator()(boost::system::error_code const&, unsigned long) const /home/tomas/zdrojaky/cesnet/nghttp2-asio/lib/asio_server_connection.h:207:11 (libnghttp2_asio.so.0+0x1ec7a8) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#23 boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>::operator()(boost::system::error_code, unsigned long, int) /usr/include/boost/asio/impl/write.hpp:380:9 (libnghttp2_asio.so.0+0x1ec4e5) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#24 boost::asio::detail::binder2<boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>, boost::system::error_code, unsigned long>::operator()() /usr/include/boost/asio/detail/bind_handler.hpp:181:5 (libnghttp2_asio.so.0+0x1edd41) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#25 void boost::asio::detail::handler_work<boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>, boost::asio::any_io_executor, void>::complete<boost::asio::detail::binder2<boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>, boost::system::error_code, unsigned long>>(boost::asio::detail::binder2<boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>, boost::system::error_code, unsigned long>&, boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>&) /usr/include/boost/asio/detail/handler_work.hpp:470:7 (libnghttp2_asio.so.0+0x1edb2e) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#26 boost::asio::detail::reactive_socket_send_op<boost::asio::const_buffer, boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>, boost::asio::mutable_buffer, boost::asio::mutable_buffer const*, boost::asio::detail::transfer_all_t, nghttp2::asio_http2::server::connection<boost::asio::basic_stream_socket<boost::asio::ip::tcp, boost::asio::any_io_executor>>::do_write()::'lambda'(boost::system::error_code const&, unsigned long)>, boost::asio::any_io_executor>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) /usr/include/boost/asio/detail/reactive_socket_send_op.hpp:154:9 (libnghttp2_asio.so.0+0x1ed66c) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
#27 boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /usr/include/boost/asio/detail/scheduler_operation.hpp:40:5 (test-restconf-subscribed-notifications+0x165ce9) (BuildId: 9270c9f5d6da566b5b7f223994c283ecf35f0e43)
|
||||
#28 boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /usr/include/boost/asio/detail/impl/scheduler.ipp:492:12 (test-restconf-subscribed-notifications+0x165ce9)
|
||||
#29 boost::asio::detail::scheduler::run(boost::system::error_code&) /usr/include/boost/asio/detail/impl/scheduler.ipp:208:10 (test-restconf-subscribed-notifications+0x165292) (BuildId: 9270c9f5d6da566b5b7f223994c283ecf35f0e43)
|
||||
#30 boost::asio::io_context::run() /usr/include/boost/asio/impl/io_context.ipp:71:24 (libnghttp2_asio.so.0+0x1675bf) (BuildId: d31f110d6b82f3b5923f6e08b6ae7d409d710cc4)
|
||||
(...)
|
||||
|
||||
Change-Id: Ifdb1f8610cacffca3bb49da17aa9b1d267cdd472
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 17 ++++++++++++++++-
|
||||
1 file changed, 16 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 0a974d4..f97d953 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -160,7 +160,22 @@ void EventStream::enqueue(const std::string& fieldName, const std::string& what)
|
||||
spdlog::trace("{}: new event, ∑ queue size = {}", peer, len);
|
||||
queue.push_back(buf);
|
||||
state = HasEvents;
|
||||
- boost::asio::post(res.io_service(), [&res = this->res]() { res.resume(); });
|
||||
+ boost::asio::post(res.io_service(), [weak = weak_from_this()]() {
|
||||
+ auto myself = weak.lock();
|
||||
+ if (!myself) {
|
||||
+ spdlog::trace("enqueue: already disconnected");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ std::lock_guard lock{myself->mtx};
|
||||
+ // if state is Closed, then res.on_close() already finished, which means we must not use res anymore
|
||||
+ if (myself->state == Closed) {
|
||||
+ spdlog::trace("{}: enqueue: not resuming, the response's on_close handler already finished", myself->peer);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ myself->res.resume();
|
||||
+ });
|
||||
}
|
||||
|
||||
void EventStream::start_ping()
|
||||
--
|
||||
2.43.0
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user