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

|
||||
[](https://gerrit.cesnet.cz/q/project:CzechLight/rousette)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From db64d7b3fc0a0373067e6c5ac9e41b93351778a1 Mon Sep 17 00:00:00 2001
|
||||
From 4eae6200aa812950ebbac1660a1899f4edf41e11 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 7 Aug 2024 19:07:35 +0200
|
||||
Subject: [PATCH 04/13] close long-lived connections on SIGTERM
|
||||
Subject: [PATCH 06/38] close long-lived connections on SIGTERM
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Without this patch, all the ongoing SSE streams would be left alive for
|
||||
@@ -20,7 +23,7 @@ hope it is).
|
||||
|
||||
Change-Id: If442134783ba1d699de47c51a9068378f53e8339
|
||||
Co-authored-by: Tomas Pecka <tomas.pecka@cesnet.cz>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/clock.cpp | 7 +--
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From 5c8a139d04d9b3dfb28b8c0e1a4aa7b945a1e8fc Mon Sep 17 00:00:00 2001
|
||||
From ca2894d4888c673d227fc196a25f83ded20e8f04 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 30 Jun 2025 15:38:02 +0200
|
||||
Subject: [PATCH 05/13] restconf: refactor server stop
|
||||
Subject: [PATCH 07/38] restconf: refactor server stop
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
In 94b24795 ("server: simplify listening/stopping") we needed to call
|
||||
@@ -17,7 +20,7 @@ does not seem like it should be called from *every* io_service.)
|
||||
[2] https://www.boost.org/doc/libs/1_85_0/doc/html/boost_asio/reference/post.html
|
||||
|
||||
Change-Id: I2f33c38a78dce4081a03326c9a9bb25817fc9d2f
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 13 +++++--------
|
||||
1 file changed, 5 insertions(+), 8 deletions(-)
|
||||
@@ -0,0 +1,33 @@
|
||||
From 9961430bacdd8dbac64a01b2a2ffb6a4b7e806b6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 13 May 2025 13:48:35 +0200
|
||||
Subject: [PATCH 08/38] tests: use std::string::starts_with
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We are C++20, so we can use line, which is more readable.
|
||||
|
||||
Change-Id: I40d4038b421f6bc1fcf320f609b50d5ce7018a45
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf_utils.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index cbfce2c..08b9623 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -228,7 +228,7 @@ std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
|
||||
std::string event;
|
||||
|
||||
while (std::getline(iss, line)) {
|
||||
- if (line.compare(0, prefix.size(), prefix) == 0) {
|
||||
+ if (line.starts_with(prefix)) {
|
||||
event += line.substr(prefix.size());
|
||||
} else if (line.empty()) {
|
||||
res.emplace_back(std::move(event));
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
From 746c0cdfef6808f393be7946630b8acbb0636706 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 22 Jul 2025 17:47:50 +0200
|
||||
Subject: [PATCH 09/38] tests: add SSE event watcher for comments
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I want to test whether client receives even the Server Sent Events
|
||||
which should be ignored, i.e., lines starting with colon.
|
||||
|
||||
Change-Id: If54f0af05b4884aab01325f12fd0a6859791b41b
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/event_watchers.cpp | 7 ++++++-
|
||||
tests/event_watchers.h | 4 +++-
|
||||
tests/restconf_utils.cpp | 36 ++++++++++++++++++++----------------
|
||||
tests/restconf_utils.h | 12 +++++++++---
|
||||
4 files changed, 38 insertions(+), 21 deletions(-)
|
||||
|
||||
diff --git a/tests/event_watchers.cpp b/tests/event_watchers.cpp
|
||||
index 690338e..26ba951 100644
|
||||
--- a/tests/event_watchers.cpp
|
||||
+++ b/tests/event_watchers.cpp
|
||||
@@ -64,7 +64,7 @@ void RestconfNotificationWatcher::setDataFormat(const libyang::DataFormat dataFo
|
||||
this->dataFormat = dataFormat;
|
||||
}
|
||||
|
||||
-void RestconfNotificationWatcher::operator()(const std::string& msg) const
|
||||
+void RestconfNotificationWatcher::dataEvent(const std::string& msg) const
|
||||
{
|
||||
spdlog::trace("Client received data: {}", msg);
|
||||
auto notifDataNode = ctx.parseOp(msg,
|
||||
@@ -79,3 +79,8 @@ void RestconfNotificationWatcher::operator()(const std::string& msg) const
|
||||
|
||||
data(*dataRoot->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink));
|
||||
}
|
||||
+
|
||||
+void RestconfNotificationWatcher::commentEvent(const std::string& msg) const
|
||||
+{
|
||||
+ comment(msg);
|
||||
+}
|
||||
diff --git a/tests/event_watchers.h b/tests/event_watchers.h
|
||||
index 3b533ba..7022b64 100644
|
||||
--- a/tests/event_watchers.h
|
||||
+++ b/tests/event_watchers.h
|
||||
@@ -39,8 +39,10 @@ struct RestconfNotificationWatcher {
|
||||
|
||||
RestconfNotificationWatcher(const libyang::Context& ctx);
|
||||
void setDataFormat(const libyang::DataFormat dataFormat);
|
||||
- void operator()(const std::string& msg) const;
|
||||
+ void dataEvent(const std::string& msg) const;
|
||||
+ void commentEvent(const std::string& msg) const;
|
||||
|
||||
+ MAKE_CONST_MOCK1(comment, void(const std::string&));
|
||||
MAKE_CONST_MOCK1(data, void(const std::string&));
|
||||
};
|
||||
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index 08b9623..5b1ebe3 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -169,10 +169,11 @@ SSEClient::SSEClient(
|
||||
const std::string& server_address,
|
||||
const std::string& server_port,
|
||||
std::binary_semaphore& requestSent,
|
||||
- const RestconfNotificationWatcher& notification,
|
||||
+ const RestconfNotificationWatcher& eventWatcher,
|
||||
const std::string& uri,
|
||||
const std::map<std::string, std::string>& headers,
|
||||
- const boost::posix_time::seconds silenceTimeout)
|
||||
+ const boost::posix_time::seconds silenceTimeout,
|
||||
+ const ReportIgnoredLines reportIgnoredLines)
|
||||
: client(std::make_shared<ng_client::session>(io, server_address, server_port))
|
||||
, t(io, silenceTimeout)
|
||||
{
|
||||
@@ -191,17 +192,15 @@ SSEClient::SSEClient(
|
||||
}
|
||||
});
|
||||
|
||||
- client->on_connect([&, uri, reqHeaders, silenceTimeout, server_address, server_port](auto) {
|
||||
+ client->on_connect([&, uri, reqHeaders, silenceTimeout, server_address, server_port, reportIgnoredLines](auto) {
|
||||
boost::system::error_code ec;
|
||||
|
||||
auto req = client->submit(ec, "GET", serverAddressAndPort(server_address, server_port) + uri, "", reqHeaders);
|
||||
- req->on_response([&, silenceTimeout](const ng_client::response& res) {
|
||||
+ req->on_response([&, silenceTimeout, reportIgnoredLines](const ng_client::response& res) {
|
||||
requestSent.release();
|
||||
- res.on_data([&, silenceTimeout](const uint8_t* data, std::size_t len) {
|
||||
+ res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
|
||||
// not a production-ready code. In real-life condition the data received in one callback might probably be incomplete
|
||||
- for (const auto& event : parseEvents(std::string(reinterpret_cast<const char*>(data), len))) {
|
||||
- notification(event);
|
||||
- }
|
||||
+ parseEvents(std::string(reinterpret_cast<const char*>(data), len), eventWatcher, reportIgnoredLines);
|
||||
t.expires_from_now(silenceTimeout);
|
||||
});
|
||||
});
|
||||
@@ -218,25 +217,30 @@ SSEClient::SSEClient(
|
||||
});
|
||||
}
|
||||
|
||||
-std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
|
||||
+void SSEClient::parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
|
||||
{
|
||||
- static const std::string prefix = "data:";
|
||||
+ static const std::string dataPrefix = "data:";
|
||||
+ static const std::string ignorePrefix = ":";
|
||||
|
||||
- std::vector<std::string> res;
|
||||
std::istringstream iss(msg);
|
||||
std::string line;
|
||||
std::string event;
|
||||
|
||||
while (std::getline(iss, line)) {
|
||||
- if (line.starts_with(prefix)) {
|
||||
- event += line.substr(prefix.size());
|
||||
- } else if (line.empty()) {
|
||||
- res.emplace_back(std::move(event));
|
||||
+ if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
|
||||
+ eventWatcher.commentEvent(line);
|
||||
+ } else if (line.starts_with(ignorePrefix)) {
|
||||
+ continue;
|
||||
+ } else if (line.starts_with(dataPrefix)) {
|
||||
+ event += line.substr(dataPrefix.size());
|
||||
+ } else if (line.empty() && !event.empty()) {
|
||||
+ eventWatcher.dataEvent(event);
|
||||
event.clear();
|
||||
+ } else if (line.empty()) {
|
||||
+ continue;
|
||||
} else {
|
||||
CAPTURE(msg);
|
||||
FAIL("Unprefixed response");
|
||||
}
|
||||
}
|
||||
- return res;
|
||||
}
|
||||
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
|
||||
index 9efe398..9dde10b 100644
|
||||
--- a/tests/restconf_utils.h
|
||||
+++ b/tests/restconf_utils.h
|
||||
@@ -88,17 +88,23 @@ struct SSEClient {
|
||||
std::shared_ptr<ng_client::session> client;
|
||||
boost::asio::deadline_timer t;
|
||||
|
||||
+ enum class ReportIgnoredLines {
|
||||
+ No,
|
||||
+ Yes,
|
||||
+ };
|
||||
+
|
||||
SSEClient(
|
||||
boost::asio::io_service& io,
|
||||
const std::string& server_address,
|
||||
const std::string& server_port,
|
||||
std::binary_semaphore& requestSent,
|
||||
- const RestconfNotificationWatcher& notification,
|
||||
+ const RestconfNotificationWatcher& eventWatcher,
|
||||
const std::string& uri,
|
||||
const std::map<std::string, std::string>& headers,
|
||||
- const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1)); // test code; the server should respond "soon"
|
||||
+ const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
|
||||
+ const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
|
||||
|
||||
- static std::vector<std::string> parseEvents(const std::string& msg);
|
||||
+ static void parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||
};
|
||||
|
||||
#define PREPARE_LOOP_WITH_EXCEPTIONS \
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From 086cfe1847e406df90852dc3cb52ece3d0cbe22b Mon Sep 17 00:00:00 2001
|
||||
From 5becffe8a1dd47c8836ce1800a1b72acdf86021f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 12 May 2025 14:56:54 +0200
|
||||
Subject: [PATCH 06/13] http: send keep-alive pings from EventStream
|
||||
Subject: [PATCH 10/38] http: send keep-alive pings from EventStream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The nghttp2-asio server disconnects client after 60 seconds of no
|
||||
@@ -28,7 +31,7 @@ to the server when the server sends these "keep-alive comments".
|
||||
[3] https://html.spec.whatwg.org/multipage/server-sent-events.html#authoring-notes
|
||||
|
||||
Change-Id: I57e510d0b61ac7ed032c582779780c64768b7d53
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/clock.cpp | 4 +-
|
||||
src/http/EventStream.cpp | 44 +++++++++++++--
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From 06e2751e4da052195db96aaa90549fba398208bd Mon Sep 17 00:00:00 2001
|
||||
From ff4ff1c193083feca76d9f0f4485e4b175c373c2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 23 Jul 2025 14:27:26 +0200
|
||||
Subject: [PATCH 07/13] refactor: event streams use named constructors
|
||||
Subject: [PATCH 11/38] refactor: event streams use named constructors
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This commit refactors the EventStream and NotificationStream to use
|
||||
@@ -14,7 +17,7 @@ named constructors which perform the double initialization themselves.
|
||||
This way, the code is more readable and the intention should be clearer.
|
||||
|
||||
Change-Id: Iac96c49c20670dfe924d7c8db33328ed9c2fc9dd
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/clock.cpp | 3 +--
|
||||
src/http/EventStream.cpp | 20 ++++++++++++++++++++
|
||||
+6
-3
@@ -1,13 +1,16 @@
|
||||
From e98e079d0fdedf48facbfcf187950946c1ffed7a Mon Sep 17 00:00:00 2001
|
||||
From f4602a03adc9134a9b7a9d338e900b40557da6c4 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 7 Aug 2025 12:14:02 +0200
|
||||
Subject: [PATCH 08/13] refactor: a better convention for weak_from_this->lock
|
||||
Subject: [PATCH 12/38] refactor: a better convention for weak_from_this->lock
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 44 ++++++++++++++++++++--------------------
|
||||
1 file changed, 22 insertions(+), 22 deletions(-)
|
||||
@@ -1,78 +0,0 @@
|
||||
From ba43271df7cc83806a08d6937719544786fd331d Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Sun, 28 Sep 2025 06:04:12 +0200
|
||||
Subject: [PATCH 13/13] Add '--log-level LEVEL' command line option
|
||||
Organization: Wires
|
||||
|
||||
The default 'trace' log level is quite verbose for production systems.
|
||||
This commit changes the default to 'info' and adds a command line option
|
||||
to control the log level.
|
||||
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
src/restconf/main.cpp | 27 +++++++++++++++++++++++++--
|
||||
1 file changed, 25 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/main.cpp b/src/restconf/main.cpp
|
||||
index 477e846..f253421 100644
|
||||
--- a/src/restconf/main.cpp
|
||||
+++ b/src/restconf/main.cpp
|
||||
@@ -26,11 +26,12 @@
|
||||
static const char usage[] =
|
||||
R"(Rousette - RESTCONF server
|
||||
Usage:
|
||||
- rousette [--syslog] [--timeout <SECONDS>] [--help]
|
||||
+ rousette [--syslog] [--timeout <SECONDS>] [--log-level <LEVEL>] [--help]
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
-t --timeout <SECONDS> Change default timeout in sysrepo (if not set, use sysrepo internal).
|
||||
--syslog Log to syslog.
|
||||
+ --log-level <LEVEL> Set log level (trace, debug, info, warn, error, critical, off) [default: info].
|
||||
)";
|
||||
#ifdef HAVE_SYSTEMD
|
||||
|
||||
@@ -75,10 +76,32 @@ int main(int argc, char* argv [])
|
||||
{
|
||||
auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true,""/* version */, true);
|
||||
auto timeout = std::chrono::milliseconds{0};
|
||||
+ auto logLevel = spdlog::level::info;
|
||||
|
||||
if (args["--timeout"]) {
|
||||
timeout = std::chrono::milliseconds{args["--timeout"].asLong() * 1000};
|
||||
}
|
||||
+
|
||||
+ if (args["--log-level"]) {
|
||||
+ auto levelStr = args["--log-level"].asString();
|
||||
+ if (levelStr == "trace") {
|
||||
+ logLevel = spdlog::level::trace;
|
||||
+ } else if (levelStr == "debug") {
|
||||
+ logLevel = spdlog::level::debug;
|
||||
+ } else if (levelStr == "info") {
|
||||
+ logLevel = spdlog::level::info;
|
||||
+ } else if (levelStr == "warn" || levelStr == "warning") {
|
||||
+ logLevel = spdlog::level::warn;
|
||||
+ } else if (levelStr == "error" || levelStr == "err") {
|
||||
+ logLevel = spdlog::level::err;
|
||||
+ } else if (levelStr == "critical") {
|
||||
+ logLevel = spdlog::level::critical;
|
||||
+ } else if (levelStr == "off") {
|
||||
+ logLevel = spdlog::level::off;
|
||||
+ } else {
|
||||
+ throw std::runtime_error("Invalid log level: " + levelStr + ". Valid levels: trace, debug, info, warn, error, critical, off");
|
||||
+ }
|
||||
+ }
|
||||
if (args["--syslog"].asBool()) {
|
||||
auto syslog_sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("rousette", LOG_PID, LOG_USER, true);
|
||||
auto logger = std::make_shared<spdlog::logger>("rousette", syslog_sink);
|
||||
@@ -94,7 +117,7 @@ int main(int argc, char* argv [])
|
||||
auto logger = std::make_shared<spdlog::logger>("rousette", stdout_sink);
|
||||
spdlog::set_default_logger(logger);
|
||||
}
|
||||
- spdlog::set_level(spdlog::level::trace);
|
||||
+ spdlog::set_level(logLevel);
|
||||
|
||||
/* We will parse URIs using boost::spirit's alnum/alpha/... matchers which are locale-dependent.
|
||||
* Let's use something stable no matter what the system is using
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+6
-3
@@ -1,12 +1,15 @@
|
||||
From de7c55a1e8d3dda20ee3dc408612f2ddb309e888 Mon Sep 17 00:00:00 2001
|
||||
From 4e9b535a59861f25c0602eaa1fc39126d7cd9899 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 7 Aug 2025 12:21:54 +0200
|
||||
Subject: [PATCH 09/13] fix a possible bad_weak_ptr exception
|
||||
Subject: [PATCH 13/38] fix a possible bad_weak_ptr exception
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I8c7f7a943a1d848f15527988cb76c2a0a10089e6
|
||||
Fixes: 4eae6200 (close long-lived connections on SIGTERM)
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From 0f034d6da3e6d0a91bbe09717cb33aaf3a3ed747 Mon Sep 17 00:00:00 2001
|
||||
From 6bca750f866b5b14c4d9c3da68e5c8f1e4eee36c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 19 May 2025 12:11:09 +0200
|
||||
Subject: [PATCH 10/13] http: add optional callbacks to EventStream
|
||||
Subject: [PATCH 14/38] http: add optional callbacks to EventStream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
In some cases, it might be useful to have a callback that is called when
|
||||
@@ -11,7 +14,7 @@ implementation where we would like to clean up stuff after client
|
||||
disconnects.
|
||||
|
||||
Change-Id: Icfc2959e38b812b7c18f45976415209b29151c7b
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 18 +++++++++++++++---
|
||||
src/http/EventStream.h | 10 ++++++++--
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From d72adb3da31cb509e64c8f33174e375fa9468b0b Mon Sep 17 00:00:00 2001
|
||||
From f6ee629de8abef42a24c42b185052b0c8e78bd6b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 19 May 2025 12:23:16 +0200
|
||||
Subject: [PATCH 11/13] restconf: add internal RPC handler dispatcher
|
||||
Subject: [PATCH 15/38] restconf: add internal RPC handler dispatcher
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This code adds a new internal RPC handler dispatcher that checks if user
|
||||
@@ -11,7 +14,7 @@ So far, there are no RPCs processed, but support for the
|
||||
ietf-subscribed-notifications:establish-subscription RPC is coming soon.
|
||||
|
||||
Change-Id: I99121a511011229e4098f95e91601b39d333444a
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 24 ++++++++++++++++++++----
|
||||
tests/restconf-rpc.cpp | 18 ++++++++++++++++++
|
||||
@@ -0,0 +1,120 @@
|
||||
From b7966613b43b01402c9f0af286a0b3237161779d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 2 Sep 2025 15:33:43 +0200
|
||||
Subject: [PATCH 16/38] tests: processing incomplete events in SSE client
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
During my testing I noticed that I obtained incomplete message in
|
||||
on_data callback. The message was split into two callbacks.
|
||||
This commit fixes the FIXME by buffering the data and processing
|
||||
a complete event, once available.
|
||||
|
||||
Change-Id: Ied07e69e8b518f20fcc82134a4c041e7ec3a06d6
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf_utils.cpp | 53 +++++++++++++++++++++++-----------------
|
||||
tests/restconf_utils.h | 3 ++-
|
||||
2 files changed, 33 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index 5b1ebe3..740b1fa 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -199,8 +199,8 @@ SSEClient::SSEClient(
|
||||
req->on_response([&, silenceTimeout, reportIgnoredLines](const ng_client::response& res) {
|
||||
requestSent.release();
|
||||
res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
|
||||
- // not a production-ready code. In real-life condition the data received in one callback might probably be incomplete
|
||||
- parseEvents(std::string(reinterpret_cast<const char*>(data), len), eventWatcher, reportIgnoredLines);
|
||||
+ dataBuffer.append(std::string(reinterpret_cast<const char*>(data), len));
|
||||
+ parseEvents(eventWatcher, reportIgnoredLines);
|
||||
t.expires_from_now(silenceTimeout);
|
||||
});
|
||||
});
|
||||
@@ -217,30 +217,39 @@ SSEClient::SSEClient(
|
||||
});
|
||||
}
|
||||
|
||||
-void SSEClient::parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
|
||||
+void SSEClient::parseEvents(const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines)
|
||||
{
|
||||
static const std::string dataPrefix = "data:";
|
||||
static const std::string ignorePrefix = ":";
|
||||
|
||||
- std::istringstream iss(msg);
|
||||
- std::string line;
|
||||
- std::string event;
|
||||
-
|
||||
- while (std::getline(iss, line)) {
|
||||
- if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
|
||||
- eventWatcher.commentEvent(line);
|
||||
- } else if (line.starts_with(ignorePrefix)) {
|
||||
- continue;
|
||||
- } else if (line.starts_with(dataPrefix)) {
|
||||
- event += line.substr(dataPrefix.size());
|
||||
- } else if (line.empty() && !event.empty()) {
|
||||
- eventWatcher.dataEvent(event);
|
||||
- event.clear();
|
||||
- } else if (line.empty()) {
|
||||
- continue;
|
||||
- } else {
|
||||
- CAPTURE(msg);
|
||||
- FAIL("Unprefixed response");
|
||||
+ std::size_t pos = 0;
|
||||
+ constexpr auto EVENT_SEPARATOR = "\n\n"; // FIXME: Not a production-ready code; does not deal with all possible newline combinations of CR and LF
|
||||
+
|
||||
+ while ((pos = dataBuffer.find(EVENT_SEPARATOR)) != std::string::npos) {
|
||||
+ // extract event
|
||||
+ auto rawEvent = dataBuffer.substr(0, pos + std::char_traits<char>::length(EVENT_SEPARATOR));
|
||||
+ std::istringstream stream(rawEvent);
|
||||
+ dataBuffer.erase(0, pos + std::char_traits<char>::length(EVENT_SEPARATOR));
|
||||
+
|
||||
+ // split on newlines
|
||||
+ std::string line;
|
||||
+ std::string event;
|
||||
+ while (std::getline(stream, line)) {
|
||||
+ if (line.starts_with(ignorePrefix) && reportIgnoredLines == ReportIgnoredLines::Yes) {
|
||||
+ eventWatcher.commentEvent(line);
|
||||
+ } else if (line.starts_with(ignorePrefix)) {
|
||||
+ continue;
|
||||
+ } else if (line.starts_with(dataPrefix)) {
|
||||
+ event += line.substr(dataPrefix.size());
|
||||
+ } else if (line.empty() && !event.empty()) {
|
||||
+ eventWatcher.dataEvent(event);
|
||||
+ event.clear();
|
||||
+ } else if (line.empty()) {
|
||||
+ continue;
|
||||
+ } else {
|
||||
+ CAPTURE(rawEvent);
|
||||
+ FAIL("Unprefixed response");
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
|
||||
index 9dde10b..c35df54 100644
|
||||
--- a/tests/restconf_utils.h
|
||||
+++ b/tests/restconf_utils.h
|
||||
@@ -87,6 +87,7 @@ void setupRealNacm(sysrepo::Session session);
|
||||
struct SSEClient {
|
||||
std::shared_ptr<ng_client::session> client;
|
||||
boost::asio::deadline_timer t;
|
||||
+ std::string dataBuffer;
|
||||
|
||||
enum class ReportIgnoredLines {
|
||||
No,
|
||||
@@ -104,7 +105,7 @@ struct SSEClient {
|
||||
const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
|
||||
const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
|
||||
|
||||
- static void parseEvents(const std::string& msg, const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||
+ void parseEvents(const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||
};
|
||||
|
||||
#define PREPARE_LOOP_WITH_EXCEPTIONS \
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+6
-3
@@ -1,7 +1,10 @@
|
||||
From 86ea8a1fdc2cb8baa1edbd32bd4f8b45acdef816 Mon Sep 17 00:00:00 2001
|
||||
From 1067e05674633b97d64b428686aff44822230c5f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 8 Sep 2025 20:24:15 +0200
|
||||
Subject: [PATCH 12/13] EventStream: fix possible heap-use-after-free
|
||||
Subject: [PATCH 17/38] EventStream: fix possible heap-use-after-free
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I have started getting heap-use-after-free errors from sanitizers (see
|
||||
@@ -61,7 +64,7 @@ before actually resuming the response.
|
||||
(...)
|
||||
|
||||
Change-Id: Ifdb1f8610cacffca3bb49da17aa9b1d267cdd472
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 17 ++++++++++++++++-
|
||||
1 file changed, 16 insertions(+), 1 deletion(-)
|
||||
@@ -0,0 +1,52 @@
|
||||
From fff342945eb3c8deb6f7aa67aca54f9c9f87f3ec Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 11 Sep 2025 18:56:31 +0200
|
||||
Subject: [PATCH 18/38] Use a working version of nghttp2-asio
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The latest upstream version does not build on Boost 1.87, and there's a
|
||||
nasty crash in the destructor which led to a nice little heap of hair on
|
||||
our desks. It's time to fork that thing.
|
||||
|
||||
Link: https://github.com/nghttp2/nghttp2-asio/pull/9
|
||||
Link: https://github.com/nghttp2/nghttp2-asio/pull/25
|
||||
Change-Id: Iec9619ce45e7e76f0781d39966d0b6c7cc6fa778
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 2 +-
|
||||
README.md | 3 ++-
|
||||
2 files changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index e5a87b6..a994f34 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -6,7 +6,7 @@
|
||||
required-projects: &projects
|
||||
- CzechLight/dependencies
|
||||
- name: github/nghttp2/nghttp2-asio
|
||||
- override-checkout: main
|
||||
+ override-checkout: cesnet/main
|
||||
vars:
|
||||
zuul_work_dir: "{{ zuul.projects[zuul.project.canonical_hostname + '/CzechLight/rousette'].src_dir }}"
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
index a42c64d..437afd3 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -84,7 +84,8 @@ In practical terms, this means that the NACM access rules for the following XPat
|
||||
|
||||
## Dependencies
|
||||
|
||||
-- [nghttp2-asio](https://github.com/nghttp2/nghttp2-asio) - asynchronous C++ library for HTTP/2
|
||||
+- [nghttp2-asio](https://github.com/CESNET/nghttp2-asio) - asynchronous C++ library for HTTP/2
|
||||
+ - we strongly recommend using [our fork](https://github.com/CESNET/nghttp2-asio) which appears to be better maintained, and which contains important bugfixes which have not been merged upstream yet
|
||||
- [sysrepo-cpp](https://github.com/sysrepo/sysrepo-cpp) - object-oriented bindings of the [*sysrepo*](https://github.com/sysrepo/sysrepo) library
|
||||
- [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang*
|
||||
- [PAM](http://www.linux-pam.org/) - for authentication
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
From ffc52b2e454ae4ac4fa1948cb76a9469026d453e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 6 Oct 2025 22:35:27 +0200
|
||||
Subject: [PATCH 19/38] ping: use a monotonic timer
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The deadline_timer uses system's UTC clock which is not monotonic (i.e.,
|
||||
it jumps when the wall clock is adjusted). Let's switch to a monotonic
|
||||
timer.
|
||||
|
||||
Given that boost::asio::steady_timer::expires_from_now() is marked as
|
||||
deprecated, use the other function.
|
||||
|
||||
Change-Id: I40383721ecb0f12bfcb3f638124a32150a29bf48
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 2 +-
|
||||
src/http/EventStream.h | 4 ++--
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index f97d953..280addd 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -180,7 +180,7 @@ void EventStream::enqueue(const std::string& fieldName, const std::string& what)
|
||||
|
||||
void EventStream::start_ping()
|
||||
{
|
||||
- ping.expires_from_now(boost::posix_time::seconds(m_keepAlivePingInterval.count()));
|
||||
+ ping.expires_after(m_keepAlivePingInterval);
|
||||
ping.async_wait([weak = weak_from_this()](const boost::system::error_code& ec) {
|
||||
auto myself = weak.lock();
|
||||
if (!myself) {
|
||||
diff --git a/src/http/EventStream.h b/src/http/EventStream.h
|
||||
index 87d8c82..f9f0dd6 100644
|
||||
--- a/src/http/EventStream.h
|
||||
+++ b/src/http/EventStream.h
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
-#include <boost/asio/deadline_timer.hpp>
|
||||
+#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/signals2.hpp>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
@@ -50,7 +50,7 @@ private:
|
||||
};
|
||||
|
||||
State state = WaitingForEvents;
|
||||
- boost::asio::deadline_timer ping;
|
||||
+ boost::asio::steady_timer ping;
|
||||
std::list<std::string> queue;
|
||||
mutable std::mutex mtx; // for `state` and `queue`
|
||||
boost::signals2::scoped_connection eventSub, terminateSub;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
From 4493316404f31354ea18bc92a1706a3834ad972b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 6 Oct 2025 22:50:11 +0200
|
||||
Subject: [PATCH 20/38] tests: use a monotonic timer
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I89cdd08082b025643dac81788ca174d3c2177089
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-eventstream.cpp | 2 +-
|
||||
tests/restconf_utils.cpp | 6 +++---
|
||||
tests/restconf_utils.h | 4 ++--
|
||||
3 files changed, 6 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/tests/restconf-eventstream.cpp b/tests/restconf-eventstream.cpp
|
||||
index ad1040d..ecb86e3 100644
|
||||
--- a/tests/restconf-eventstream.cpp
|
||||
+++ b/tests/restconf-eventstream.cpp
|
||||
@@ -101,7 +101,7 @@ TEST_CASE("Event stream tests")
|
||||
netconfWatcher,
|
||||
"/streams/NETCONF/JSON",
|
||||
std::map<std::string, std::string>{AUTH_ROOT},
|
||||
- boost::posix_time::seconds{5},
|
||||
+ std::chrono::seconds{5},
|
||||
SSEClient::ReportIgnoredLines::Yes);
|
||||
|
||||
RUN_LOOP_WITH_EXCEPTIONS;
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index 740b1fa..cb4e26a 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -172,10 +172,10 @@ SSEClient::SSEClient(
|
||||
const RestconfNotificationWatcher& eventWatcher,
|
||||
const std::string& uri,
|
||||
const std::map<std::string, std::string>& headers,
|
||||
- const boost::posix_time::seconds silenceTimeout,
|
||||
+ const std::chrono::seconds silenceTimeout,
|
||||
const ReportIgnoredLines reportIgnoredLines)
|
||||
: client(std::make_shared<ng_client::session>(io, server_address, server_port))
|
||||
- , t(io, silenceTimeout)
|
||||
+ , t(io, std::chrono::seconds(silenceTimeout))
|
||||
{
|
||||
ng::header_map reqHeaders;
|
||||
for (const auto& [name, value] : headers) {
|
||||
@@ -201,7 +201,7 @@ SSEClient::SSEClient(
|
||||
res.on_data([&, silenceTimeout, reportIgnoredLines](const uint8_t* data, std::size_t len) {
|
||||
dataBuffer.append(std::string(reinterpret_cast<const char*>(data), len));
|
||||
parseEvents(eventWatcher, reportIgnoredLines);
|
||||
- t.expires_from_now(silenceTimeout);
|
||||
+ t.expires_after(std::chrono::seconds(silenceTimeout));
|
||||
});
|
||||
});
|
||||
|
||||
diff --git a/tests/restconf_utils.h b/tests/restconf_utils.h
|
||||
index c35df54..a5676f4 100644
|
||||
--- a/tests/restconf_utils.h
|
||||
+++ b/tests/restconf_utils.h
|
||||
@@ -86,7 +86,7 @@ void setupRealNacm(sysrepo::Session session);
|
||||
|
||||
struct SSEClient {
|
||||
std::shared_ptr<ng_client::session> client;
|
||||
- boost::asio::deadline_timer t;
|
||||
+ boost::asio::steady_timer t;
|
||||
std::string dataBuffer;
|
||||
|
||||
enum class ReportIgnoredLines {
|
||||
@@ -102,7 +102,7 @@ struct SSEClient {
|
||||
const RestconfNotificationWatcher& eventWatcher,
|
||||
const std::string& uri,
|
||||
const std::map<std::string, std::string>& headers,
|
||||
- const boost::posix_time::seconds silenceTimeout = boost::posix_time::seconds(1), // test code; the server should respond "soon"
|
||||
+ const std::chrono::seconds silenceTimeout = std::chrono::seconds{1}, // test code; the server should respond "soon"
|
||||
const ReportIgnoredLines reportIgnoredLines = ReportIgnoredLines::No);
|
||||
|
||||
void parseEvents(const RestconfNotificationWatcher& eventWatcher, const ReportIgnoredLines reportIgnoredLines);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
From 8077a63ac97529d56782d2315f23c44f6036b85f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 23 Oct 2025 18:17:37 +0200
|
||||
Subject: [PATCH 21/38] port to libyang v4
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
- the prefix of the "ietf-netconf-with-defaults" module has changed,
|
||||
- RPC parsing needs a flag to reject unknown input data nodes,
|
||||
- due to the "printed context" mess, we now need an extra flag to ensure
|
||||
that we can still access the YANG schema source.
|
||||
|
||||
The last item is interesting. We could also read directly from the
|
||||
on-disk location, but that looks a bit meh. It's also a bit annoying
|
||||
that one has to change this thing over a lot of places, but I think that
|
||||
this is much nicer than trying to force a change over an existing
|
||||
connection (e.g., via a connection-specific call in the Server::Server
|
||||
ctor which might hit some internal locks depending on how the sysrepo
|
||||
state look like).
|
||||
|
||||
I was considering adding a wrapper for `ly_ctx_get_options()` and a
|
||||
check in Server::Server, but then I decided that I won't bother.
|
||||
|
||||
Oh, and this "disable printed context" is also needed for the RESTCONF
|
||||
*client* which is used in the unit test. The `lyd_parse_op` cannot parse
|
||||
notifications *iff* the `notifications` module from RFC 5277 is missing
|
||||
*and* when the libyang context does not have the "parsed" info from the
|
||||
`ietf-yang-types` available. The printed context, as provided by
|
||||
sysrepo, does not have that data available. This is all papered over as
|
||||
long as the context flag setting works, which is the case with
|
||||
post-4.2.4 version of sysrepo (still untagged). Yay.
|
||||
|
||||
We still set these flags to a single value in all of the unit tests and
|
||||
in the main() as well to maintain the remaining bits of our sanity, of
|
||||
course.
|
||||
|
||||
Change-Id: I142f650affb53411b5c1dd81311400a2aa6dec20
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8990
|
||||
Bug: https://github.com/CESNET/libyang/pull/2448
|
||||
Depends-on: https://github.com/sysrepo/sysrepo/commit/6dc5641762962b93d54a54443e2fd43aa319a7a6
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 6 +++---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
src/restconf/main.cpp | 4 ++++
|
||||
tests/restconf-defaults.cpp | 2 ++
|
||||
tests/restconf-delete.cpp | 2 ++
|
||||
tests/restconf-eventstream.cpp | 1 +
|
||||
tests/restconf-nacm.cpp | 2 ++
|
||||
tests/restconf-notifications.cpp | 1 +
|
||||
tests/restconf-plain-patch.cpp | 2 ++
|
||||
tests/restconf-reading.cpp | 12 +++++++-----
|
||||
tests/restconf-rpc.cpp | 2 ++
|
||||
tests/restconf-writing.cpp | 2 ++
|
||||
tests/restconf-yang-patch.cpp | 2 ++
|
||||
tests/restconf-yang-schema.cpp | 2 ++
|
||||
14 files changed, 33 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c0304af..1223ded 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,9 +74,9 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=6)
|
||||
-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=4)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=7)
|
||||
+pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=5)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||
pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 74c06f7..633f0bb 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -487,7 +487,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
auto [parent, rpcNode] = ctx.newPath2(requestCtx->restconfRequest.path);
|
||||
|
||||
if (!requestCtx->payload.empty()) {
|
||||
- rpcNode->parseOp(requestCtx->payload, *requestCtx->dataFormat.request, libyang::OperationType::RpcRestconf);
|
||||
+ rpcNode->parseOp(requestCtx->payload, *requestCtx->dataFormat.request, libyang::OperationType::RpcRestconf, libyang::ParseOptions::Strict);
|
||||
}
|
||||
|
||||
std::optional<libyang::DataNode> rpcReply;
|
||||
diff --git a/src/restconf/main.cpp b/src/restconf/main.cpp
|
||||
index 477e846..b1e2bc6 100644
|
||||
--- a/src/restconf/main.cpp
|
||||
+++ b/src/restconf/main.cpp
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <docopt.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sysrepo-cpp/Session.hpp>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
static const char usage[] =
|
||||
R"(Rousette - RESTCONF server
|
||||
@@ -103,6 +104,9 @@ int main(int argc, char* argv [])
|
||||
throw std::runtime_error("Could not set locale C.UTF-8");
|
||||
}
|
||||
|
||||
+ // schema access is required
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
+
|
||||
auto conn = sysrepo::Connection{};
|
||||
auto server = rousette::restconf::Server{conn, "::1", "10080", timeout};
|
||||
|
||||
diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp
|
||||
index 89083f1..e783e59 100644
|
||||
--- a/tests/restconf-defaults.cpp
|
||||
+++ b/tests/restconf-defaults.cpp
|
||||
@@ -9,6 +9,7 @@
|
||||
static const auto SERVER_PORT = "10087";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
#include "tests/event_watchers.h"
|
||||
@@ -18,6 +19,7 @@ TEST_CASE("default handling")
|
||||
trompeloeil::sequence seq1;
|
||||
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
diff --git a/tests/restconf-delete.cpp b/tests/restconf-delete.cpp
|
||||
index 4818ff3..3b716bd 100644
|
||||
--- a/tests/restconf-delete.cpp
|
||||
+++ b/tests/restconf-delete.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
static const auto SERVER_PORT = "10086";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
#include "tests/event_watchers.h"
|
||||
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10086";
|
||||
TEST_CASE("deleting data")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
diff --git a/tests/restconf-eventstream.cpp b/tests/restconf-eventstream.cpp
|
||||
index ecb86e3..a099f54 100644
|
||||
--- a/tests/restconf-eventstream.cpp
|
||||
+++ b/tests/restconf-eventstream.cpp
|
||||
@@ -29,6 +29,7 @@ TEST_CASE("Event stream tests")
|
||||
|
||||
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 7799fe8..0e06d56 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -10,11 +10,13 @@ static const auto SERVER_PORT = "10082";
|
||||
#include "tests/aux-utils.h"
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
|
||||
TEST_CASE("NACM")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
diff --git a/tests/restconf-notifications.cpp b/tests/restconf-notifications.cpp
|
||||
index 04131d7..ac355d8 100644
|
||||
--- a/tests/restconf-notifications.cpp
|
||||
+++ b/tests/restconf-notifications.cpp
|
||||
@@ -29,6 +29,7 @@ TEST_CASE("NETCONF notification streams")
|
||||
|
||||
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
diff --git a/tests/restconf-plain-patch.cpp b/tests/restconf-plain-patch.cpp
|
||||
index 34813d1..c7e035d 100644
|
||||
--- a/tests/restconf-plain-patch.cpp
|
||||
+++ b/tests/restconf-plain-patch.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
static const auto SERVER_PORT = "10089";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
#include "tests/event_watchers.h"
|
||||
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10089";
|
||||
TEST_CASE("Plain patch")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index 4f0d2ee..b32a598 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -10,12 +10,14 @@ static const auto SERVER_PORT = "10081";
|
||||
#include "tests/aux-utils.h"
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/event_watchers.h"
|
||||
|
||||
TEST_CASE("reading data")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
@@ -794,7 +796,7 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true,
|
||||
"@enabled": {
|
||||
- "ietf-netconf-with-defaults:default": true
|
||||
+ "default:default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -803,7 +805,7 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true,
|
||||
"@enabled": {
|
||||
- "ietf-netconf-with-defaults:default": true
|
||||
+ "default:default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -868,7 +870,7 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true,
|
||||
"@enabled": {
|
||||
- "ietf-netconf-with-defaults:default": true
|
||||
+ "default:default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -877,7 +879,7 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true,
|
||||
"@enabled": {
|
||||
- "ietf-netconf-with-defaults:default": true
|
||||
+ "default:default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -932,7 +934,7 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true,
|
||||
"@enabled": {
|
||||
- "ietf-netconf-with-defaults:default": true
|
||||
+ "default:default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp
|
||||
index 43aaed4..bcbd397 100644
|
||||
--- a/tests/restconf-rpc.cpp
|
||||
+++ b/tests/restconf-rpc.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
static const auto SERVER_PORT = "10084";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
#include "tests/event_watchers.h"
|
||||
@@ -33,6 +34,7 @@ std::map<std::string, std::string> nodesToMap(libyang::DataNode node)
|
||||
TEST_CASE("invoking actions and rpcs")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
SUBSCRIBE_MODULE(sub1, srSess, "example");
|
||||
diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index d37ab03..3797b72 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
static const auto SERVER_PORT = "10083";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
#include "tests/event_watchers.h"
|
||||
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10083";
|
||||
TEST_CASE("writing data")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
diff --git a/tests/restconf-yang-patch.cpp b/tests/restconf-yang-patch.cpp
|
||||
index 2b35c59..19e0405 100644
|
||||
--- a/tests/restconf-yang-patch.cpp
|
||||
+++ b/tests/restconf-yang-patch.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
static const auto SERVER_PORT = "10090";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
#include "tests/event_watchers.h"
|
||||
@@ -16,6 +17,7 @@ static const auto SERVER_PORT = "10090";
|
||||
TEST_CASE("YANG patch")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
diff --git a/tests/restconf-yang-schema.cpp b/tests/restconf-yang-schema.cpp
|
||||
index 6e374b1..b56367d 100644
|
||||
--- a/tests/restconf-yang-schema.cpp
|
||||
+++ b/tests/restconf-yang-schema.cpp
|
||||
@@ -9,12 +9,14 @@
|
||||
static const auto SERVER_PORT = "10085";
|
||||
#include <nghttp2/asio_http2.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
#include "restconf/Server.h"
|
||||
#include "tests/aux-utils.h"
|
||||
|
||||
TEST_CASE("obtaining YANG schemas")
|
||||
{
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
From b4bf655502d949a5f351e6acd313f47eecefb062 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 13 Oct 2025 12:26:46 +0200
|
||||
Subject: [PATCH 22/38] restconf: prevent lock order inversion
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The locked section in EventStream's implementation of nghttp's on_close
|
||||
callback. Without doing that, we might get a lock order inversion in
|
||||
case somebody tries to lock the mutex inside onClientDisconnectedCb. And
|
||||
this would indeed happen with the next patch. If that is applied, then
|
||||
there are indeed two different mutexes involved and the lock order
|
||||
inversion might happen.
|
||||
|
||||
- Case 1: A thread acquires the mutex (let's call it M1) in
|
||||
DynamicNotificationHTTPStream::awaitNextNotification and
|
||||
then sends the notification signal, which acquires the mutex inside
|
||||
EventStream (mutex M2).
|
||||
|
||||
- Case 2: the nghttp's on_close callback acquired M2 and
|
||||
then proceeds to call onClientDisconnectedCb which, in our case,
|
||||
acquires M1.
|
||||
|
||||
One can view this as a fixup of commit 6bca750 ("http: add optional
|
||||
callbacks to EventStream"), I am not sure.
|
||||
|
||||
See-also: 6bca750f866b5b14c4d9c3da68e5c8f1e4eee36c ("http: add optional callbacks to EventStream")
|
||||
Change-Id: Ib6626c127ff2eb5feb1d17dece28e70265749df4
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/http/EventStream.cpp | 14 +++++++++-----
|
||||
1 file changed, 9 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 280addd..9f19030 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -82,11 +82,15 @@ void EventStream::activate()
|
||||
|
||||
res.on_close([myself](const auto ec) {
|
||||
spdlog::debug("{}: closed ({})", myself->peer, nghttp2_http2_strerror(ec));
|
||||
- std::lock_guard lock{myself->mtx};
|
||||
- myself->ping.cancel();
|
||||
- myself->eventSub.disconnect();
|
||||
- myself->terminateSub.disconnect();
|
||||
- myself->state = Closed;
|
||||
+
|
||||
+ {
|
||||
+ std::lock_guard lock{myself->mtx};
|
||||
+ myself->ping.cancel();
|
||||
+ myself->eventSub.disconnect();
|
||||
+ myself->terminateSub.disconnect();
|
||||
+ myself->state = Closed;
|
||||
+ }
|
||||
+
|
||||
if (myself->onClientDisconnectedCb) {
|
||||
myself->onClientDisconnectedCb();
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
From 27cbe6750bdfc9f346421e55ebe555b8c036d77c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 11 Nov 2025 15:01:21 +0100
|
||||
Subject: [PATCH 23/38] refactor: do not pollute scope with unnecessary
|
||||
variables
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The parent variable was not used in the function, it contributed
|
||||
nothing.
|
||||
|
||||
Change-Id: I5b9b1a077d44d088084c3b2b93359ab7464ec8aa
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 633f0bb..77ae1be 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -446,9 +446,9 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, cons
|
||||
const auto rpcPath = rpcInput.path();
|
||||
|
||||
// Is the user authorized to call the operation?
|
||||
- auto [parent, rpcNode] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||
- if (!sess.checkNacmOperation(*rpcNode)) {
|
||||
- throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode->path());
|
||||
+ auto rpcNode = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||
+ if (!sess.checkNacmOperation(*rpcNode.createdNode)) {
|
||||
+ throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode.createdNode->path());
|
||||
}
|
||||
|
||||
if (auto it = handlers.find(rpcPath); it != handlers.end()) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
From 5592ae49cd7607fdcb2c9e554a521b53545e761b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 21 Oct 2025 17:23:44 +0200
|
||||
Subject: [PATCH 24/38] restconf: validate input for internal rpcs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Jan found an issue where invalid input to internal rpcs was accepted. It
|
||||
turns out that the issue was in parsing, but not validating input for
|
||||
internally processed RPCs (those RPCs for subscribed notifications).
|
||||
|
||||
This patch adds the validation using a wrapper for lyd_validate_op from
|
||||
libyang-cpp.
|
||||
|
||||
I shuffled a little bit of the code around together with this change to
|
||||
make it cleaner.
|
||||
Also, to make the code generic, we store the name of the internal RPC
|
||||
and a XPath for fetching data required for validation (see
|
||||
lyd_validate_op docs). The code validates the input data before processing
|
||||
the RPC.
|
||||
|
||||
Change-Id: I55296895e6993b4ca27d21ce2a64ec2d159a35fc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 36 +++++++++++++++++++++++++++++-------
|
||||
1 file changed, 29 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 77ae1be..7e8db58 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -438,9 +438,12 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
return {editNode, replacementNode};
|
||||
}
|
||||
|
||||
-std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, const libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||
+std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||
{
|
||||
- using InternalRPCHandler = std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)>;
|
||||
+ struct InternalRPCHandler {
|
||||
+ std::optional<std::string> validationDataXPath; ///< XPath to data used for RPC input validation
|
||||
+ std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)> rpcHandler; // The function that processes the RPC
|
||||
+ };
|
||||
const std::map<std::string, InternalRPCHandler> handlers;
|
||||
|
||||
const auto rpcPath = rpcInput.path();
|
||||
@@ -451,13 +454,32 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, cons
|
||||
throw ErrorResponse(403, "application", "access-denied", "Access denied.", rpcNode.createdNode->path());
|
||||
}
|
||||
|
||||
- if (auto it = handlers.find(rpcPath); it != handlers.end()) {
|
||||
- auto [parent, rpcOutput] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||
- it->second(sess, requestEncoding, rpcInput, *rpcOutput);
|
||||
- return *parent;
|
||||
+ auto handlerIt = handlers.find(rpcPath);
|
||||
+ if (handlerIt == handlers.end()) {
|
||||
+ throw ErrorResponse(501, "application", "operation-not-supported", "Unsupported RPC call to " + rpcPath, rpcPath);
|
||||
+ }
|
||||
+
|
||||
+ // RPC input validation.
|
||||
+ sess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
+ auto validationData = handlerIt->second.validationDataXPath ? sess.getData(*handlerIt->second.validationDataXPath) : std::nullopt;
|
||||
+
|
||||
+ try {
|
||||
+ libyang::validateOp(rpcInput, validationData, libyang::OperationType::RpcRestconf);
|
||||
+ } catch (const libyang::ErrorWithCode& exc) {
|
||||
+ const auto errors = sess.getContext().getErrors();
|
||||
+
|
||||
+ if (!errors.empty()) {
|
||||
+ const auto& error = errors.back();
|
||||
+ throw ErrorResponse(400, "protocol", "invalid-value", error.message, error.dataPath.value_or(rpcInput.path()));
|
||||
+ } else {
|
||||
+ // Fallback if no detailed error is available
|
||||
+ throw ErrorResponse(400, "protocol", "invalid-value", "Input validation failed"s, rpcInput.path());
|
||||
+ }
|
||||
}
|
||||
|
||||
- throw ErrorResponse(501, "application", "operation-not-supported", "Unsupported RPC call to " + rpcPath, rpcPath);
|
||||
+ auto [parent, rpcOutput] = sess.getContext().newPath2(rpcPath, std::nullopt);
|
||||
+ handlerIt->second.rpcHandler(sess, requestEncoding, rpcInput, *rpcOutput);
|
||||
+ return *parent;
|
||||
}
|
||||
|
||||
void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
From c2413237501eea3125fe31a9701919d14402737f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 11 Nov 2025 17:30:33 +0100
|
||||
Subject: [PATCH 25/38] tests: allow read from parts
|
||||
ietf-subscribed-notifications for anonymous user
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The subsequent commits will add more tests for subscribed notifications.
|
||||
Because the validation of
|
||||
ietf-subscribed-notifications:establish-subscription will require read
|
||||
access to ietf-subscribed-notifications:filters, we need to allow the
|
||||
access for users.
|
||||
|
||||
Change-Id: I50e49c8c60b7e6be47c51f3d8cd546fbb6f49294
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-reading.cpp | 5 ++++-
|
||||
tests/restconf_utils.cpp | 4 ++++
|
||||
2 files changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index b32a598..b11bc48 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -110,6 +110,7 @@ TEST_CASE("reading data")
|
||||
]
|
||||
}
|
||||
},
|
||||
+ "ietf-subscribed-notifications:filters": {},
|
||||
"ietf-system:system": {
|
||||
"contact": "contact",
|
||||
"hostname": "hostname",
|
||||
@@ -168,6 +169,7 @@ TEST_CASE("reading data")
|
||||
]
|
||||
}
|
||||
},
|
||||
+ "ietf-subscribed-notifications:filters": {},
|
||||
"ietf-system:system": {
|
||||
"contact": "contact",
|
||||
"hostname": "hostname",
|
||||
@@ -195,7 +197,8 @@ TEST_CASE("reading data")
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true"
|
||||
},
|
||||
- "example:ordered-lists": {}
|
||||
+ "example:ordered-lists": {},
|
||||
+ "ietf-subscribed-notifications:filters": {}
|
||||
}
|
||||
)"});
|
||||
}
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index cb4e26a..80dca05 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -156,6 +156,10 @@ void setupRealNacm(sysrepo::Session session)
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/action", "permit");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/access-operations", "exec");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/rpc-name", "establish-subscription");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/module-name", "ietf-subscribed-notifications");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/action", "permit");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/access-operations", "read");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='17']/path", "/ietf-subscribed-notifications:filters");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/module-name", "*");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/action", "deny");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,679 @@
|
||||
From aa04b0a99fb51aed998aa9f1ab0a527cc09e647f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 19 May 2025 12:11:09 +0200
|
||||
Subject: [PATCH 26/38] restconf: establish-subscription RPC for subscribed
|
||||
notifications
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is a very bare-bones implementation of the subscribed
|
||||
notifications concept from RFC 8639 [1]. We only support the
|
||||
establish-subscription RPC to the NETCONF event stream.
|
||||
|
||||
This patch *does not* ship the code which implements the endpoint for
|
||||
client connection because this patch is already too large and the
|
||||
endpoint is not strictly necessary for the RPC to work. It will come in
|
||||
the follow-up patch.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/rfc8639/
|
||||
|
||||
Change-Id: I0217e5abc56cfd73859dbcc610fb1f342dc33a10
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 8 +-
|
||||
src/restconf/DynamicSubscriptions.cpp | 177 ++++++++++++++
|
||||
src/restconf/DynamicSubscriptions.h | 57 +++++
|
||||
src/restconf/Server.cpp | 25 +-
|
||||
src/restconf/Server.h | 2 +
|
||||
tests/restconf-subscribed-notifications.cpp | 253 ++++++++++++++++++++
|
||||
6 files changed, 513 insertions(+), 9 deletions(-)
|
||||
create mode 100644 src/restconf/DynamicSubscriptions.cpp
|
||||
create mode 100644 src/restconf/DynamicSubscriptions.h
|
||||
create mode 100644 tests/restconf-subscribed-notifications.cpp
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 1223ded..18af5ba 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -110,6 +110,7 @@ add_library(rousette-auth STATIC
|
||||
target_link_libraries(rousette-auth PUBLIC spdlog::spdlog PkgConfig::SYSREPO-CPP PkgConfig::PAM rousette-auth-pam PkgConfig::nghttp2)
|
||||
|
||||
add_library(rousette-restconf STATIC
|
||||
+ src/restconf/DynamicSubscriptions.cpp
|
||||
src/restconf/NotificationStream.cpp
|
||||
src/restconf/Server.cpp
|
||||
src/restconf/YangSchemaLocations.cpp
|
||||
@@ -211,7 +212,11 @@ if(BUILD_TESTING)
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf@2017-01-26.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-monitoring@2017-01-26.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-yang-patch@2017-02-22.yang
|
||||
- --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/iana-if-type@2014-05-08.yang
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||
@@ -228,6 +233,7 @@ if(BUILD_TESTING)
|
||||
rousette_test(NAME restconf-plain-patch LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
rousette_test(NAME restconf-yang-patch LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
rousette_test(NAME restconf-eventstream LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
+ rousette_test(NAME restconf-subscribed-notifications LIBRARIES rousette-restconf FIXTURE common-models WRAP_PAM)
|
||||
set(nested-models
|
||||
${common-models}
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/root-mod.yang)
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
new file mode 100644
|
||||
index 0000000..7b13990
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -0,0 +1,177 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ *
|
||||
+*/
|
||||
+#include <boost/uuid/uuid_io.hpp>
|
||||
+#include <fmt/ostream.h>
|
||||
+#include <libyang-cpp/Time.hpp>
|
||||
+#include <nghttp2/asio_http2_server.h>
|
||||
+#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/exception.hpp>
|
||||
+#include "restconf/DynamicSubscriptions.h"
|
||||
+#include "restconf/Exceptions.h"
|
||||
+
|
||||
+namespace {
|
||||
+
|
||||
+/** @brief Parses the YANG date-and-time attribute from the RPC input, if present
|
||||
+ *
|
||||
+ * @param rpcInput The RPC input node.
|
||||
+ * @param path The path to the YANG leaf.
|
||||
+ */
|
||||
+std::optional<sysrepo::NotificationTimeStamp> optionalTime(const libyang::DataNode& rpcInput, const std::string& path)
|
||||
+{
|
||||
+ if (auto stopTimeNode = rpcInput.findPath(path)) {
|
||||
+ return libyang::fromYangTimeFormat<sysrepo::NotificationTimeStamp::clock>(stopTimeNode->asTerm().valueStr());
|
||||
+ }
|
||||
+
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
+libyang::DataFormat getEncoding(const libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||
+{
|
||||
+ /* FIXME: So far we allow only encode-json or encode-xml encoding values and not their derived values.
|
||||
+ * We do not know what those derived values might mean and how do they change the meaning of the encoding leaf.
|
||||
+ */
|
||||
+ if (auto encodingNode = rpcInput.findPath("encoding")) {
|
||||
+ const auto encodingStr = encodingNode->asTerm().valueStr();
|
||||
+ if (encodingStr == "ietf-subscribed-notifications:encode-json") {
|
||||
+ return libyang::DataFormat::JSON;
|
||||
+ } else if (encodingStr == "ietf-subscribed-notifications:encode-xml") {
|
||||
+ return libyang::DataFormat::XML;
|
||||
+ } else {
|
||||
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Unsupported encoding in establish-subscription: '" + encodingStr + "'. Currently we support only 'encode-xml' and 'encode-json' identities.");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return requestEncoding;
|
||||
+}
|
||||
+
|
||||
+sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, const libyang::DataNode& rpcInput)
|
||||
+{
|
||||
+ auto streamNode = rpcInput.findPath("stream");
|
||||
+
|
||||
+ if (!streamNode) {
|
||||
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream is required");
|
||||
+ }
|
||||
+
|
||||
+ if (rpcInput.findPath("stream-filter-name")) {
|
||||
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream filtering is not supported");
|
||||
+ }
|
||||
+
|
||||
+ auto stopTime = optionalTime(rpcInput, "stop-time");
|
||||
+
|
||||
+ return session.subscribeNotifications(
|
||||
+ std::nullopt /* TODO xpath filter */,
|
||||
+ streamNode->asTerm().valueStr(),
|
||||
+ stopTime,
|
||||
+ std::nullopt /* TODO replayStart */);
|
||||
+}
|
||||
+}
|
||||
+
|
||||
+namespace rousette::restconf {
|
||||
+
|
||||
+DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri)
|
||||
+ : m_restconfStreamUri(streamRootUri)
|
||||
+ , m_uuidGenerator(boost::uuids::random_generator())
|
||||
+{
|
||||
+}
|
||||
+
|
||||
+DynamicSubscriptions::~DynamicSubscriptions() = default;
|
||||
+
|
||||
+void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput)
|
||||
+{
|
||||
+ // Generate a new UUID associated with the subscription. The UUID will be used as a part of the URI so that the URI is not predictable (RFC 8650, section 5)
|
||||
+ auto uuid = makeUUID();
|
||||
+
|
||||
+ auto dataFormat = getEncoding(rpcInput, requestEncoding);
|
||||
+
|
||||
+ try {
|
||||
+ auto sub = makeStreamSubscription(session, rpcInput);
|
||||
+
|
||||
+ rpcOutput.newPath("id", std::to_string(sub.subscriptionId()), libyang::CreationOptions::Output);
|
||||
+ rpcOutput.newPath("ietf-restconf-subscribed-notifications:uri", m_restconfStreamUri + "subscribed/" + boost::uuids::to_string(uuid), libyang::CreationOptions::Output);
|
||||
+
|
||||
+ std::lock_guard lock(m_mutex);
|
||||
+ m_subscriptions[uuid] = std::make_shared<SubscriptionData>(
|
||||
+ std::move(sub),
|
||||
+ dataFormat,
|
||||
+ uuid,
|
||||
+ *session.getNacmUser());
|
||||
+ } catch (const sysrepo::ErrorWithCode& e) {
|
||||
+ throw ErrorResponse(400, "application", "invalid-attribute", e.what());
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+void DynamicSubscriptions::terminateSubscription(const uint32_t subId)
|
||||
+{
|
||||
+ std::lock_guard lock(m_mutex);
|
||||
+
|
||||
+ auto it = std::find_if(m_subscriptions.begin(), m_subscriptions.end(), [subId](const auto& entry) {
|
||||
+ return entry.second->subscription.subscriptionId() == subId;
|
||||
+ });
|
||||
+
|
||||
+ if (it == m_subscriptions.end()) {
|
||||
+ spdlog::warn("Requested termination of subscription with id {}, but subscription not found", subId);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ const auto& [uuid, subscriptionData] = *it;
|
||||
+ spdlog::debug("{}: termination requested", fmt::streamed(*subscriptionData));
|
||||
+ subscriptionData->subscription.terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||
+ m_subscriptions.erase(uuid);
|
||||
+}
|
||||
+
|
||||
+/** @brief Returns the subscription data for the given UUID and user.
|
||||
+ *
|
||||
+ * @param uuid The UUID of the subscription.
|
||||
+ * @return A shared pointer to the SubscriptionData object if found and user is the one who established the subscription (or NACM recovery user), otherwise nullptr.
|
||||
+ */
|
||||
+std::shared_ptr<DynamicSubscriptions::SubscriptionData> DynamicSubscriptions::getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user)
|
||||
+{
|
||||
+ std::lock_guard lock(m_mutex);
|
||||
+ if (auto it = m_subscriptions.find(uuid); it != m_subscriptions.end() && (it->second->user == user || user == sysrepo::Session::getNacmRecoveryUser())) {
|
||||
+ return it->second;
|
||||
+ }
|
||||
+
|
||||
+ return nullptr;
|
||||
+}
|
||||
+
|
||||
+boost::uuids::uuid DynamicSubscriptions::makeUUID()
|
||||
+{
|
||||
+ // uuid generator instance accesses must be synchronized (https://www.boost.org/doc/libs/1_88_0/libs/uuid/doc/html/uuid.html#design_notes)
|
||||
+ std::lock_guard lock(m_mutex);
|
||||
+ return m_uuidGenerator();
|
||||
+}
|
||||
+
|
||||
+DynamicSubscriptions::SubscriptionData::SubscriptionData(
|
||||
+ sysrepo::DynamicSubscription sub,
|
||||
+ libyang::DataFormat format,
|
||||
+ boost::uuids::uuid uuid,
|
||||
+ const std::string& user)
|
||||
+ : subscription(std::move(sub))
|
||||
+ , dataFormat(format)
|
||||
+ , uuid(uuid)
|
||||
+ , user(user)
|
||||
+{
|
||||
+ spdlog::debug("{}: created", fmt::streamed(*this));
|
||||
+}
|
||||
+
|
||||
+DynamicSubscriptions::SubscriptionData::~SubscriptionData()
|
||||
+{
|
||||
+ try {
|
||||
+ subscription.terminate();
|
||||
+ } catch (const sysrepo::ErrorWithCode& e) { // Maybe it was already terminated (stop-time).
|
||||
+ spdlog::warn("Failed to terminate {}: {}", fmt::streamed(*this), e.what());
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+std::ostream& operator<<(std::ostream& os, const DynamicSubscriptions::SubscriptionData& sub)
|
||||
+{
|
||||
+ return os << "dynamic subscription (id " << sub.subscription.subscriptionId()
|
||||
+ << ", user " << sub.user
|
||||
+ << ", uuid " << boost::uuids::to_string(sub.uuid)
|
||||
+ << ")";
|
||||
+}
|
||||
+}
|
||||
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||
new file mode 100644
|
||||
index 0000000..366d1a1
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/DynamicSubscriptions.h
|
||||
@@ -0,0 +1,57 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ *
|
||||
+*/
|
||||
+#pragma once
|
||||
+
|
||||
+#include <boost/asio.hpp>
|
||||
+#include <boost/asio/io_context.hpp>
|
||||
+#include <boost/uuid/random_generator.hpp>
|
||||
+#include <map>
|
||||
+#include <memory>
|
||||
+#include <sysrepo-cpp/Subscription.hpp>
|
||||
+
|
||||
+namespace libyang {
|
||||
+enum class DataFormat;
|
||||
+}
|
||||
+
|
||||
+namespace rousette::restconf {
|
||||
+
|
||||
+/** Dynamic subscriptions manager.
|
||||
+ *
|
||||
+ * Stores all dynamic subscriptions and provides a way to retrieve them by the UUID.
|
||||
+ * */
|
||||
+class DynamicSubscriptions {
|
||||
+public:
|
||||
+ struct SubscriptionData : public std::enable_shared_from_this<SubscriptionData> {
|
||||
+ sysrepo::DynamicSubscription subscription;
|
||||
+ libyang::DataFormat dataFormat; ///< Encoding of the notification stream
|
||||
+ boost::uuids::uuid uuid; ///< UUID is part of the GET URI, it identifies subscriptions for clients
|
||||
+ std::string user; ///< User who initiated the establish-subscription RPC
|
||||
+
|
||||
+ SubscriptionData(
|
||||
+ sysrepo::DynamicSubscription sub,
|
||||
+ libyang::DataFormat format,
|
||||
+ boost::uuids::uuid uuid,
|
||||
+ const std::string& user);
|
||||
+ ~SubscriptionData();
|
||||
+ };
|
||||
+
|
||||
+ DynamicSubscriptions(const std::string& streamRootUri);
|
||||
+ ~DynamicSubscriptions();
|
||||
+ std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||
+ void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||
+
|
||||
+private:
|
||||
+ std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||
+ std::string m_restconfStreamUri;
|
||||
+ std::map<boost::uuids::uuid, std::shared_ptr<SubscriptionData>> m_subscriptions;
|
||||
+ boost::uuids::random_generator m_uuidGenerator;
|
||||
+
|
||||
+ void terminateSubscription(const uint32_t subId);
|
||||
+
|
||||
+ boost::uuids::uuid makeUUID();
|
||||
+};
|
||||
+}
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 7e8db58..7287976 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -438,13 +438,16 @@ libyang::CreatedNodes createEditForPutAndPatch(libyang::Context& ctx, const std:
|
||||
return {editNode, replacementNode};
|
||||
}
|
||||
|
||||
-std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding)
|
||||
+std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, libyang::DataNode& rpcInput, const libyang::DataFormat requestEncoding, DynamicSubscriptions& dynamicSubscriptions)
|
||||
{
|
||||
struct InternalRPCHandler {
|
||||
std::optional<std::string> validationDataXPath; ///< XPath to data used for RPC input validation
|
||||
std::function<void(sysrepo::Session&, const libyang::DataFormat, const libyang::DataNode&, libyang::DataNode&)> rpcHandler; // The function that processes the RPC
|
||||
};
|
||||
- const std::map<std::string, InternalRPCHandler> handlers;
|
||||
+ const std::map<std::string, InternalRPCHandler> handlers{
|
||||
+ {"/ietf-subscribed-notifications:establish-subscription",
|
||||
+ {"/ietf-subscribed-notifications:filters", [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.establishSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||
+ };
|
||||
|
||||
const auto rpcPath = rpcInput.path();
|
||||
|
||||
@@ -482,7 +485,7 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, liby
|
||||
return *parent;
|
||||
}
|
||||
|
||||
-void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
+void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout, DynamicSubscriptions& dynamicSubscriptions)
|
||||
{
|
||||
requestCtx->sess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
auto ctx = requestCtx->sess.getContext();
|
||||
@@ -516,7 +519,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
if (requestCtx->restconfRequest.type == RestconfRequest::Type::Execute) {
|
||||
rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout);
|
||||
} else if (requestCtx->restconfRequest.type == RestconfRequest::Type::ExecuteInternal) {
|
||||
- rpcReply = processInternalRPC(requestCtx->sess, *rpcNode, *requestCtx->dataFormat.request);
|
||||
+ rpcReply = processInternalRPC(requestCtx->sess, *rpcNode, *requestCtx->dataFormat.request, dynamicSubscriptions);
|
||||
}
|
||||
|
||||
if (!rpcReply || rpcReply->immediateChildren().empty()) {
|
||||
@@ -902,10 +905,16 @@ std::vector<std::shared_ptr<boost::asio::io_context>> Server::io_services() cons
|
||||
return server->io_services();
|
||||
}
|
||||
|
||||
-Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port, const std::chrono::milliseconds timeout, const std::chrono::seconds keepAlivePingInterval)
|
||||
+Server::Server(
|
||||
+ sysrepo::Connection conn,
|
||||
+ const std::string& address,
|
||||
+ const std::string& port,
|
||||
+ const std::chrono::milliseconds timeout,
|
||||
+ const std::chrono::seconds keepAlivePingInterval)
|
||||
: m_monitoringSession(conn.sessionStart(sysrepo::Datastore::Operational))
|
||||
, nacm(conn)
|
||||
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||
+ , m_dynamicSubscriptions(netconfStreamRoot)
|
||||
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||
{
|
||||
server->num_threads(1); // we only use one thread for the server, so we can call join() right away
|
||||
@@ -917,7 +926,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
{"ietf-netconf", "", {}},
|
||||
{"ietf-yang-library", "2019-01-04", {}},
|
||||
{"ietf-yang-patch", "2017-02-22", {}},
|
||||
- {"ietf-subscribed-notifications", "2019-09-09", {}},
|
||||
+ {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json"}},
|
||||
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||
}) {
|
||||
if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||
@@ -1217,12 +1226,12 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
case RestconfRequest::Type::ExecuteInternal: {
|
||||
auto requestCtx = std::make_shared<RequestContext>(req, res, dataFormat, sess, restconfRequest);
|
||||
|
||||
- req.on_data([requestCtx, timeout, peer=http::peer_from_request(req)](const uint8_t* data, std::size_t length) {
|
||||
+ req.on_data([this, requestCtx, timeout, peer=http::peer_from_request(req)](const uint8_t* data, std::size_t length) {
|
||||
if (length > 0) {
|
||||
requestCtx->payload.append(reinterpret_cast<const char*>(data), length);
|
||||
} else {
|
||||
spdlog::trace("{}: HTTP payload: {}", peer, requestCtx->payload);
|
||||
- WITH_RESTCONF_EXCEPTIONS(processActionOrRPC, rejectWithError)(requestCtx, timeout);
|
||||
+ WITH_RESTCONF_EXCEPTIONS(processActionOrRPC, rejectWithError)(requestCtx, timeout, m_dynamicSubscriptions);
|
||||
}
|
||||
});
|
||||
break;
|
||||
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||
index 0720c8d..c98f736 100644
|
||||
--- a/src/restconf/Server.h
|
||||
+++ b/src/restconf/Server.h
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <sysrepo-cpp/Subscription.hpp>
|
||||
#include "auth/Nacm.h"
|
||||
#include "http/EventStream.h"
|
||||
+#include "restconf/DynamicSubscriptions.h"
|
||||
|
||||
namespace nghttp2::asio_http2::server {
|
||||
class http2;
|
||||
@@ -43,6 +44,7 @@ private:
|
||||
std::optional<sysrepo::Subscription> m_monitoringOperSub;
|
||||
auth::Nacm nacm;
|
||||
std::unique_ptr<nghttp2::asio_http2::server::http2> server;
|
||||
+ DynamicSubscriptions m_dynamicSubscriptions;
|
||||
std::unique_ptr<sr::OpticalEvents> dwdmEvents;
|
||||
using JsonDiffSignal = boost::signals2::signal<void(const std::string& json)>;
|
||||
JsonDiffSignal opticsChange;
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
new file mode 100644
|
||||
index 0000000..3e8e011
|
||||
--- /dev/null
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -0,0 +1,253 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ *
|
||||
+ */
|
||||
+
|
||||
+#include "trompeloeil_doctest.h"
|
||||
+static const auto SERVER_PORT = "10092";
|
||||
+#include <libyang-cpp/Time.hpp>
|
||||
+#include <nghttp2/asio_http2.h>
|
||||
+#include <regex>
|
||||
+#include <spdlog/spdlog.h>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
+#include "restconf/Server.h"
|
||||
+#include "tests/aux-utils.h"
|
||||
+#include "tests/event_watchers.h"
|
||||
+#include "tests/pretty_printers.h"
|
||||
+
|
||||
+#define SEND_NOTIFICATION(DATA) notifSession.sendNotification(*ctx.parseOp(DATA, libyang::DataFormat::JSON, libyang::OperationType::NotificationYang).op, sysrepo::Wait::No);
|
||||
+
|
||||
+constexpr auto uuidV4Regex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}";
|
||||
+
|
||||
+using namespace std::chrono_literals;
|
||||
+using namespace std::string_literals;
|
||||
+
|
||||
+/** @brief Calls establish-subscription rpc, returns the url of the stream associated with the created subscription */
|
||||
+std::string establishSubscription(
|
||||
+ const libyang::Context& ctx,
|
||||
+ const libyang::DataFormat rpcEncoding,
|
||||
+ const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||
+ const std::optional<std::string>& encodingLeafValue)
|
||||
+{
|
||||
+ constexpr auto jsonPrefix = "ietf-subscribed-notifications";
|
||||
+ constexpr auto xmlNamespace = "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications";
|
||||
+
|
||||
+ auto stopTime = libyang::yangTimeFormat(std::chrono::system_clock::now() + 5s, libyang::TimezoneInterpretation::Local);
|
||||
+ std::map<std::string, std::string> requestHeaders;
|
||||
+ ng::header_map expectedHeaders;
|
||||
+
|
||||
+ if (rpcRequestAuthHeader) {
|
||||
+ requestHeaders.insert(*rpcRequestAuthHeader);
|
||||
+ }
|
||||
+
|
||||
+ std::optional<libyang::DataNode> envelope;
|
||||
+ auto rpcTree = ctx.newPath("/ietf-subscribed-notifications:establish-subscription");
|
||||
+ rpcTree.newPath("stream", "NETCONF");
|
||||
+ rpcTree.newPath("stop-time", stopTime);
|
||||
+
|
||||
+ if (encodingLeafValue) {
|
||||
+ rpcTree.newPath("encoding", *encodingLeafValue);
|
||||
+ }
|
||||
+
|
||||
+ switch (rpcEncoding) {
|
||||
+ case libyang::DataFormat::JSON:
|
||||
+ requestHeaders.insert(CONTENT_TYPE_JSON);
|
||||
+ expectedHeaders = jsonHeaders;
|
||||
+ envelope = ctx.newOpaqueJSON({jsonPrefix, jsonPrefix, "input"}, std::nullopt);
|
||||
+ break;
|
||||
+ case libyang::DataFormat::XML:
|
||||
+ requestHeaders.insert(CONTENT_TYPE_XML);
|
||||
+ expectedHeaders = xmlHeaders;
|
||||
+ envelope = ctx.newOpaqueXML({xmlNamespace, jsonPrefix, "input"}, std::nullopt);
|
||||
+ break;
|
||||
+ default:
|
||||
+ FAIL("Unhandled libyang DataFormat");
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ // reconnect everything
|
||||
+ auto data = rpcTree.child();
|
||||
+ data->unlinkWithSiblings();
|
||||
+ envelope->insertChild(*data);
|
||||
+
|
||||
+ auto body = *envelope->printStr(rpcEncoding, libyang::PrintFlags::WithSiblings);
|
||||
+ auto resp = post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", requestHeaders, body);
|
||||
+ REQUIRE(resp.equalStatusCodeAndHeaders(Response{200, expectedHeaders, ""}));
|
||||
+
|
||||
+ auto reply = ctx.newPath("/ietf-subscribed-notifications:establish-subscription");
|
||||
+ REQUIRE(reply.parseOp(resp.data, rpcEncoding, libyang::OperationType::ReplyRestconf).tree);
|
||||
+
|
||||
+ auto urlNode = reply.findPath("ietf-restconf-subscribed-notifications:uri", libyang::InputOutputNodes::Output);
|
||||
+ REQUIRE(urlNode);
|
||||
+
|
||||
+ return urlNode->asTerm().valueStr();
|
||||
+}
|
||||
+
|
||||
+TEST_CASE("RESTCONF subscribed notifications")
|
||||
+{
|
||||
+ trompeloeil::sequence seq1, seq2;
|
||||
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||
+ spdlog::set_level(spdlog::level::trace);
|
||||
+
|
||||
+ auto srConn = sysrepo::Connection{};
|
||||
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
+
|
||||
+ auto nacmGuard = manageNacm(srSess);
|
||||
+ auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||
+ setupRealNacm(srSess);
|
||||
+
|
||||
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
+
|
||||
+ libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ std::optional<std::string> rpcSubscriptionEncoding;
|
||||
+ std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
+
|
||||
+ SECTION("NACM authorization")
|
||||
+ {
|
||||
+ SECTION("Anonymous access for establish-subscription is disabled")
|
||||
+ {
|
||||
+ /* Remove anonymous user's permission to execute RPCs in ietf-subscribed-notifications.
|
||||
+ * Intentionally a SECTION. We can remove NACM rule for a while in order to test access denied.
|
||||
+ * The rule gets automatically restored for the rest of the tests.
|
||||
+ */
|
||||
+ srSess.switchDatastore(sysrepo::Datastore::Running);
|
||||
+ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']");
|
||||
+ srSess.applyChanges();
|
||||
+
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF" } })###")
|
||||
+ == Response{403, jsonHeaders, R"###({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "access-denied",
|
||||
+ "error-path": "/ietf-subscribed-notifications:establish-subscription",
|
||||
+ "error-message": "Access denied."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)###"});
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Invalid establish-subscription requests")
|
||||
+ {
|
||||
+ // stop-time in the past
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "stop-time": "1999-09-09T09:09:09Z" } })###")
|
||||
+ == Response{400, jsonHeaders, R"###({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-attribute",
|
||||
+ "error-message": "Couldn't create notification subscription: SR_ERR_INVAL_ARG\u000A Specified \"stop-time\" is in the past. (SR_ERR_INVAL_ARG)"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)###"});
|
||||
+
|
||||
+ // invalid stream
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "ajsdhauisds" } })###")
|
||||
+ == Response{400, jsonHeaders, R"###({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-attribute",
|
||||
+ "error-message": "Couldn't create notification subscription: SR_ERR_NOT_FOUND\u000A Failed to collect modules to subscribe to, invalid stream and/or XPath filter (Item not found). (SR_ERR_NOT_FOUND)"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)###"});
|
||||
+
|
||||
+ // stream-filter-name is unsupported, but leafref validation triggers first
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "stream-filter-name": "xyz" } })###")
|
||||
+ == Response{400, jsonHeaders, R"###({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "protocol",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-path": "/ietf-subscribed-notifications:establish-subscription/stream-filter-name",
|
||||
+ "error-message": "Invalid leafref value \"xyz\" - no target instance \"/sn:filters/sn:stream-filter/sn:name\" with the same value."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)###"});
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Valid requests")
|
||||
+ {
|
||||
+ SECTION("XML stream")
|
||||
+ {
|
||||
+ netconfWatcher.setDataFormat(libyang::DataFormat::XML);
|
||||
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||
+
|
||||
+ SECTION("Stream encoding inferred from request content-type")
|
||||
+ {
|
||||
+ rpcRequestEncoding = libyang::DataFormat::XML;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Explicitly asked for XML stream encoding")
|
||||
+ {
|
||||
+ rpcSubscriptionEncoding = "encode-xml";
|
||||
+
|
||||
+ SECTION("Request content-type JSON")
|
||||
+ {
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Request content-type XML")
|
||||
+ {
|
||||
+ rpcRequestEncoding = libyang::DataFormat::XML;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ SECTION("JSON stream")
|
||||
+ {
|
||||
+ netconfWatcher.setDataFormat(libyang::DataFormat::JSON);
|
||||
+
|
||||
+ SECTION("NACM: anonymous user cannot read example-notif module")
|
||||
+ {
|
||||
+ rpcRequestAuthHeader = std::nullopt;
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ rpcSubscriptionEncoding = "encode-json";
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Content-type with set encode leaf")
|
||||
+ {
|
||||
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||
+
|
||||
+ SECTION("Stream encoding inferred from request content-type")
|
||||
+ {
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Explicitly asked for JSON stream encoding")
|
||||
+ {
|
||||
+ rpcSubscriptionEncoding = "encode-json";
|
||||
+ SECTION("Request content-type JSON")
|
||||
+ {
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Request content-type XML")
|
||||
+ {
|
||||
+ rpcRequestEncoding = libyang::DataFormat::XML;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ auto uri = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
From e2c8036130a2265476579ffa9f4354741b54bbd5 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 13 Oct 2025 16:10:51 +0200
|
||||
Subject: [PATCH 27/38] refactor: restconf stream request types
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
In commit 97ceef1 ("restconf: refactor uri parser for stream URIs") we
|
||||
prepared the URI parser for the new subscribed stream notifications.
|
||||
What I did not think through was that the subscribed notifications do
|
||||
not have query parameters. However, I refactored the types in a way
|
||||
that the query parameters member is now in the common base type.
|
||||
|
||||
This patch fixes that by editing the types in a way that only the
|
||||
stream notifications will have the query parameters member. The
|
||||
type for subscribed notifications (which will come in a followup patch)
|
||||
will not have it.
|
||||
|
||||
See-also: 97ceef119c900c37bbaa27860c3b43cfa6d69f95 ("restconf: refactor uri parser for stream URIs")
|
||||
Change-Id: Idc1e05f7ff05cea84b5806f60e217605f75e7a9f
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
src/restconf/uri.cpp | 15 ++++++++-------
|
||||
src/restconf/uri.h | 14 +++++++-------
|
||||
src/restconf/uri_impl.h | 2 +-
|
||||
tests/uri-parser.cpp | 12 ++++++------
|
||||
5 files changed, 23 insertions(+), 22 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 7287976..616d7d4 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -1020,7 +1020,7 @@ Server::Server(
|
||||
shutdownRequested,
|
||||
keepAlivePingInterval,
|
||||
sess,
|
||||
- streamRequest.type.encoding,
|
||||
+ streamRequest.encoding,
|
||||
xpathFilter,
|
||||
startTime,
|
||||
stopTime);
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index 956c155..6ee011c 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -58,11 +58,11 @@ const auto resources = x3::rule<class resources, URIPath>{"resources"} =
|
||||
const auto uriGrammar = x3::rule<class grammar, URIPath>{"grammar"} = x3::lit("/restconf") >> resources;
|
||||
|
||||
|
||||
-const auto netconfStream = x3::rule<class netconfStream, RestconfStreamRequest::NetconfStream>{"netconfStream"} =
|
||||
+const auto netconfStream = x3::rule<class netconfStream, NetconfStreamRequest>{"netconfStream"} =
|
||||
x3::lit("/NETCONF") >>
|
||||
((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) |
|
||||
(x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON)));
|
||||
-const auto streamUriGrammar = x3::rule<class grammar, RestconfStreamRequest::NetconfStream>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||
+const auto streamUriGrammar = x3::rule<class grammar, NetconfStreamRequest>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||
|
||||
// clang-format on
|
||||
}
|
||||
@@ -204,9 +204,9 @@ std::optional<queryParams::QueryParams> parseQueryParams(const std::string& quer
|
||||
return ret;
|
||||
}
|
||||
|
||||
-std::optional<RestconfStreamRequest::NetconfStream> parseStreamUri(const std::string& uriPath)
|
||||
+std::optional<RestconfStreamRequest> parseStreamUri(const std::string& uriPath)
|
||||
{
|
||||
- std::optional<RestconfStreamRequest::NetconfStream> ret;
|
||||
+ std::optional<RestconfStreamRequest> ret;
|
||||
|
||||
if (!x3::parse(std::begin(uriPath), std::end(uriPath), streamUriGrammar >> x3::eoi, ret)) {
|
||||
return std::nullopt;
|
||||
@@ -668,8 +668,8 @@ std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> asYangMod
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
-RestconfStreamRequest::NetconfStream::NetconfStream() = default;
|
||||
-RestconfStreamRequest::NetconfStream::NetconfStream(const libyang::DataFormat& encoding)
|
||||
+NetconfStreamRequest::NetconfStreamRequest() = default;
|
||||
+NetconfStreamRequest::NetconfStreamRequest(const libyang::DataFormat& encoding)
|
||||
: encoding(encoding)
|
||||
{
|
||||
}
|
||||
@@ -692,7 +692,8 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
|
||||
|
||||
validateQueryParametersForStream(*queryParameters);
|
||||
|
||||
- return {*type, *queryParameters};
|
||||
+ type->queryParams = *queryParameters;
|
||||
+ return *type;
|
||||
}
|
||||
|
||||
/** @brief Returns a set of allowed HTTP methods for given URI. Usable for the 'allow' header */
|
||||
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||
index d9dd29f..f6c63a7 100644
|
||||
--- a/src/restconf/uri.h
|
||||
+++ b/src/restconf/uri.h
|
||||
@@ -197,16 +197,16 @@ struct RestconfRequest {
|
||||
RestconfRequest(Type type, const boost::optional<ApiIdentifier>& datastore, const std::string& path, const queryParams::QueryParams& queryParams);
|
||||
};
|
||||
|
||||
-struct RestconfStreamRequest {
|
||||
- struct NetconfStream {
|
||||
- libyang::DataFormat encoding;
|
||||
-
|
||||
- NetconfStream();
|
||||
- NetconfStream(const libyang::DataFormat& encoding);
|
||||
- } type;
|
||||
+struct NetconfStreamRequest {
|
||||
+ libyang::DataFormat encoding;
|
||||
queryParams::QueryParams queryParams;
|
||||
+
|
||||
+ NetconfStreamRequest();
|
||||
+ NetconfStreamRequest(const libyang::DataFormat& encoding);
|
||||
};
|
||||
|
||||
+using RestconfStreamRequest = NetconfStreamRequest;
|
||||
+
|
||||
RestconfRequest asRestconfRequest(const libyang::Context& ctx, const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString = "");
|
||||
std::optional<libyang::SchemaNode> asLibyangSchemaNode(const libyang::Context& ctx, const std::vector<PathSegment>& pathSegments);
|
||||
std::pair<std::string, PathSegment> asLibyangPathSplit(const libyang::Context& ctx, const std::string& uriPath);
|
||||
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||
index 00b22f6..bc36cca 100644
|
||||
--- a/src/restconf/uri_impl.h
|
||||
+++ b/src/restconf/uri_impl.h
|
||||
@@ -66,7 +66,7 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::YangModule, name, revision);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
|
||||
|
||||
-BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::RestconfStreamRequest::NetconfStream, encoding);
|
||||
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::NetconfStreamRequest, encoding);
|
||||
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index 23c2b79..38ca80f 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -1082,15 +1082,15 @@ TEST_CASE("URI path parser")
|
||||
using rousette::restconf::RestconfStreamRequest;
|
||||
|
||||
{
|
||||
- auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||
- REQUIRE(type.encoding == libyang::DataFormat::XML);
|
||||
- REQUIRE(queryParams.empty());
|
||||
+ auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||
+ REQUIRE(req.encoding == libyang::DataFormat::XML);
|
||||
+ REQUIRE(req.queryParams.empty());
|
||||
}
|
||||
|
||||
{
|
||||
- auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||
- REQUIRE(type.encoding == libyang::DataFormat::JSON);
|
||||
- REQUIRE(queryParams.empty());
|
||||
+ auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||
+ REQUIRE(req.encoding == libyang::DataFormat::JSON);
|
||||
+ REQUIRE(req.queryParams.empty());
|
||||
}
|
||||
|
||||
REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF", ""),
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,893 @@
|
||||
From b3a55ae0a1ef9e092b0c8f0d7c3dbd7baa708cc8 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 29 Jul 2025 11:39:21 +0200
|
||||
Subject: [PATCH 28/38] restconf: endpoint for subscribed notifications
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This patch creates a new endpoint for the subscribed notification
|
||||
streams in the RESTCONF server. The previous commit added the
|
||||
establish-subscription RPC [1], which returned a stream URI, that was
|
||||
not accessible in that patch. The patch was extra large even without
|
||||
that functionality so I have split it into two commits.
|
||||
|
||||
There is one more catch worth mentioning both here and in the comments:
|
||||
In case sysrepo sends events fast enough it is possible that the
|
||||
function responsible for reading them blocks the event loop and the
|
||||
function responsible for sending the actual data to the client is not
|
||||
called. To mitigate that, I added limit to the number of events read
|
||||
in one go.
|
||||
However, we are not sure if that is enough. Is it possible that the
|
||||
reading function might get called several times in a row? My knowledge
|
||||
of boost::asio is not enough.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/rfc8639/
|
||||
|
||||
Change-Id: I07dcdd3fb9ef4f05f93ae6dda0e0d71094a0bbfc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/restconf/DynamicSubscriptions.cpp | 127 ++++++++++++-
|
||||
src/restconf/DynamicSubscriptions.h | 46 +++++
|
||||
src/restconf/Server.cpp | 51 +++--
|
||||
src/restconf/uri.cpp | 46 ++++-
|
||||
src/restconf/uri.h | 10 +-
|
||||
src/restconf/uri_impl.h | 1 +
|
||||
src/restconf/utils/io.cpp | 30 +++
|
||||
src/restconf/utils/io.h | 12 ++
|
||||
tests/restconf-subscribed-notifications.cpp | 199 ++++++++++++++++++++
|
||||
tests/uri-parser.cpp | 41 +++-
|
||||
11 files changed, 527 insertions(+), 37 deletions(-)
|
||||
create mode 100644 src/restconf/utils/io.cpp
|
||||
create mode 100644 src/restconf/utils/io.h
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 18af5ba..5d3b7ce 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -116,6 +116,7 @@ add_library(rousette-restconf STATIC
|
||||
src/restconf/YangSchemaLocations.cpp
|
||||
src/restconf/uri.cpp
|
||||
src/restconf/utils/dataformat.cpp
|
||||
+ src/restconf/utils/io.cpp
|
||||
src/restconf/utils/sysrepo.cpp
|
||||
src/restconf/utils/yang.cpp
|
||||
)
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
index 7b13990..7f4b171 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <sysrepo-cpp/utils/exception.hpp>
|
||||
#include "restconf/DynamicSubscriptions.h"
|
||||
#include "restconf/Exceptions.h"
|
||||
+#include "restconf/utils/io.h"
|
||||
+#include "restconf/utils/yang.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -119,7 +121,7 @@ void DynamicSubscriptions::terminateSubscription(const uint32_t subId)
|
||||
|
||||
const auto& [uuid, subscriptionData] = *it;
|
||||
spdlog::debug("{}: termination requested", fmt::streamed(*subscriptionData));
|
||||
- subscriptionData->subscription.terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||
+ subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||
m_subscriptions.erase(uuid);
|
||||
}
|
||||
|
||||
@@ -154,14 +156,54 @@ DynamicSubscriptions::SubscriptionData::SubscriptionData(
|
||||
, dataFormat(format)
|
||||
, uuid(uuid)
|
||||
, user(user)
|
||||
+ , state(State::Start)
|
||||
{
|
||||
spdlog::debug("{}: created", fmt::streamed(*this));
|
||||
}
|
||||
|
||||
DynamicSubscriptions::SubscriptionData::~SubscriptionData()
|
||||
{
|
||||
+ terminate();
|
||||
+}
|
||||
+
|
||||
+void DynamicSubscriptions::SubscriptionData::clientDisconnected()
|
||||
+{
|
||||
+ spdlog::debug("{}: client disconnected", fmt::streamed(*this));
|
||||
+
|
||||
+ std::lock_guard lock(mutex);
|
||||
+ if (state == State::Terminating) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ state = State::Start;
|
||||
+}
|
||||
+
|
||||
+void DynamicSubscriptions::SubscriptionData::clientConnected()
|
||||
+{
|
||||
+ spdlog::debug("{}: client connected", fmt::streamed(*this));
|
||||
+ std::lock_guard lock(mutex);
|
||||
+ state = State::ReceiverActive;
|
||||
+}
|
||||
+
|
||||
+bool DynamicSubscriptions::SubscriptionData::isReadyToAcceptClient() const
|
||||
+{
|
||||
+ std::lock_guard lock(mutex);
|
||||
+ return state == State::Start;
|
||||
+}
|
||||
+
|
||||
+void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::string>& reason)
|
||||
+{
|
||||
+ std::lock_guard lock(mutex);
|
||||
+
|
||||
+ // already terminating, do nothing
|
||||
+ if (state == State::Terminating) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ state = State::Terminating;
|
||||
+ spdlog::debug("{}: terminating subscription ({})", fmt::streamed(*this), reason.value_or("<no reason>"));
|
||||
try {
|
||||
- subscription.terminate();
|
||||
+ subscription.terminate(reason);
|
||||
} catch (const sysrepo::ErrorWithCode& e) { // Maybe it was already terminated (stop-time).
|
||||
spdlog::warn("Failed to terminate {}: {}", fmt::streamed(*this), e.what());
|
||||
}
|
||||
@@ -174,4 +216,85 @@ std::ostream& operator<<(std::ostream& os, const DynamicSubscriptions::Subscript
|
||||
<< ", uuid " << boost::uuids::to_string(sub.uuid)
|
||||
<< ")";
|
||||
}
|
||||
+
|
||||
+DynamicSubscriptionHttpStream::DynamicSubscriptionHttpStream(
|
||||
+ const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData)
|
||||
+ : EventStream(
|
||||
+ req,
|
||||
+ res,
|
||||
+ termination,
|
||||
+ *signal,
|
||||
+ keepAlivePingInterval,
|
||||
+ std::nullopt /* no initial event */,
|
||||
+ [this]() { m_subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription"); },
|
||||
+ [this]() { m_subscriptionData->clientDisconnected(); })
|
||||
+ , m_subscriptionData(subscriptionData)
|
||||
+ , m_signal(signal)
|
||||
+ , m_stream(res.io_service(), m_subscriptionData->subscription.fd())
|
||||
+{
|
||||
+}
|
||||
+
|
||||
+DynamicSubscriptionHttpStream::~DynamicSubscriptionHttpStream()
|
||||
+{
|
||||
+ // The stream does not own the file descriptor, sysrepo does. It will be closed when the subscription terminates.
|
||||
+ m_stream.release();
|
||||
+}
|
||||
+
|
||||
+/** @brief Waits for the next notifications and process them */
|
||||
+void DynamicSubscriptionHttpStream::awaitNextNotification()
|
||||
+{
|
||||
+ constexpr auto MAX_EVENTS = 50;
|
||||
+
|
||||
+ m_stream.async_wait(boost::asio::posix::stream_descriptor::wait_read, [this](const boost::system::error_code& err) {
|
||||
+ // Unfortunately wait_read does not return operation_aborted when the file descriptor is closed and poll results in POLLHUP
|
||||
+ if (err == boost::asio::error::operation_aborted || utils::pipeIsClosedAndNoData(m_subscriptionData->subscription.fd())) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ size_t eventsProcessed = 0;
|
||||
+ /* Process all the available notifications, but at most N
|
||||
+ * In case sysrepo is providing the events fast enough, this loop would still run inside the event loop
|
||||
+ * and the event responsible for sending the data to the client would not get to be processed.
|
||||
+ * TODO: Is this enough? What if this async_wait keeps getting called and nothing gets sent?
|
||||
+ */
|
||||
+ while (++eventsProcessed < MAX_EVENTS && utils::pipeHasData(m_subscriptionData->subscription.fd())) {
|
||||
+ std::lock_guard lock(m_subscriptionData->mutex); // sysrepo-cpp's processEvent and terminate is not thread safe
|
||||
+ m_subscriptionData->subscription.processEvent([&](const std::optional<libyang::DataNode>& notificationTree, const sysrepo::NotificationTimeStamp& time) {
|
||||
+ (*m_signal)(rousette::restconf::as_restconf_notification(
|
||||
+ m_subscriptionData->subscription.getSession().getContext(),
|
||||
+ m_subscriptionData->dataFormat,
|
||||
+ *notificationTree,
|
||||
+ time));
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ // and wait for more
|
||||
+ awaitNextNotification();
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+void DynamicSubscriptionHttpStream::activate()
|
||||
+{
|
||||
+ m_subscriptionData->clientConnected();
|
||||
+ EventStream::activate();
|
||||
+ awaitNextNotification();
|
||||
+}
|
||||
+
|
||||
+std::shared_ptr<DynamicSubscriptionHttpStream> DynamicSubscriptionHttpStream::create(
|
||||
+ const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData)
|
||||
+{
|
||||
+ auto signal = std::make_shared<rousette::http::EventStream::EventSignal>();
|
||||
+ auto stream = std::shared_ptr<DynamicSubscriptionHttpStream>(new DynamicSubscriptionHttpStream(req, res, termination, signal, keepAlivePingInterval, subscriptionData));
|
||||
+ stream->activate();
|
||||
+ return stream;
|
||||
+}
|
||||
}
|
||||
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||
index 366d1a1..1874390 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.h
|
||||
+++ b/src/restconf/DynamicSubscriptions.h
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sysrepo-cpp/Subscription.hpp>
|
||||
+#include "http/EventStream.h"
|
||||
|
||||
namespace libyang {
|
||||
enum class DataFormat;
|
||||
@@ -26,17 +27,28 @@ namespace rousette::restconf {
|
||||
class DynamicSubscriptions {
|
||||
public:
|
||||
struct SubscriptionData : public std::enable_shared_from_this<SubscriptionData> {
|
||||
+ mutable std::mutex mutex;
|
||||
sysrepo::DynamicSubscription subscription;
|
||||
libyang::DataFormat dataFormat; ///< Encoding of the notification stream
|
||||
boost::uuids::uuid uuid; ///< UUID is part of the GET URI, it identifies subscriptions for clients
|
||||
std::string user; ///< User who initiated the establish-subscription RPC
|
||||
|
||||
+ enum class State {
|
||||
+ Start, ///< Subscription is ready to be consumed by a client
|
||||
+ ReceiverActive, ///< Subscription is being consumed by a client
|
||||
+ Terminating, ///< Subscription is being terminated by the server shutdown
|
||||
+ } state;
|
||||
+
|
||||
SubscriptionData(
|
||||
sysrepo::DynamicSubscription sub,
|
||||
libyang::DataFormat format,
|
||||
boost::uuids::uuid uuid,
|
||||
const std::string& user);
|
||||
~SubscriptionData();
|
||||
+ void clientDisconnected();
|
||||
+ void clientConnected();
|
||||
+ void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||
+ bool isReadyToAcceptClient() const;
|
||||
};
|
||||
|
||||
DynamicSubscriptions(const std::string& streamRootUri);
|
||||
@@ -54,4 +66,38 @@ private:
|
||||
|
||||
boost::uuids::uuid makeUUID();
|
||||
};
|
||||
+
|
||||
+/** @brief Subscribes to sysrepo's subscribed notification and sends the notifications via HTTP/2 Event stream.
|
||||
+ *
|
||||
+ * @see rousette::http::EventStream
|
||||
+ * @see rousette::http::NotificationStream
|
||||
+ * */
|
||||
+class DynamicSubscriptionHttpStream : public http::EventStream {
|
||||
+public:
|
||||
+ ~DynamicSubscriptionHttpStream();
|
||||
+
|
||||
+ static std::shared_ptr<DynamicSubscriptionHttpStream> create(
|
||||
+ const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData);
|
||||
+
|
||||
+private:
|
||||
+ std::shared_ptr<DynamicSubscriptions::SubscriptionData> m_subscriptionData;
|
||||
+ std::shared_ptr<rousette::http::EventStream::EventSignal> m_signal;
|
||||
+ boost::asio::posix::stream_descriptor m_stream;
|
||||
+
|
||||
+ void awaitNextNotification();
|
||||
+
|
||||
+protected:
|
||||
+ DynamicSubscriptionHttpStream(
|
||||
+ const nghttp2::asio_http2::server::request& req,
|
||||
+ const nghttp2::asio_http2::server::response& res,
|
||||
+ rousette::http::EventStream::Termination& termination,
|
||||
+ std::shared_ptr<rousette::http::EventStream::EventSignal> signal,
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::shared_ptr<DynamicSubscriptions::SubscriptionData>& subscriptionData);
|
||||
+ void activate();
|
||||
+};
|
||||
}
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 616d7d4..2a8ad33 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -1003,27 +1003,40 @@ Server::Server(
|
||||
|
||||
auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
|
||||
|
||||
- if (auto it = streamRequest.queryParams.find("filter"); it != streamRequest.queryParams.end()) {
|
||||
- xpathFilter = std::get<std::string>(it->second);
|
||||
- }
|
||||
+ if (auto *request = std::get_if<SubscribedStreamRequest>(&streamRequest)) {
|
||||
+ if (auto sub = m_dynamicSubscriptions.getSubscriptionForUser(request->uuid, sess.getNacmUser())) {
|
||||
+ if (!sub->isReadyToAcceptClient()) {
|
||||
+ throw ErrorResponse(409, "application", "resource-denied", "There is already another GET request on this subscription.");
|
||||
+ }
|
||||
|
||||
- if (auto it = streamRequest.queryParams.find("start-time"); it != streamRequest.queryParams.end()) {
|
||||
- startTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||
- }
|
||||
- if (auto it = streamRequest.queryParams.find("stop-time"); it != streamRequest.queryParams.end()) {
|
||||
- stopTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||
- }
|
||||
+ DynamicSubscriptionHttpStream::create(req, res, shutdownRequested, keepAlivePingInterval, sub);
|
||||
+ } else {
|
||||
+ throw ErrorResponse(404, "application", "invalid-value", "Subscription not found.");
|
||||
+ }
|
||||
+ } else if (auto *request = std::get_if<NetconfStreamRequest>(&streamRequest)) {
|
||||
+ if (auto it = request->queryParams.find("filter"); it != request->queryParams.end()) {
|
||||
+ xpathFilter = std::get<std::string>(it->second);
|
||||
+ }
|
||||
+ if (auto it = request->queryParams.find("start-time"); it != request->queryParams.end()) {
|
||||
+ startTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||
+ }
|
||||
+ if (auto it = request->queryParams.find("stop-time"); it != request->queryParams.end()) {
|
||||
+ stopTime = libyang::fromYangTimeFormat<std::chrono::system_clock>(std::get<std::string>(it->second));
|
||||
+ }
|
||||
|
||||
- NotificationStream::create(
|
||||
- req,
|
||||
- res,
|
||||
- shutdownRequested,
|
||||
- keepAlivePingInterval,
|
||||
- sess,
|
||||
- streamRequest.encoding,
|
||||
- xpathFilter,
|
||||
- startTime,
|
||||
- stopTime);
|
||||
+ NotificationStream::create(
|
||||
+ req,
|
||||
+ res,
|
||||
+ shutdownRequested,
|
||||
+ keepAlivePingInterval,
|
||||
+ sess,
|
||||
+ request->encoding,
|
||||
+ xpathFilter,
|
||||
+ startTime,
|
||||
+ stopTime);
|
||||
+ } else {
|
||||
+ throw ErrorResponse(500, "protocol", "invalid-value", "Invalid request."); // should not happen
|
||||
+ }
|
||||
} catch (const auth::Error& e) {
|
||||
processAuthError(req, res, e, [&res]() {
|
||||
res.write_head(401, {TEXT_PLAIN, CORS});
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index 6ee011c..99c3d1f 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
|
||||
#include <boost/fusion/include/std_pair.hpp>
|
||||
#include <boost/spirit/home/x3/support/ast/variant.hpp>
|
||||
+#include <boost/uuid/nil_generator.hpp>
|
||||
+#include <boost/uuid/string_generator.hpp>
|
||||
#include <experimental/iterator>
|
||||
#include <libyang-cpp/Enum.hpp>
|
||||
#include <map>
|
||||
@@ -57,12 +59,30 @@ const auto resources = x3::rule<class resources, URIPath>{"resources"} =
|
||||
((x3::lit("/") | x3::eps) /* /restconf/ and /restconf */ >> x3::attr(URIPrefix{URIPrefix::Type::RestconfRoot}) >> x3::attr(std::vector<PathSegment>{}));
|
||||
const auto uriGrammar = x3::rule<class grammar, URIPath>{"grammar"} = x3::lit("/restconf") >> resources;
|
||||
|
||||
+const auto string_to_uuid = [](auto& ctx) {
|
||||
+ try {
|
||||
+ _val(ctx) = boost::uuids::string_generator()(_attr(ctx));
|
||||
+ _pass(ctx) = true;
|
||||
+ } catch (const std::runtime_error&) {
|
||||
+ _pass(ctx) = false;
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+const auto uuid_impl = x3::rule<class uuid_impl, std::string>{"uuid_impl"} =
|
||||
+ x3::repeat(8)[x3::xdigit] >> x3::char_('-') >>
|
||||
+ x3::repeat(4)[x3::xdigit] >> x3::char_('-') >>
|
||||
+ x3::repeat(4)[x3::xdigit] >> x3::char_('-') >>
|
||||
+ x3::repeat(4)[x3::xdigit] >> x3::char_('-') >>
|
||||
+ x3::repeat(12)[x3::xdigit];
|
||||
+const auto uuid = x3::rule<class uuid, boost::uuids::uuid>{"uuid"} = uuid_impl[string_to_uuid];
|
||||
+const auto subscribedStream = x3::rule<class subscribedStream, SubscribedStreamRequest>{"subscribedStream"} = x3::lit("/subscribed") >> "/" >> uuid;
|
||||
|
||||
const auto netconfStream = x3::rule<class netconfStream, NetconfStreamRequest>{"netconfStream"} =
|
||||
x3::lit("/NETCONF") >>
|
||||
((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) |
|
||||
(x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON)));
|
||||
-const auto streamUriGrammar = x3::rule<class grammar, NetconfStreamRequest>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||
+const auto streamUriGrammar = x3::rule<class grammar, std::variant<NetconfStreamRequest, SubscribedStreamRequest>>{"streamsGrammar"} =
|
||||
+ x3::lit("/streams") >> (netconfStream | subscribedStream);
|
||||
|
||||
// clang-format on
|
||||
}
|
||||
@@ -674,6 +694,16 @@ NetconfStreamRequest::NetconfStreamRequest(const libyang::DataFormat& encoding)
|
||||
{
|
||||
}
|
||||
|
||||
+SubscribedStreamRequest::SubscribedStreamRequest()
|
||||
+ : uuid(boost::uuids::nil_uuid())
|
||||
+{
|
||||
+}
|
||||
+
|
||||
+SubscribedStreamRequest::SubscribedStreamRequest(const boost::uuids::uuid& uuid)
|
||||
+ : uuid(uuid)
|
||||
+{
|
||||
+}
|
||||
+
|
||||
RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
|
||||
{
|
||||
if (httpMethod != "GET" && httpMethod != "HEAD") {
|
||||
@@ -685,14 +715,16 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
|
||||
throw ErrorResponse(404, "application", "invalid-value", "Invalid stream");
|
||||
}
|
||||
|
||||
- auto queryParameters = impl::parseQueryParams(uriQueryString);
|
||||
- if (!queryParameters) {
|
||||
- throw ErrorResponse(400, "protocol", "invalid-value", "Query parameters syntax error");
|
||||
- }
|
||||
+ if (auto* request = std::get_if<NetconfStreamRequest>(&type.value())) {
|
||||
+ auto queryParameters = impl::parseQueryParams(uriQueryString);
|
||||
+ if (!queryParameters) {
|
||||
+ throw ErrorResponse(400, "protocol", "invalid-value", "Query parameters syntax error");
|
||||
+ }
|
||||
|
||||
- validateQueryParametersForStream(*queryParameters);
|
||||
+ validateQueryParametersForStream(*queryParameters);
|
||||
+ request->queryParams = *queryParameters;
|
||||
+ }
|
||||
|
||||
- type->queryParams = *queryParameters;
|
||||
return *type;
|
||||
}
|
||||
|
||||
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||
index f6c63a7..7893c6f 100644
|
||||
--- a/src/restconf/uri.h
|
||||
+++ b/src/restconf/uri.h
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <boost/optional.hpp>
|
||||
+#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include <libyang-cpp/Module.hpp>
|
||||
#include <libyang-cpp/SchemaNode.hpp>
|
||||
@@ -205,7 +206,14 @@ struct NetconfStreamRequest {
|
||||
NetconfStreamRequest(const libyang::DataFormat& encoding);
|
||||
};
|
||||
|
||||
-using RestconfStreamRequest = NetconfStreamRequest;
|
||||
+struct SubscribedStreamRequest {
|
||||
+ boost::uuids::uuid uuid;
|
||||
+
|
||||
+ SubscribedStreamRequest();
|
||||
+ SubscribedStreamRequest(const boost::uuids::uuid& uuid);
|
||||
+};
|
||||
+
|
||||
+using RestconfStreamRequest = std::variant<NetconfStreamRequest, SubscribedStreamRequest>;
|
||||
|
||||
RestconfRequest asRestconfRequest(const libyang::Context& ctx, const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString = "");
|
||||
std::optional<libyang::SchemaNode> asLibyangSchemaNode(const libyang::Context& ctx, const std::vector<PathSegment>& pathSegments);
|
||||
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||
index bc36cca..cf57859 100644
|
||||
--- a/src/restconf/uri_impl.h
|
||||
+++ b/src/restconf/uri_impl.h
|
||||
@@ -67,6 +67,7 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
|
||||
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::NetconfStreamRequest, encoding);
|
||||
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::SubscribedStreamRequest, uuid);
|
||||
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
|
||||
diff --git a/src/restconf/utils/io.cpp b/src/restconf/utils/io.cpp
|
||||
new file mode 100644
|
||||
index 0000000..d657096
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/utils/io.cpp
|
||||
@@ -0,0 +1,30 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ */
|
||||
+
|
||||
+#include <poll.h>
|
||||
+#include "restconf/utils/io.h"
|
||||
+
|
||||
+namespace rousette::restconf::utils {
|
||||
+bool pipeHasData(const int fd)
|
||||
+{
|
||||
+ pollfd fds = {
|
||||
+ .fd = fd,
|
||||
+ .events = POLLIN | POLLHUP,
|
||||
+ .revents = 0};
|
||||
+
|
||||
+ return poll(&fds, 1, 0) == 1 && fds.revents & POLLIN;
|
||||
+}
|
||||
+
|
||||
+bool pipeIsClosedAndNoData(const int fd)
|
||||
+{
|
||||
+ pollfd fds = {
|
||||
+ .fd = fd,
|
||||
+ .events = POLLIN | POLLHUP,
|
||||
+ .revents = 0};
|
||||
+
|
||||
+ return poll(&fds, 1, 0) == 1 && fds.revents & POLLHUP && !(fds.revents & POLLIN);
|
||||
+}
|
||||
+}
|
||||
diff --git a/src/restconf/utils/io.h b/src/restconf/utils/io.h
|
||||
new file mode 100644
|
||||
index 0000000..ac9f2f8
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/utils/io.h
|
||||
@@ -0,0 +1,12 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ */
|
||||
+
|
||||
+
|
||||
+namespace rousette::restconf::utils {
|
||||
+
|
||||
+bool pipeHasData(const int fd);
|
||||
+bool pipeIsClosedAndNoData(const int fd);
|
||||
+}
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
index 3e8e011..11dd992 100644
|
||||
--- a/tests/restconf-subscribed-notifications.cpp
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -91,6 +91,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
auto srConn = sysrepo::Connection{};
|
||||
auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
@@ -99,6 +100,19 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||
setupRealNacm(srSess);
|
||||
|
||||
+ // parent for nested notification
|
||||
+ srSess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
+ srSess.setItem("/example:tlc/list[name='k1']/choice1", "something must me here");
|
||||
+ srSess.applyChanges();
|
||||
+ std::vector<std::string> notificationsJSON{
|
||||
+ R"({"example:eventA":{"message":"blabla","progress":11}})",
|
||||
+ R"({"example:eventB":{}})",
|
||||
+ R"({"example-notif:something-happened":{}})",
|
||||
+ R"({"example:eventA":{"message":"almost finished","progress":99}})",
|
||||
+ R"({"example:tlc":{"list":[{"name":"k1","notif":{"message":"nested"}}]}})",
|
||||
+ };
|
||||
+ std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
+
|
||||
RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
|
||||
libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
@@ -132,6 +146,78 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
}
|
||||
)###"});
|
||||
}
|
||||
+
|
||||
+ SECTION("User DWDM establishes subscription")
|
||||
+ {
|
||||
+ std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||
+ auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||
+
|
||||
+ std::map<std::string, std::string> headers;
|
||||
+
|
||||
+ SECTION("Users who cannot GET")
|
||||
+ {
|
||||
+ SECTION("anonymous") { }
|
||||
+ SECTION("norules") { headers.insert(AUTH_NORULES); }
|
||||
+ REQUIRE(get(uri, headers) == Response{404, plaintextHeaders, "Subscription not found."});
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Users who can GET")
|
||||
+ {
|
||||
+ SECTION("root") { headers.insert(AUTH_ROOT); }
|
||||
+ SECTION("dwdm") { headers.insert(AUTH_DWDM); }
|
||||
+ REQUIRE(head(uri, headers) == Response{200, eventStreamHeaders, ""});
|
||||
+ }
|
||||
+
|
||||
+ SECTION("GET on the same subscription concurently")
|
||||
+ {
|
||||
+ std::map<std::string, std::string> headers;
|
||||
+ std::string response;
|
||||
+ int status;
|
||||
+
|
||||
+ SECTION("Allowed users")
|
||||
+ {
|
||||
+ response = "There is already another GET request on this subscription.";
|
||||
+ status = 409;
|
||||
+
|
||||
+ SECTION("Same user")
|
||||
+ {
|
||||
+ headers.insert(AUTH_DWDM);
|
||||
+ }
|
||||
+ SECTION("Different user")
|
||||
+ {
|
||||
+ headers.insert(AUTH_ROOT);
|
||||
+ }
|
||||
+ }
|
||||
+ SECTION("Disallowed user")
|
||||
+ {
|
||||
+ headers.insert(AUTH_NORULES);
|
||||
+ response = "Subscription not found.";
|
||||
+ status = 404;
|
||||
+ }
|
||||
+
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS
|
||||
+ auto thr = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+ REQUIRE(get(uri, headers) == Response{status, plaintextHeaders, response});
|
||||
+ }));
|
||||
+
|
||||
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_DWDM});
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("GET on the same subscription sequentially")
|
||||
+ {
|
||||
+ boost::asio::io_service io;
|
||||
+ {
|
||||
+ std::binary_semaphore requestSent(0); // SSEClient needs to notify when the request is sent, but we don't care about it here.
|
||||
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_DWDM});
|
||||
+ }
|
||||
+ {
|
||||
+ std::binary_semaphore requestSent(0);
|
||||
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_DWDM});
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
SECTION("Invalid establish-subscription requests")
|
||||
@@ -209,6 +295,12 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
rpcRequestEncoding = libyang::DataFormat::XML;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[2], seq2);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||
}
|
||||
|
||||
SECTION("JSON stream")
|
||||
@@ -220,6 +312,11 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
rpcRequestAuthHeader = std::nullopt;
|
||||
rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
rpcSubscriptionEncoding = "encode-json";
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||
}
|
||||
|
||||
SECTION("Content-type with set encode leaf")
|
||||
@@ -244,10 +341,112 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
rpcRequestEncoding = libyang::DataFormat::XML;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[2], seq2);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||
}
|
||||
}
|
||||
|
||||
auto uri = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||
+
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS
|
||||
+
|
||||
+ // Here's how these two threads work together.
|
||||
+ //
|
||||
+ // The main test thread (this one):
|
||||
+ // - sets up all the expectations
|
||||
+ // - has an HTTP client which calls/spends the expectations based on the incoming SSE data
|
||||
+ // - blocks while it runs the ASIO event loop
|
||||
+ //
|
||||
+ // The auxiliary thread (the notificationThread):
|
||||
+ // - waits for the HTTP client having issued its long-lived HTTP GET
|
||||
+ // - sends a bunch of notifications to sysrepo
|
||||
+ // - waits for all the expectations getting spent, and then terminates the ASIO event loop cleanly
|
||||
+
|
||||
+ std::jthread notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+
|
||||
+ SEND_NOTIFICATION(notificationsJSON[0]);
|
||||
+ SEND_NOTIFICATION(notificationsJSON[1]);
|
||||
+ std::this_thread::sleep_for(500ms); // simulate some delays; server might be slow in creating notifications, client should still remain connected
|
||||
+ SEND_NOTIFICATION(notificationsJSON[2]);
|
||||
+ SEND_NOTIFICATION(notificationsJSON[3]);
|
||||
+ std::this_thread::sleep_for(500ms);
|
||||
+ SEND_NOTIFICATION(notificationsJSON[4]);
|
||||
+
|
||||
+ // once the main thread has processed all the notifications, stop the ASIO loop
|
||||
+ waitForCompletionAndBitMore(seq1);
|
||||
+ waitForCompletionAndBitMore(seq2);
|
||||
+ }));
|
||||
+
|
||||
+ std::map<std::string, std::string> streamHeaders;
|
||||
+ if (rpcRequestAuthHeader) {
|
||||
+ streamHeaders.insert(*rpcRequestAuthHeader);
|
||||
+ }
|
||||
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, streamHeaders);
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
}
|
||||
}
|
||||
+
|
||||
+TEST_CASE("Terminating server under notification load")
|
||||
+{
|
||||
+ trompeloeil::sequence seq1;
|
||||
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||
+ spdlog::set_level(spdlog::level::trace);
|
||||
+
|
||||
+ auto srConn = sysrepo::Connection{};
|
||||
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
+
|
||||
+ auto nacmGuard = manageNacm(srSess);
|
||||
+ auto server = std::make_unique<rousette::restconf::Server>(srConn, SERVER_ADDRESS, SERVER_PORT);
|
||||
+ setupRealNacm(srSess);
|
||||
+
|
||||
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
+ constexpr auto notif = R"({"example:eventB":{}})";
|
||||
+
|
||||
+ std::optional<std::string> rpcSubscriptionEncoding;
|
||||
+ std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
+
|
||||
+ std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||
+ auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||
+
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
+
|
||||
+ std::atomic<bool> serverRunning = true;
|
||||
+ std::atomic<size_t> notificationsReceived = 0;
|
||||
+ constexpr size_t NOTIFICATIONS_BEFORE_TERMINATE = 50;
|
||||
+
|
||||
+ auto notificationThread = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+
|
||||
+ while (serverRunning) {
|
||||
+ SEND_NOTIFICATION(notif);
|
||||
+ }
|
||||
+ }));
|
||||
+
|
||||
+ auto serverShutdownThread = std::jthread([&]() {
|
||||
+ while (notificationsReceived <= NOTIFICATIONS_BEFORE_TERMINATE) {
|
||||
+ // A condition variable would be more elegant, but this is just a test...
|
||||
+ std::this_thread::sleep_for(20ms);
|
||||
+ }
|
||||
+ server.reset();
|
||||
+ serverRunning = false;
|
||||
+ });
|
||||
+
|
||||
+ netconfWatcher.setDataFormat(libyang::DataFormat::JSON);
|
||||
+ REQUIRE_CALL(netconfWatcher, data(notif)).IN_SEQUENCE(seq1).TIMES(AT_LEAST(NOTIFICATIONS_BEFORE_TERMINATE)).LR_SIDE_EFFECT(notificationsReceived++);
|
||||
+ REQUIRE_CALL(netconfWatcher, data(R"({ietf-subscribed-notifications:subscription-terminated":{"id":1,"reason":"no-such-subscription"}})")).IN_SEQUENCE(seq1).TIMES(AT_MOST(1));
|
||||
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {auth});
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+}
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index 38ca80f..2ad244f 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include "trompeloeil_doctest.h"
|
||||
+#include <boost/uuid/string_generator.hpp>
|
||||
#include <experimental/iterator>
|
||||
#include <string>
|
||||
#include <sysrepo-cpp/Connection.hpp>
|
||||
@@ -1042,8 +1043,11 @@ TEST_CASE("URI path parser")
|
||||
|
||||
SECTION("filter")
|
||||
{
|
||||
+ using rousette::restconf::NetconfStreamRequest;
|
||||
+
|
||||
auto resp = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "filter=/asd");
|
||||
- REQUIRE(resp.queryParams == QueryParams({{"filter", "/asd"s}}));
|
||||
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(resp));
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(resp).queryParams == QueryParams({{"filter", "/asd"s}}));
|
||||
|
||||
REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "GET", "/restconf/data/example:ordered-lists", "filter=something"),
|
||||
serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'filter' can be used only with streams").c_str(),
|
||||
@@ -1052,8 +1056,11 @@ TEST_CASE("URI path parser")
|
||||
|
||||
SECTION("start-time")
|
||||
{
|
||||
+ using rousette::restconf::NetconfStreamRequest;
|
||||
+
|
||||
auto resp = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "start-time=2024-01-01T01:01:01Z");
|
||||
- REQUIRE(resp.queryParams == QueryParams({{"start-time", "2024-01-01T01:01:01Z"s}}));
|
||||
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(resp));
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(resp).queryParams == QueryParams({{"start-time", "2024-01-01T01:01:01Z"s}}));
|
||||
|
||||
REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "GET", "/restconf/data/example:ordered-lists", "start-time=2024-01-01T01:01:01Z"),
|
||||
serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'start-time' can be used only with streams").c_str(),
|
||||
@@ -1062,8 +1069,11 @@ TEST_CASE("URI path parser")
|
||||
|
||||
SECTION("stop-time")
|
||||
{
|
||||
+ using rousette::restconf::NetconfStreamRequest;
|
||||
+
|
||||
auto resp = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "stop-time=2024-01-01T01:01:01Z");
|
||||
- REQUIRE(resp.queryParams == QueryParams({{"stop-time", "2024-01-01T01:01:01Z"s}}));
|
||||
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(resp));
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(resp).queryParams == QueryParams({{"stop-time", "2024-01-01T01:01:01Z"s}}));
|
||||
|
||||
REQUIRE_THROWS_WITH_AS(asRestconfRequest(ctx, "GET", "/restconf/data/example:ordered-lists", "stop-time=2024-01-01T01:01:01Z"),
|
||||
serializeErrorResponse(400, "protocol", "invalid-value", "Query parameter 'stop-time' can be used only with streams").c_str(),
|
||||
@@ -1079,18 +1089,27 @@ TEST_CASE("URI path parser")
|
||||
SECTION("Streams")
|
||||
{
|
||||
using rousette::restconf::asRestconfStreamRequest;
|
||||
- using rousette::restconf::RestconfStreamRequest;
|
||||
+ using rousette::restconf::NetconfStreamRequest;
|
||||
+ using rousette::restconf::SubscribedStreamRequest;
|
||||
|
||||
{
|
||||
auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||
- REQUIRE(req.encoding == libyang::DataFormat::XML);
|
||||
- REQUIRE(req.queryParams.empty());
|
||||
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(req));
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(req).encoding == libyang::DataFormat::XML);
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(req).queryParams.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto req = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||
- REQUIRE(req.encoding == libyang::DataFormat::JSON);
|
||||
- REQUIRE(req.queryParams.empty());
|
||||
+ REQUIRE(std::holds_alternative<NetconfStreamRequest>(req));
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(req).encoding == libyang::DataFormat::JSON);
|
||||
+ REQUIRE(std::get<NetconfStreamRequest>(req).queryParams.empty());
|
||||
+ }
|
||||
+
|
||||
+ {
|
||||
+ auto req = asRestconfStreamRequest("GET", "/streams/subscribed/a40f0a50-061a-4832-a6ac-c4db7df81a10", "");
|
||||
+ REQUIRE(std::holds_alternative<SubscribedStreamRequest>(req));
|
||||
+ REQUIRE(std::get<SubscribedStreamRequest>(req).uuid == boost::uuids::string_generator()("a40f0a50-061a-4832-a6ac-c4db7df81a10"));
|
||||
}
|
||||
|
||||
REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF", ""),
|
||||
@@ -1105,6 +1124,12 @@ TEST_CASE("URI path parser")
|
||||
REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/NETCONF/XM", ""),
|
||||
serializeErrorResponse(404, "application", "invalid-value", "Invalid stream").c_str(),
|
||||
rousette::restconf::ErrorResponse);
|
||||
+ REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/subscribed", ""),
|
||||
+ serializeErrorResponse(404, "application", "invalid-value", "Invalid stream").c_str(),
|
||||
+ rousette::restconf::ErrorResponse);
|
||||
+ REQUIRE_THROWS_WITH_AS(asRestconfStreamRequest("GET", "/streams/subscribed/123-456-789", ""),
|
||||
+ serializeErrorResponse(404, "application", "invalid-value", "Invalid stream").c_str(),
|
||||
+ rousette::restconf::ErrorResponse);
|
||||
|
||||
|
||||
for (const auto& httpMethod : {"OPTIONS", "PATCH", "DELETE", "POST", "PUT"}) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
From 9c39cdd8a8a6bb8fb94d4f943313ec8c4a1823d7 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 10 Nov 2025 19:58:36 +0100
|
||||
Subject: [PATCH 29/38] restconf: dynamic subscriptions shutdown method
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Dynamic subscriptions now have a proper shutdown method that sets a
|
||||
Terminating state on all the subscriptions so that each subscription
|
||||
cannot call its terminate() method.
|
||||
|
||||
Change-Id: I4532bbafff7c7386a18ed0636f1102842d2749fa
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/DynamicSubscriptions.cpp | 10 ++++++++++
|
||||
src/restconf/DynamicSubscriptions.h | 1 +
|
||||
src/restconf/Server.cpp | 1 +
|
||||
3 files changed, 12 insertions(+)
|
||||
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
index 7f4b171..9c195f4 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -82,6 +82,16 @@ DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri)
|
||||
|
||||
DynamicSubscriptions::~DynamicSubscriptions() = default;
|
||||
|
||||
+void DynamicSubscriptions::stop()
|
||||
+{
|
||||
+ std::lock_guard lock(m_mutex);
|
||||
+ for (const auto& [uuid, subscriptionData] : m_subscriptions) {
|
||||
+ // We are already terminating and will destroy via destructor, calls to terminate() will do nothing
|
||||
+ std::lock_guard lock(subscriptionData->mutex);
|
||||
+ subscriptionData->state = SubscriptionData::State::Terminating;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput)
|
||||
{
|
||||
// Generate a new UUID associated with the subscription. The UUID will be used as a part of the URI so that the URI is not predictable (RFC 8650, section 5)
|
||||
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||
index 1874390..0309cad 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.h
|
||||
+++ b/src/restconf/DynamicSubscriptions.h
|
||||
@@ -55,6 +55,7 @@ public:
|
||||
~DynamicSubscriptions();
|
||||
std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||
void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||
+ void stop();
|
||||
|
||||
private:
|
||||
std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 2a8ad33..5486e99 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -884,6 +884,7 @@ void Server::stop()
|
||||
});
|
||||
|
||||
shutdownRequested();
|
||||
+ m_dynamicSubscriptions.stop();
|
||||
}
|
||||
|
||||
void Server::join()
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
From 3154d9209dc876fee43d9e83907ab44e06008c65 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 14 Oct 2025 12:25:20 +0200
|
||||
Subject: [PATCH 30/38] restconf: terminate subsc. notification streams after
|
||||
60s with no client
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We allow anonymous users to create notification streams. However, such
|
||||
users will not have permissions to delete the subscriptions which opens
|
||||
a potential for resource exhaustion. To prevent this, we now terminate
|
||||
the notification streams after 60 seconds of inactivity.
|
||||
|
||||
The period is configurable via the Server constructor, but the default
|
||||
is 60 seconds.
|
||||
|
||||
Change-Id: Id399d108069704fa8ab79b9ef6ab855dd62cfc8d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/DynamicSubscriptions.cpp | 72 ++++++++++++++++++---
|
||||
src/restconf/DynamicSubscriptions.h | 22 ++++++-
|
||||
src/restconf/Server.cpp | 5 +-
|
||||
src/restconf/Server.h | 3 +-
|
||||
tests/restconf-subscribed-notifications.cpp | 34 ++++++++++
|
||||
5 files changed, 122 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
index 9c195f4..f6defb4 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -74,9 +74,11 @@ sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, c
|
||||
|
||||
namespace rousette::restconf {
|
||||
|
||||
-DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri)
|
||||
+DynamicSubscriptions::DynamicSubscriptions(const std::string& streamRootUri, const nghttp2::asio_http2::server::http2& server, const std::chrono::seconds inactivityTimeout)
|
||||
: m_restconfStreamUri(streamRootUri)
|
||||
+ , m_server(server)
|
||||
, m_uuidGenerator(boost::uuids::random_generator())
|
||||
+ , m_inactivityTimeout(inactivityTimeout)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -86,9 +88,7 @@ void DynamicSubscriptions::stop()
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
for (const auto& [uuid, subscriptionData] : m_subscriptions) {
|
||||
- // We are already terminating and will destroy via destructor, calls to terminate() will do nothing
|
||||
- std::lock_guard lock(subscriptionData->mutex);
|
||||
- subscriptionData->state = SubscriptionData::State::Terminating;
|
||||
+ subscriptionData->stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,10 @@ void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, cons
|
||||
std::move(sub),
|
||||
dataFormat,
|
||||
uuid,
|
||||
- *session.getNacmUser());
|
||||
+ *session.getNacmUser(),
|
||||
+ *m_server.io_services().at(0),
|
||||
+ m_inactivityTimeout,
|
||||
+ [this, subId = sub.subscriptionId()]() { terminateSubscription(subId); });
|
||||
} catch (const sysrepo::ErrorWithCode& e) {
|
||||
throw ErrorResponse(400, "application", "invalid-attribute", e.what());
|
||||
}
|
||||
@@ -161,18 +164,29 @@ DynamicSubscriptions::SubscriptionData::SubscriptionData(
|
||||
sysrepo::DynamicSubscription sub,
|
||||
libyang::DataFormat format,
|
||||
boost::uuids::uuid uuid,
|
||||
- const std::string& user)
|
||||
+ const std::string& user,
|
||||
+ boost::asio::io_context& io,
|
||||
+ std::chrono::seconds inactivityTimeout,
|
||||
+ std::function<void()> onClientInactiveCallback)
|
||||
: subscription(std::move(sub))
|
||||
, dataFormat(format)
|
||||
, uuid(uuid)
|
||||
, user(user)
|
||||
, state(State::Start)
|
||||
+ , inactivityTimeout(inactivityTimeout)
|
||||
+ , clientInactiveTimer(io)
|
||||
+ , onClientInactiveCallback(std::move(onClientInactiveCallback))
|
||||
{
|
||||
spdlog::debug("{}: created", fmt::streamed(*this));
|
||||
+
|
||||
+ std::lock_guard lock(mutex);
|
||||
+ inactivityStart();
|
||||
}
|
||||
|
||||
DynamicSubscriptions::SubscriptionData::~SubscriptionData()
|
||||
{
|
||||
+ std::lock_guard lock(mutex);
|
||||
+ inactivityCancel();
|
||||
terminate();
|
||||
}
|
||||
|
||||
@@ -186,12 +200,14 @@ void DynamicSubscriptions::SubscriptionData::clientDisconnected()
|
||||
}
|
||||
|
||||
state = State::Start;
|
||||
+ inactivityStart();
|
||||
}
|
||||
|
||||
void DynamicSubscriptions::SubscriptionData::clientConnected()
|
||||
{
|
||||
spdlog::debug("{}: client connected", fmt::streamed(*this));
|
||||
std::lock_guard lock(mutex);
|
||||
+ inactivityCancel();
|
||||
state = State::ReceiverActive;
|
||||
}
|
||||
|
||||
@@ -201,10 +217,37 @@ bool DynamicSubscriptions::SubscriptionData::isReadyToAcceptClient() const
|
||||
return state == State::Start;
|
||||
}
|
||||
|
||||
-void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::string>& reason)
|
||||
+/** @pre The mutex must be locked by the caller. */
|
||||
+void DynamicSubscriptions::SubscriptionData::inactivityStart()
|
||||
{
|
||||
- std::lock_guard lock(mutex);
|
||||
+ if (state == State::Terminating) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ spdlog::trace("{}: starting inactivity timer", fmt::streamed(*this));
|
||||
+
|
||||
+ clientInactiveTimer.expires_after(inactivityTimeout);
|
||||
+ clientInactiveTimer.async_wait([weakThis = weak_from_this()](const boost::system::error_code& err) {
|
||||
+ auto self = weakThis.lock();
|
||||
+ if (!self || err == boost::asio::error::operation_aborted) {
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
+ spdlog::trace("{}: client inactive, perform inactivity callback", fmt::streamed(*self));
|
||||
+ self->onClientInactiveCallback();
|
||||
+ });
|
||||
+}
|
||||
+
|
||||
+/** @pre The mutex must be locked by the caller. */
|
||||
+void DynamicSubscriptions::SubscriptionData::inactivityCancel()
|
||||
+{
|
||||
+ spdlog::trace("{}: cancelling inactivity timer", fmt::streamed(*this));
|
||||
+ clientInactiveTimer.cancel();
|
||||
+}
|
||||
+
|
||||
+/** @pre The mutex must be locked by the caller. */
|
||||
+void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::string>& reason)
|
||||
+{
|
||||
// already terminating, do nothing
|
||||
if (state == State::Terminating) {
|
||||
return;
|
||||
@@ -219,6 +262,14 @@ void DynamicSubscriptions::SubscriptionData::terminate(const std::optional<std::
|
||||
}
|
||||
}
|
||||
|
||||
+void DynamicSubscriptions::SubscriptionData::stop()
|
||||
+{
|
||||
+ std::lock_guard lock(mutex);
|
||||
+ inactivityCancel();
|
||||
+ // We are already terminating and will destroy via destructor, calls to terminate() will do nothing
|
||||
+ state = SubscriptionData::State::Terminating;
|
||||
+}
|
||||
+
|
||||
std::ostream& operator<<(std::ostream& os, const DynamicSubscriptions::SubscriptionData& sub)
|
||||
{
|
||||
return os << "dynamic subscription (id " << sub.subscription.subscriptionId()
|
||||
@@ -241,7 +292,10 @@ DynamicSubscriptionHttpStream::DynamicSubscriptionHttpStream(
|
||||
*signal,
|
||||
keepAlivePingInterval,
|
||||
std::nullopt /* no initial event */,
|
||||
- [this]() { m_subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription"); },
|
||||
+ [this]() {
|
||||
+ std::lock_guard lock(m_subscriptionData->mutex);
|
||||
+ m_subscriptionData->terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||
+ },
|
||||
[this]() { m_subscriptionData->clientDisconnected(); })
|
||||
, m_subscriptionData(subscriptionData)
|
||||
, m_signal(signal)
|
||||
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||
index 0309cad..0422947 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.h
|
||||
+++ b/src/restconf/DynamicSubscriptions.h
|
||||
@@ -18,6 +18,10 @@ namespace libyang {
|
||||
enum class DataFormat;
|
||||
}
|
||||
|
||||
+namespace nghttp2::asio_http2::server {
|
||||
+class http2;
|
||||
+}
|
||||
+
|
||||
namespace rousette::restconf {
|
||||
|
||||
/** Dynamic subscriptions manager.
|
||||
@@ -39,19 +43,31 @@ public:
|
||||
Terminating, ///< Subscription is being terminated by the server shutdown
|
||||
} state;
|
||||
|
||||
+ std::chrono::seconds inactivityTimeout; ///< Time after which the subscription is considered inactive and can be removed if no client is connected
|
||||
+ boost::asio::steady_timer clientInactiveTimer; ///< Timer used for auto-destruction of subscriptions that are unused
|
||||
+ std::function<void()> onClientInactiveCallback;
|
||||
+
|
||||
SubscriptionData(
|
||||
sysrepo::DynamicSubscription sub,
|
||||
libyang::DataFormat format,
|
||||
boost::uuids::uuid uuid,
|
||||
- const std::string& user);
|
||||
+ const std::string& user,
|
||||
+ boost::asio::io_context& io,
|
||||
+ std::chrono::seconds inactivityTimeout,
|
||||
+ std::function<void()> onClientInactiveCallback);
|
||||
~SubscriptionData();
|
||||
void clientDisconnected();
|
||||
void clientConnected();
|
||||
void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||
bool isReadyToAcceptClient() const;
|
||||
+ void stop();
|
||||
+
|
||||
+ private:
|
||||
+ void inactivityStart();
|
||||
+ void inactivityCancel();
|
||||
};
|
||||
|
||||
- DynamicSubscriptions(const std::string& streamRootUri);
|
||||
+ DynamicSubscriptions(const std::string& streamRootUri, const nghttp2::asio_http2::server::http2& server, const std::chrono::seconds inactivityTimeout);
|
||||
~DynamicSubscriptions();
|
||||
std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||
void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||
@@ -60,8 +76,10 @@ public:
|
||||
private:
|
||||
std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||
std::string m_restconfStreamUri;
|
||||
+ const nghttp2::asio_http2::server::http2& m_server;
|
||||
std::map<boost::uuids::uuid, std::shared_ptr<SubscriptionData>> m_subscriptions;
|
||||
boost::uuids::random_generator m_uuidGenerator;
|
||||
+ std::chrono::seconds m_inactivityTimeout;
|
||||
|
||||
void terminateSubscription(const uint32_t subId);
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 5486e99..0c7e855 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -911,11 +911,12 @@ Server::Server(
|
||||
const std::string& address,
|
||||
const std::string& port,
|
||||
const std::chrono::milliseconds timeout,
|
||||
- const std::chrono::seconds keepAlivePingInterval)
|
||||
+ const std::chrono::seconds keepAlivePingInterval,
|
||||
+ const std::chrono::seconds subNotifInactivityTimeout)
|
||||
: m_monitoringSession(conn.sessionStart(sysrepo::Datastore::Operational))
|
||||
, nacm(conn)
|
||||
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||
- , m_dynamicSubscriptions(netconfStreamRoot)
|
||||
+ , m_dynamicSubscriptions(netconfStreamRoot, *server, subNotifInactivityTimeout)
|
||||
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||
{
|
||||
server->num_threads(1); // we only use one thread for the server, so we can call join() right away
|
||||
diff --git a/src/restconf/Server.h b/src/restconf/Server.h
|
||||
index c98f736..b4db948 100644
|
||||
--- a/src/restconf/Server.h
|
||||
+++ b/src/restconf/Server.h
|
||||
@@ -33,7 +33,8 @@ public:
|
||||
const std::string& address,
|
||||
const std::string& port,
|
||||
const std::chrono::milliseconds timeout = std::chrono::milliseconds{0},
|
||||
- const std::chrono::seconds keepAlivePingInterval = std::chrono::seconds{55});
|
||||
+ const std::chrono::seconds keepAlivePingInterval = std::chrono::seconds{55},
|
||||
+ const std::chrono::seconds subNotifInactivityTimeout = std::chrono::seconds{60});
|
||||
~Server();
|
||||
void join();
|
||||
void stop();
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
index 11dd992..68e9286 100644
|
||||
--- a/tests/restconf-subscribed-notifications.cpp
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -450,3 +450,37 @@ TEST_CASE("Terminating server under notification load")
|
||||
SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {auth});
|
||||
RUN_LOOP_WITH_EXCEPTIONS;
|
||||
}
|
||||
+
|
||||
+TEST_CASE("Cleaning up inactive subscriptions")
|
||||
+{
|
||||
+ trompeloeil::sequence seq1, seq2;
|
||||
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
|
||||
+ spdlog::set_level(spdlog::level::trace);
|
||||
+
|
||||
+ auto srConn = sysrepo::Connection{};
|
||||
+ auto srSess = srConn.sessionStart(sysrepo::Datastore::Running);
|
||||
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
+
|
||||
+ RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
+
|
||||
+ auto nacmGuard = manageNacm(srSess);
|
||||
+ constexpr auto inactivityTimeout = 2s;
|
||||
+ auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||
+
|
||||
+ auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||
+
|
||||
+ SECTION("Client connects and disconnects")
|
||||
+ {
|
||||
+ PREPARE_LOOP_WITH_EXCEPTIONS
|
||||
+ auto thr = std::jthread(wrap_exceptions_and_asio(bg, io, [&]() {
|
||||
+ WAIT_UNTIL_SSE_CLIENT_REQUESTS;
|
||||
+ std::this_thread::sleep_for(1s);
|
||||
+ }));
|
||||
+
|
||||
+ SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, {AUTH_ROOT});
|
||||
+ RUN_LOOP_WITH_EXCEPTIONS;
|
||||
+ }
|
||||
+
|
||||
+ std::this_thread::sleep_for(inactivityTimeout + 1500ms);
|
||||
+ REQUIRE(get(uri, {AUTH_ROOT}) == Response{404, plaintextHeaders, "Subscription not found."});
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
From 22cfcbf52be604afe0caafc892f4b7befe03b9db Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 17:00:59 +0200
|
||||
Subject: [PATCH 31/38] restconf: add {kill,delete}-subscription RPCs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Add implementations for deleting and killing subscriptions [1,2].
|
||||
|
||||
Rousette allows establishing subscriptions by anonymous users. Anonymous
|
||||
users can not call delete-subscription RPC though because we do not know
|
||||
if it is the same anonymous user who initiated the subscription.
|
||||
The subscriptions are killed after a minute of inactivity, so the
|
||||
subscription will get deleted by rousette automatically after the client
|
||||
disconnects.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/html/rfc8639.html#section-2.4.4
|
||||
[2] https://datatracker.ietf.org/doc/html/rfc8639.html#section-2.4.5
|
||||
|
||||
Change-Id: I443d6fd88f2797045b5ae0a7d7e1a761fe74e7cb
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/DynamicSubscriptions.cpp | 44 +++++
|
||||
src/restconf/DynamicSubscriptions.h | 2 +
|
||||
src/restconf/Server.cpp | 4 +
|
||||
tests/restconf-subscribed-notifications.cpp | 178 +++++++++++++++++++-
|
||||
4 files changed, 222 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
index f6defb4..fabc226 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -119,6 +119,32 @@ void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, cons
|
||||
}
|
||||
}
|
||||
|
||||
+void DynamicSubscriptions::deleteSubscription(sysrepo::Session& session, const libyang::DataFormat, const libyang::DataNode& rpcInput, libyang::DataNode&)
|
||||
+{
|
||||
+ const auto isKill = rpcInput.findPath("/ietf-subscribed-notifications:kill-subscription") != std::nullopt;
|
||||
+ const auto subId = std::get<uint32_t>(rpcInput.findPath("id")->asTerm().value());
|
||||
+
|
||||
+ // The RPC is already NACM-checked. Now, retrieve the subscription, if the current user has permission for it
|
||||
+ auto subscriptionData = getSubscriptionForUser(subId, session.getNacmUser());
|
||||
+ if (!subscriptionData) {
|
||||
+ throw ErrorResponse(404, "application", "invalid-value", "Subscription not found.", rpcInput.path());
|
||||
+ }
|
||||
+
|
||||
+ /* I *think* the RFC 8639 says that root can use delete-subscription only for subscriptions created by root.
|
||||
+ * This checks if the current user is root and the subscription was created by a different user. If so, reject the request.
|
||||
+ */
|
||||
+ if (!isKill && session.getNacmUser() == session.getNacmRecoveryUser() && subscriptionData->user != session.getNacmRecoveryUser()) {
|
||||
+ //FIXME: pass additional error info (rc:yang-data delete-subscription-error-info from RFC 8639)
|
||||
+ throw ErrorResponse(400, "application", "invalid-attribute", "Trying to delete subscription not created by root. Use kill-subscription instead.", rpcInput.path());
|
||||
+ }
|
||||
+
|
||||
+ spdlog::debug("Terminating subscription id {}", subId);
|
||||
+ subscriptionData->subscription.terminate("ietf-subscribed-notifications:no-such-subscription");
|
||||
+
|
||||
+ std::unique_lock lock(m_mutex);
|
||||
+ m_subscriptions.erase(subscriptionData->uuid);
|
||||
+}
|
||||
+
|
||||
void DynamicSubscriptions::terminateSubscription(const uint32_t subId)
|
||||
{
|
||||
std::lock_guard lock(m_mutex);
|
||||
@@ -153,6 +179,24 @@ std::shared_ptr<DynamicSubscriptions::SubscriptionData> DynamicSubscriptions::ge
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+/** @brief Returns the subscription data for the given subscription id and user.
|
||||
+ *
|
||||
+ * @param id The ID of the subscription.
|
||||
+ * @return A shared pointer to the SubscriptionData object if found and user is the one who established the subscription (or NACM recovery user), otherwise nullptr.
|
||||
+ */
|
||||
+std::shared_ptr<DynamicSubscriptions::SubscriptionData> DynamicSubscriptions::getSubscriptionForUser(const uint32_t subId, const std::optional<std::string>& user)
|
||||
+{
|
||||
+ std::unique_lock lock(m_mutex);
|
||||
+
|
||||
+ // FIXME: This is linear search. Maybe use something like boost::multi_index?
|
||||
+ if (auto it = std::find_if(m_subscriptions.begin(), m_subscriptions.end(), [subId](const auto& entry) { return entry.second->subscription.subscriptionId() == subId; });
|
||||
+ it != m_subscriptions.end() && (it->second->user == user || user == sysrepo::Session::getNacmRecoveryUser())) {
|
||||
+ return it->second;
|
||||
+ }
|
||||
+
|
||||
+ return nullptr;
|
||||
+}
|
||||
+
|
||||
boost::uuids::uuid DynamicSubscriptions::makeUUID()
|
||||
{
|
||||
// uuid generator instance accesses must be synchronized (https://www.boost.org/doc/libs/1_88_0/libs/uuid/doc/html/uuid.html#design_notes)
|
||||
diff --git a/src/restconf/DynamicSubscriptions.h b/src/restconf/DynamicSubscriptions.h
|
||||
index 0422947..8057b17 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.h
|
||||
+++ b/src/restconf/DynamicSubscriptions.h
|
||||
@@ -70,8 +70,10 @@ public:
|
||||
DynamicSubscriptions(const std::string& streamRootUri, const nghttp2::asio_http2::server::http2& server, const std::chrono::seconds inactivityTimeout);
|
||||
~DynamicSubscriptions();
|
||||
std::shared_ptr<SubscriptionData> getSubscriptionForUser(const boost::uuids::uuid& uuid, const std::optional<std::string>& user);
|
||||
+ std::shared_ptr<SubscriptionData> getSubscriptionForUser(const uint32_t subId, const std::optional<std::string>& user);
|
||||
void establishSubscription(sysrepo::Session& session, const libyang::DataFormat requestEncoding, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput);
|
||||
void stop();
|
||||
+ void deleteSubscription(sysrepo::Session& session, const libyang::DataFormat, const libyang::DataNode& rpcInput, libyang::DataNode&);
|
||||
|
||||
private:
|
||||
std::mutex m_mutex; ///< Lock for shared data (subscriptions storage and uuid generator)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 0c7e855..04cdf56 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -447,6 +447,10 @@ std::optional<libyang::DataNode> processInternalRPC(sysrepo::Session& sess, liby
|
||||
const std::map<std::string, InternalRPCHandler> handlers{
|
||||
{"/ietf-subscribed-notifications:establish-subscription",
|
||||
{"/ietf-subscribed-notifications:filters", [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.establishSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||
+ {"/ietf-subscribed-notifications:kill-subscription",
|
||||
+ {std::nullopt, [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.deleteSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||
+ {"/ietf-subscribed-notifications:delete-subscription",
|
||||
+ {std::nullopt, [&dynamicSubscriptions](auto&&... args) { return dynamicSubscriptions.deleteSubscription(std::forward<decltype(args)>(args)...); }}},
|
||||
};
|
||||
|
||||
const auto rpcPath = rpcInput.path();
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
index 68e9286..7c7de4b 100644
|
||||
--- a/tests/restconf-subscribed-notifications.cpp
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -24,8 +24,13 @@ constexpr auto uuidV4Regex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89a
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::string_literals;
|
||||
|
||||
+struct EstablishSubscriptionResult {
|
||||
+ uint32_t id;
|
||||
+ std::string url;
|
||||
+};
|
||||
+
|
||||
/** @brief Calls establish-subscription rpc, returns the url of the stream associated with the created subscription */
|
||||
-std::string establishSubscription(
|
||||
+EstablishSubscriptionResult establishSubscription(
|
||||
const libyang::Context& ctx,
|
||||
const libyang::DataFormat rpcEncoding,
|
||||
const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||
@@ -79,10 +84,16 @@ std::string establishSubscription(
|
||||
auto reply = ctx.newPath("/ietf-subscribed-notifications:establish-subscription");
|
||||
REQUIRE(reply.parseOp(resp.data, rpcEncoding, libyang::OperationType::ReplyRestconf).tree);
|
||||
|
||||
+ auto idNode = reply.findPath("id", libyang::InputOutputNodes::Output);
|
||||
+ REQUIRE(idNode);
|
||||
+
|
||||
auto urlNode = reply.findPath("ietf-restconf-subscribed-notifications:uri", libyang::InputOutputNodes::Output);
|
||||
REQUIRE(urlNode);
|
||||
|
||||
- return urlNode->asTerm().valueStr();
|
||||
+ return {
|
||||
+ std::get<uint32_t>(idNode->asTerm().value()),
|
||||
+ urlNode->asTerm().valueStr(),
|
||||
+ };
|
||||
}
|
||||
|
||||
TEST_CASE("RESTCONF subscribed notifications")
|
||||
@@ -150,7 +161,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
SECTION("User DWDM establishes subscription")
|
||||
{
|
||||
std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||
- auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||
|
||||
std::map<std::string, std::string> headers;
|
||||
|
||||
@@ -350,7 +361,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
}
|
||||
}
|
||||
|
||||
- auto uri = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||
|
||||
PREPARE_LOOP_WITH_EXCEPTIONS
|
||||
@@ -393,6 +404,161 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
SSEClient cli(io, SERVER_ADDRESS, SERVER_PORT, requestSent, netconfWatcher, uri, streamHeaders);
|
||||
RUN_LOOP_WITH_EXCEPTIONS;
|
||||
}
|
||||
+
|
||||
+ SECTION("delete-subscription")
|
||||
+ {
|
||||
+ std::optional<Response> expectedResponse;
|
||||
+ std::map<std::string, std::string> headers;
|
||||
+ headers.insert(CONTENT_TYPE_JSON);
|
||||
+
|
||||
+ SECTION("Subscription created by dwdm")
|
||||
+ {
|
||||
+ rpcRequestAuthHeader = AUTH_DWDM;
|
||||
+
|
||||
+ SECTION("dwdm (author) can delete")
|
||||
+ {
|
||||
+ headers.insert(AUTH_DWDM);
|
||||
+ expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Anonymous cannot delete anything because of NACM")
|
||||
+ {
|
||||
+ expectedResponse = Response{403, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "access-denied",
|
||||
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||
+ "error-message": "Access denied."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"};
|
||||
+ }
|
||||
+
|
||||
+ SECTION("norules user")
|
||||
+ {
|
||||
+ headers.insert(AUTH_NORULES);
|
||||
+ expectedResponse = Response{404, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||
+ "error-message": "Subscription not found."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"};
|
||||
+ }
|
||||
+
|
||||
+ SECTION("root user")
|
||||
+ {
|
||||
+ headers.insert(AUTH_ROOT);
|
||||
+ expectedResponse = Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-attribute",
|
||||
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||
+ "error-message": "Trying to delete subscription not created by root. Use kill-subscription instead."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"};
|
||||
+ }
|
||||
+
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == expectedResponse);
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Anonymous user cannot delete subscription craeted by anonymous user")
|
||||
+ {
|
||||
+ rpcRequestAuthHeader = std::nullopt;
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == Response{403, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "access-denied",
|
||||
+ "error-path": "/ietf-subscribed-notifications:delete-subscription",
|
||||
+ "error-message": "Access denied."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ SECTION("kill-subscription")
|
||||
+ {
|
||||
+ std::optional<Response> expectedResponse;
|
||||
+ std::map<std::string, std::string> headers;
|
||||
+ headers.insert(CONTENT_TYPE_JSON);
|
||||
+
|
||||
+ SECTION("User cannot kill")
|
||||
+ {
|
||||
+ SECTION("dwdm (author)")
|
||||
+ {
|
||||
+ headers.insert(AUTH_DWDM);
|
||||
+ }
|
||||
+
|
||||
+ SECTION("anonymous")
|
||||
+ {
|
||||
+ }
|
||||
+
|
||||
+ expectedResponse = Response{403, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "access-denied",
|
||||
+ "error-path": "/ietf-subscribed-notifications:kill-subscription",
|
||||
+ "error-message": "Access denied."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"};
|
||||
+ }
|
||||
+
|
||||
+ SECTION("root")
|
||||
+ {
|
||||
+ headers.insert(AUTH_ROOT);
|
||||
+ expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||
+ }
|
||||
+
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", headers, body) == expectedResponse);
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Invalid kill/delete-subscription requests")
|
||||
+ {
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", {AUTH_ROOT, CONTENT_TYPE_JSON}, R"({"ietf-subscribed-notifications:input": {}})") == Response{400, jsonHeaders, R"({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "protocol",
|
||||
+ "error-tag": "invalid-value",
|
||||
+ "error-path": "/ietf-subscribed-notifications:kill-subscription",
|
||||
+ "error-message": "Mandatory node \"id\" instance does not exist."
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+ }
|
||||
}
|
||||
|
||||
TEST_CASE("Terminating server under notification load")
|
||||
@@ -416,7 +582,7 @@ TEST_CASE("Terminating server under notification load")
|
||||
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
|
||||
std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||
- auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||
|
||||
PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
|
||||
@@ -467,7 +633,7 @@ TEST_CASE("Cleaning up inactive subscriptions")
|
||||
constexpr auto inactivityTimeout = 2s;
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||
|
||||
- auto uri = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||
|
||||
SECTION("Client connects and disconnects")
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
From c5558b12ee67280b6281b639c3d8652cec3c7fa6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 19 Mar 2025 14:39:05 +0100
|
||||
Subject: [PATCH 32/38] restconf: add subscribed notifications filtering
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Implement filtering of subscribed notifications using xpath and subtree
|
||||
filters.
|
||||
|
||||
Change-Id: Iee973a6a2d03b4a0d90f952afe5436ce701787ae
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
src/restconf/DynamicSubscriptions.cpp | 13 ++++-
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
tests/restconf-subscribed-notifications.cpp | 65 ++++++++++++++++++---
|
||||
4 files changed, 70 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 5d3b7ce..28696b3 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -217,7 +217,7 @@ if(BUILD_TESTING)
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang
|
||||
- --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json --enable-feature xpath --enable-feature subtree
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
index fabc226..a13d344 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -59,13 +59,22 @@ sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, c
|
||||
}
|
||||
|
||||
if (rpcInput.findPath("stream-filter-name")) {
|
||||
- throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream filtering is not supported");
|
||||
+ /* TODO: This requires support for modifying subscriptions first; a change of entry in filters container must change all
|
||||
+ * subscriptions using this stream-filter, see for instance https://datatracker.ietf.org/doc/html/rfc8639.html#section-2.7.2 */
|
||||
+ throw rousette::restconf::ErrorResponse(400, "application", "invalid-attribute", "Stream filtering with predefined filters is not supported");
|
||||
}
|
||||
|
||||
auto stopTime = optionalTime(rpcInput, "stop-time");
|
||||
|
||||
+ std::optional<std::variant<std::string, libyang::DataNodeAny>> filter;
|
||||
+ if (auto node = rpcInput.findPath("stream-xpath-filter")) {
|
||||
+ filter = node->asTerm().valueStr();
|
||||
+ } else if (auto node = rpcInput.findPath("stream-subtree-filter")) {
|
||||
+ filter = node->asAny();
|
||||
+ }
|
||||
+
|
||||
return session.subscribeNotifications(
|
||||
- std::nullopt /* TODO xpath filter */,
|
||||
+ filter,
|
||||
streamNode->asTerm().valueStr(),
|
||||
stopTime,
|
||||
std::nullopt /* TODO replayStart */);
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 04cdf56..76603b3 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -932,7 +932,7 @@ Server::Server(
|
||||
{"ietf-netconf", "", {}},
|
||||
{"ietf-yang-library", "2019-01-04", {}},
|
||||
{"ietf-yang-patch", "2017-02-22", {}},
|
||||
- {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json"}},
|
||||
+ {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json", "xpath", "subtree"}},
|
||||
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||
}) {
|
||||
if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
index 7c7de4b..7fcf549 100644
|
||||
--- a/tests/restconf-subscribed-notifications.cpp
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -34,7 +34,8 @@ EstablishSubscriptionResult establishSubscription(
|
||||
const libyang::Context& ctx,
|
||||
const libyang::DataFormat rpcEncoding,
|
||||
const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||
- const std::optional<std::string>& encodingLeafValue)
|
||||
+ const std::optional<std::string>& encodingLeafValue,
|
||||
+ const std::variant<std::monostate, std::string, libyang::XML>& filter)
|
||||
{
|
||||
constexpr auto jsonPrefix = "ietf-subscribed-notifications";
|
||||
constexpr auto xmlNamespace = "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications";
|
||||
@@ -56,6 +57,14 @@ EstablishSubscriptionResult establishSubscription(
|
||||
rpcTree.newPath("encoding", *encodingLeafValue);
|
||||
}
|
||||
|
||||
+ if (std::holds_alternative<std::string>(filter)) {
|
||||
+ rpcTree.newPath("stream-xpath-filter", std::get<std::string>(filter));
|
||||
+ }
|
||||
+
|
||||
+ if (std::holds_alternative<libyang::XML>(filter)) {
|
||||
+ rpcTree.newPath2("stream-subtree-filter", std::get<libyang::XML>(filter));
|
||||
+ }
|
||||
+
|
||||
switch (rpcEncoding) {
|
||||
case libyang::DataFormat::JSON:
|
||||
requestHeaders.insert(CONTENT_TYPE_JSON);
|
||||
@@ -128,6 +137,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
|
||||
libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
std::optional<std::string> rpcSubscriptionEncoding;
|
||||
+ std::variant<std::monostate, std::string, libyang::XML> rpcFilter;
|
||||
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
|
||||
SECTION("NACM authorization")
|
||||
@@ -161,7 +171,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
SECTION("User DWDM establishes subscription")
|
||||
{
|
||||
std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt, rpcFilter);
|
||||
|
||||
std::map<std::string, std::string> headers;
|
||||
|
||||
@@ -277,6 +287,23 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
]
|
||||
}
|
||||
}
|
||||
+)###"});
|
||||
+
|
||||
+ srSess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
+ srSess.setItem("/ietf-subscribed-notifications:filters/stream-filter[name='xyz']/stream-xpath-filter", "/example:eventA");
|
||||
+ srSess.applyChanges();
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", {CONTENT_TYPE_JSON}, R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "stream-filter-name": "xyz" } })###")
|
||||
+ == Response{400, jsonHeaders, R"###({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-attribute",
|
||||
+ "error-message": "Stream filtering with predefined filters is not supported"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
)###"});
|
||||
}
|
||||
|
||||
@@ -330,6 +357,28 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||
}
|
||||
|
||||
+ SECTION("XPath filter set")
|
||||
+ {
|
||||
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ rpcFilter = "/example:eventA | /example:eventB";
|
||||
+ rpcSubscriptionEncoding = "encode-json";
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
+ }
|
||||
+
|
||||
+ SECTION("Subtree filter set")
|
||||
+ {
|
||||
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ // Constructing the filter as XML is only an implementation detail. The tree is then constructed as JSON in establishSubscription
|
||||
+ rpcFilter = libyang::XML{"<eventA xmlns='http://example.tld/example' />"};
|
||||
+ rpcSubscriptionEncoding = "encode-json";
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
+ }
|
||||
+
|
||||
SECTION("Content-type with set encode leaf")
|
||||
{
|
||||
rpcRequestAuthHeader = AUTH_ROOT;
|
||||
@@ -361,7 +410,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
}
|
||||
}
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||
|
||||
PREPARE_LOOP_WITH_EXCEPTIONS
|
||||
@@ -474,7 +523,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
)"};
|
||||
}
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == expectedResponse);
|
||||
}
|
||||
@@ -482,7 +531,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
SECTION("Anonymous user cannot delete subscription craeted by anonymous user")
|
||||
{
|
||||
rpcRequestAuthHeader = std::nullopt;
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == Response{403, jsonHeaders, R"({
|
||||
"ietf-restconf:errors": {
|
||||
@@ -538,7 +587,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||
}
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", headers, body) == expectedResponse);
|
||||
}
|
||||
@@ -582,7 +631,7 @@ TEST_CASE("Terminating server under notification load")
|
||||
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
|
||||
std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt, {});
|
||||
|
||||
PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
|
||||
@@ -633,7 +682,7 @@ TEST_CASE("Cleaning up inactive subscriptions")
|
||||
constexpr auto inactivityTimeout = 2s;
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt);
|
||||
+ auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt, {});
|
||||
|
||||
SECTION("Client connects and disconnects")
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
From fe91af1eb38888d7eacb36cb5a299bd3163f7c60 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 14 Oct 2025 17:22:00 +0200
|
||||
Subject: [PATCH 33/38] cmake: wrap long line
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Id94e18d581dbfbe2e6d386be6a232544f9344eec
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 7 ++++++-
|
||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 28696b3..3d79dad 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -217,7 +217,12 @@ if(BUILD_TESTING)
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-interfaces@2018-02-20.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-ip@2018-02-22.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-network-instance@2019-01-21.yang
|
||||
- --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang --enable-feature replay --enable-feature encode-xml --enable-feature encode-json --enable-feature xpath --enable-feature subtree
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-subscribed-notifications@2019-09-09.yang
|
||||
+ --enable-feature replay
|
||||
+ --enable-feature encode-xml
|
||||
+ --enable-feature encode-json
|
||||
+ --enable-feature xpath
|
||||
+ --enable-feature subtree
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
From 817debab72d10f2f30e83f1630bebdcdf3ec9455 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 10 Mar 2025 15:33:08 +0100
|
||||
Subject: [PATCH 34/38] restconf: support replays in subscribed notifications
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Idfc3ceb99c8111c5575c1c8b94d539f234fa43be
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/restconf/DynamicSubscriptions.cpp | 24 ++++-
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
tests/restconf-subscribed-notifications.cpp | 102 ++++++++++++++++++--
|
||||
4 files changed, 115 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 3d79dad..f98a64b 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -223,6 +223,7 @@ if(BUILD_TESTING)
|
||||
--enable-feature encode-json
|
||||
--enable-feature xpath
|
||||
--enable-feature subtree
|
||||
+ --enable-feature replay
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||
diff --git a/src/restconf/DynamicSubscriptions.cpp b/src/restconf/DynamicSubscriptions.cpp
|
||||
index a13d344..9be3e92 100644
|
||||
--- a/src/restconf/DynamicSubscriptions.cpp
|
||||
+++ b/src/restconf/DynamicSubscriptions.cpp
|
||||
@@ -50,7 +50,7 @@ libyang::DataFormat getEncoding(const libyang::DataNode& rpcInput, const libyang
|
||||
return requestEncoding;
|
||||
}
|
||||
|
||||
-sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, const libyang::DataNode& rpcInput)
|
||||
+sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, const libyang::DataNode& rpcInput, libyang::DataNode& rpcOutput)
|
||||
{
|
||||
auto streamNode = rpcInput.findPath("stream");
|
||||
|
||||
@@ -73,11 +73,27 @@ sysrepo::DynamicSubscription makeStreamSubscription(sysrepo::Session& session, c
|
||||
filter = node->asAny();
|
||||
}
|
||||
|
||||
- return session.subscribeNotifications(
|
||||
+ std::optional<sysrepo::NotificationTimeStamp> replayStartTime;
|
||||
+ if (auto node = rpcInput.findPath("replay-start-time")) {
|
||||
+ replayStartTime = libyang::fromYangTimeFormat<sysrepo::NotificationTimeStamp::clock>(node->asTerm().valueStr());
|
||||
+ }
|
||||
+
|
||||
+ auto sub = session.subscribeNotifications(
|
||||
filter,
|
||||
streamNode->asTerm().valueStr(),
|
||||
stopTime,
|
||||
- std::nullopt /* TODO replayStart */);
|
||||
+ replayStartTime);
|
||||
+
|
||||
+ /* Node replay-start-time-revision should be set only if time was revised to be different than the requested start time,
|
||||
+ * i.e. when the "replay-start-time" contains a value that is earlier than what a publisher's retained history.
|
||||
+ * Then the actual publisher's revised start time MUST be set in the returned "replay-start-time-revision" object.
|
||||
+ * (RFC 8639, 2.4.2.1)
|
||||
+ * */
|
||||
+ if (auto replayStartTimeRevision = sub.replayStartTime(); replayStartTimeRevision && replayStartTime) {
|
||||
+ rpcOutput.newPath("replay-start-time-revision", libyang::yangTimeFormat(*replayStartTimeRevision, libyang::TimezoneInterpretation::Local), libyang::CreationOptions::Output);
|
||||
+ }
|
||||
+
|
||||
+ return sub;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +125,7 @@ void DynamicSubscriptions::establishSubscription(sysrepo::Session& session, cons
|
||||
auto dataFormat = getEncoding(rpcInput, requestEncoding);
|
||||
|
||||
try {
|
||||
- auto sub = makeStreamSubscription(session, rpcInput);
|
||||
+ auto sub = makeStreamSubscription(session, rpcInput, rpcOutput);
|
||||
|
||||
rpcOutput.newPath("id", std::to_string(sub.subscriptionId()), libyang::CreationOptions::Output);
|
||||
rpcOutput.newPath("ietf-restconf-subscribed-notifications:uri", m_restconfStreamUri + "subscribed/" + boost::uuids::to_string(uuid), libyang::CreationOptions::Output);
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 76603b3..d166236 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -932,7 +932,7 @@ Server::Server(
|
||||
{"ietf-netconf", "", {}},
|
||||
{"ietf-yang-library", "2019-01-04", {}},
|
||||
{"ietf-yang-patch", "2017-02-22", {}},
|
||||
- {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json", "xpath", "subtree"}},
|
||||
+ {"ietf-subscribed-notifications", "2019-09-09", {"encode-xml", "encode-json", "xpath", "subtree", "replay"}},
|
||||
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||
}) {
|
||||
if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
index 7fcf549..d42fa96 100644
|
||||
--- a/tests/restconf-subscribed-notifications.cpp
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -18,7 +18,7 @@ static const auto SERVER_PORT = "10092";
|
||||
#include "tests/pretty_printers.h"
|
||||
|
||||
#define SEND_NOTIFICATION(DATA) notifSession.sendNotification(*ctx.parseOp(DATA, libyang::DataFormat::JSON, libyang::OperationType::NotificationYang).op, sysrepo::Wait::No);
|
||||
-
|
||||
+#define REPLAY_COMPLETED trompeloeil::re(R"(^\{"ietf-subscribed-notifications:replay-completed":\{"id":[0-9]+\}\}$)")
|
||||
constexpr auto uuidV4Regex = "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}";
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
@@ -27,6 +27,7 @@ using namespace std::string_literals;
|
||||
struct EstablishSubscriptionResult {
|
||||
uint32_t id;
|
||||
std::string url;
|
||||
+ std::optional<sysrepo::NotificationTimeStamp> replayStartTimeRevision;
|
||||
};
|
||||
|
||||
/** @brief Calls establish-subscription rpc, returns the url of the stream associated with the created subscription */
|
||||
@@ -35,7 +36,8 @@ EstablishSubscriptionResult establishSubscription(
|
||||
const libyang::DataFormat rpcEncoding,
|
||||
const std::optional<std::pair<std::string, std::string>>& rpcRequestAuthHeader,
|
||||
const std::optional<std::string>& encodingLeafValue,
|
||||
- const std::variant<std::monostate, std::string, libyang::XML>& filter)
|
||||
+ const std::variant<std::monostate, std::string, libyang::XML>& filter,
|
||||
+ const std::optional<sysrepo::NotificationTimeStamp>& replayStartTime)
|
||||
{
|
||||
constexpr auto jsonPrefix = "ietf-subscribed-notifications";
|
||||
constexpr auto xmlNamespace = "urn:ietf:params:xml:ns:yang:ietf-subscribed-notifications";
|
||||
@@ -65,6 +67,10 @@ EstablishSubscriptionResult establishSubscription(
|
||||
rpcTree.newPath2("stream-subtree-filter", std::get<libyang::XML>(filter));
|
||||
}
|
||||
|
||||
+ if (replayStartTime) {
|
||||
+ rpcTree.newPath("replay-start-time", libyang::yangTimeFormat(*replayStartTime, libyang::TimezoneInterpretation::Local));
|
||||
+ }
|
||||
+
|
||||
switch (rpcEncoding) {
|
||||
case libyang::DataFormat::JSON:
|
||||
requestHeaders.insert(CONTENT_TYPE_JSON);
|
||||
@@ -99,9 +105,15 @@ EstablishSubscriptionResult establishSubscription(
|
||||
auto urlNode = reply.findPath("ietf-restconf-subscribed-notifications:uri", libyang::InputOutputNodes::Output);
|
||||
REQUIRE(urlNode);
|
||||
|
||||
+ std::optional<sysrepo::NotificationTimeStamp> replayStartTimeRevision;
|
||||
+ if (auto node = reply.findPath("ietf-subscribed-notifications:replay-start-time-revision", libyang::InputOutputNodes::Output)) {
|
||||
+ replayStartTimeRevision = libyang::fromYangTimeFormat<sysrepo::NotificationTimeStamp::clock>(node->asTerm().valueStr());
|
||||
+ }
|
||||
+
|
||||
return {
|
||||
std::get<uint32_t>(idNode->asTerm().value()),
|
||||
urlNode->asTerm().valueStr(),
|
||||
+ replayStartTimeRevision
|
||||
};
|
||||
}
|
||||
|
||||
@@ -131,6 +143,8 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
R"({"example:eventA":{"message":"almost finished","progress":99}})",
|
||||
R"({"example:tlc":{"list":[{"name":"k1","notif":{"message":"nested"}}]}})",
|
||||
};
|
||||
+ const std::string notificationForReplayJSON = R"({"example:eventA":{"message":"this-should-be-sent-very-early","progress":0}})";
|
||||
+
|
||||
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
|
||||
RestconfNotificationWatcher netconfWatcher(srConn.sessionStart().getContext());
|
||||
@@ -138,7 +152,10 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
libyang::DataFormat rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
std::optional<std::string> rpcSubscriptionEncoding;
|
||||
std::variant<std::monostate, std::string, libyang::XML> rpcFilter;
|
||||
+ std::optional<sysrepo::NotificationTimeStamp> rpcReplayStart;
|
||||
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
+ std::pair<sysrepo::NotificationTimeStamp, sysrepo::NotificationTimeStamp> replayedNotificationSendInterval; // bounds for replayed notification event time
|
||||
+ bool shouldReviseStartTime = false;
|
||||
|
||||
SECTION("NACM authorization")
|
||||
{
|
||||
@@ -171,7 +188,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
SECTION("User DWDM establishes subscription")
|
||||
{
|
||||
std::pair<std::string, std::string> user = AUTH_DWDM;
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt, rpcFilter);
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, user, std::nullopt, rpcFilter, rpcReplayStart);
|
||||
|
||||
std::map<std::string, std::string> headers;
|
||||
|
||||
@@ -304,6 +321,23 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
]
|
||||
}
|
||||
}
|
||||
+)###"});
|
||||
+
|
||||
+ // replay-start-time > stop-time
|
||||
+ REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription",
|
||||
+ {CONTENT_TYPE_JSON},
|
||||
+ R"###({ "ietf-subscribed-notifications:input": { "stream": "NETCONF", "replay-start-time": "2000-11-11T11:22:33Z", "stop-time": "2000-01-01T00:00:00Z" } })###")
|
||||
+ == Response{400, jsonHeaders, R"###({
|
||||
+ "ietf-restconf:errors": {
|
||||
+ "error": [
|
||||
+ {
|
||||
+ "error-type": "application",
|
||||
+ "error-tag": "invalid-attribute",
|
||||
+ "error-message": "Couldn't create notification subscription: SR_ERR_INVAL_ARG\u000A Specified \"stop-time\" is earlier than \"start-time\". (SR_ERR_INVAL_ARG)"
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
)###"});
|
||||
}
|
||||
|
||||
@@ -379,6 +413,48 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
}
|
||||
|
||||
+ SECTION("Replays")
|
||||
+ {
|
||||
+ // Announce replay support and send one notification before the client connects
|
||||
+ srConn.setModuleReplaySupport("example", true);
|
||||
+
|
||||
+ {
|
||||
+ auto notifSession = sysrepo::Connection{}.sessionStart();
|
||||
+ auto ctx = notifSession.getContext();
|
||||
+ replayedNotificationSendInterval.first = std::chrono::system_clock::now();
|
||||
+ SEND_NOTIFICATION(notificationForReplayJSON);
|
||||
+ replayedNotificationSendInterval.second = std::chrono::system_clock::now();
|
||||
+ }
|
||||
+
|
||||
+ rpcRequestAuthHeader = AUTH_ROOT;
|
||||
+ rpcRequestEncoding = libyang::DataFormat::JSON;
|
||||
+ rpcSubscriptionEncoding = "encode-json";
|
||||
+
|
||||
+ SECTION("replay-start-time-revision is announced to the client")
|
||||
+ {
|
||||
+ rpcReplayStart = std::chrono::system_clock::now() - 666s /* Reasonable time in the past, earlier than the replayed notification was sent */;
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(notificationForReplayJSON, seq1);
|
||||
+ EXPECT_NOTIFICATION(REPLAY_COMPLETED, seq1);
|
||||
+ shouldReviseStartTime = true;
|
||||
+ }
|
||||
+
|
||||
+ SECTION("replay-start-time-revision not announced")
|
||||
+ {
|
||||
+ /* Ask for replay since the time when the first notification was sent. This should not revise the start time
|
||||
+ * because we are not asking for the start *before* our history. (RFC 8639, 2.4.2.1) */
|
||||
+ rpcReplayStart = replayedNotificationSendInterval.second;
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(REPLAY_COMPLETED, seq1);
|
||||
+ }
|
||||
+
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[0], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[1], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[2], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[3], seq1);
|
||||
+ EXPECT_NOTIFICATION(notificationsJSON[4], seq1);
|
||||
+ }
|
||||
+
|
||||
SECTION("Content-type with set encode leaf")
|
||||
{
|
||||
rpcRequestAuthHeader = AUTH_ROOT;
|
||||
@@ -410,9 +486,17 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
}
|
||||
}
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||
REQUIRE(std::regex_match(uri, std::regex("/streams/subscribed/"s + uuidV4Regex)));
|
||||
|
||||
+ if (shouldReviseStartTime) {
|
||||
+ REQUIRE(replayStartTimeRevision);
|
||||
+ REQUIRE(replayedNotificationSendInterval.first <= *replayStartTimeRevision);
|
||||
+ REQUIRE(*replayStartTimeRevision <= replayedNotificationSendInterval.second);
|
||||
+ } else {
|
||||
+ REQUIRE(!replayStartTimeRevision);
|
||||
+ }
|
||||
+
|
||||
PREPARE_LOOP_WITH_EXCEPTIONS
|
||||
|
||||
// Here's how these two threads work together.
|
||||
@@ -523,7 +607,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
)"};
|
||||
}
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == expectedResponse);
|
||||
}
|
||||
@@ -531,7 +615,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
SECTION("Anonymous user cannot delete subscription craeted by anonymous user")
|
||||
{
|
||||
rpcRequestAuthHeader = std::nullopt;
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:delete-subscription", headers, body) == Response{403, jsonHeaders, R"({
|
||||
"ietf-restconf:errors": {
|
||||
@@ -587,7 +671,7 @@ TEST_CASE("RESTCONF subscribed notifications")
|
||||
expectedResponse = Response{204, noContentTypeHeaders, ""};
|
||||
}
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter);
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), rpcRequestEncoding, rpcRequestAuthHeader, rpcSubscriptionEncoding, rpcFilter, rpcReplayStart);
|
||||
auto body = R"({"ietf-subscribed-notifications:input": { "id": )" + std::to_string(id) + "}}";
|
||||
REQUIRE(post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:kill-subscription", headers, body) == expectedResponse);
|
||||
}
|
||||
@@ -631,7 +715,7 @@ TEST_CASE("Terminating server under notification load")
|
||||
std::optional<std::pair<std::string, std::string>> rpcRequestAuthHeader;
|
||||
|
||||
std::pair<std::string, std::string> auth = AUTH_ROOT;
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt, {});
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, auth, std::nullopt, {}, std::nullopt);
|
||||
|
||||
PREPARE_LOOP_WITH_EXCEPTIONS;
|
||||
|
||||
@@ -682,7 +766,7 @@ TEST_CASE("Cleaning up inactive subscriptions")
|
||||
constexpr auto inactivityTimeout = 2s;
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT, 0ms, 55s, inactivityTimeout};
|
||||
|
||||
- auto [id, uri] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt, {});
|
||||
+ auto [id, uri, replayStartTimeRevision] = establishSubscription(srSess.getContext(), libyang::DataFormat::JSON, {AUTH_ROOT}, std::nullopt, {}, std::nullopt);
|
||||
|
||||
SECTION("Client connects and disconnects")
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
From 7d6b1a463fdacefaf7a7057564f6f4d4a8cb4632 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 25 Mar 2025 11:06:26 +0100
|
||||
Subject: [PATCH 35/38] restconf: refactor getting datastore from string
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This will be useful when processing YANG push requests.
|
||||
|
||||
Change-Id: I1e76335c8d674e81890d6cd1e973adaf521eb205
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/uri.cpp | 19 +++++--------------
|
||||
src/restconf/utils/sysrepo.cpp | 16 ++++++++++++++++
|
||||
src/restconf/utils/sysrepo.h | 1 +
|
||||
3 files changed, 22 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index 99c3d1f..6143435 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "restconf/Exceptions.h"
|
||||
#include "restconf/uri.h"
|
||||
#include "restconf/uri_impl.h"
|
||||
+#include "restconf/utils/sysrepo.h"
|
||||
#include "restconf/utils/yang.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
@@ -298,21 +299,11 @@ std::optional<sysrepo::Datastore> datastoreFromApiIdentifier(const boost::option
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
- if (*datastore->prefix == "ietf-datastores") {
|
||||
- if (datastore->identifier == "running") {
|
||||
- return sysrepo::Datastore::Running;
|
||||
- } else if (datastore->identifier == "operational") {
|
||||
- return sysrepo::Datastore::Operational;
|
||||
- } else if (datastore->identifier == "candidate") {
|
||||
- return sysrepo::Datastore::Candidate;
|
||||
- } else if (datastore->identifier == "startup") {
|
||||
- return sysrepo::Datastore::Startup;
|
||||
- } else if (datastore->identifier == "factory-default") {
|
||||
- return sysrepo::Datastore::FactoryDefault;
|
||||
- }
|
||||
+ try {
|
||||
+ return datastoreFromString(*datastore->prefix + ":"s + datastore->identifier);
|
||||
+ } catch (const std::runtime_error&) {
|
||||
+ throw ErrorResponse(400, "application", "operation-failed", "Unsupported datastore " + *datastore->prefix + ":" + datastore->identifier);
|
||||
}
|
||||
-
|
||||
- throw ErrorResponse(400, "application", "operation-failed", "Unsupported datastore " + *datastore->prefix + ":" + datastore->identifier);
|
||||
}
|
||||
|
||||
std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> getModuleOrSubmodule(const libyang::Context& ctx, const std::string& name, const std::optional<std::string>& revision)
|
||||
diff --git a/src/restconf/utils/sysrepo.cpp b/src/restconf/utils/sysrepo.cpp
|
||||
index 622ab27..9f14cc7 100644
|
||||
--- a/src/restconf/utils/sysrepo.cpp
|
||||
+++ b/src/restconf/utils/sysrepo.cpp
|
||||
@@ -22,4 +22,20 @@ ScopedDatastoreSwitch::~ScopedDatastoreSwitch()
|
||||
m_session.switchDatastore(m_oldDatastore);
|
||||
}
|
||||
|
||||
+sysrepo::Datastore datastoreFromString(const std::string& datastore)
|
||||
+{
|
||||
+ if (datastore == "ietf-datastores:running") {
|
||||
+ return sysrepo::Datastore::Running;
|
||||
+ } else if (datastore == "ietf-datastores:operational") {
|
||||
+ return sysrepo::Datastore::Operational;
|
||||
+ } else if (datastore == "ietf-datastores:candidate") {
|
||||
+ return sysrepo::Datastore::Candidate;
|
||||
+ } else if (datastore == "ietf-datastores:startup") {
|
||||
+ return sysrepo::Datastore::Startup;
|
||||
+ } else if (datastore == "ietf-datastores:factory-default") {
|
||||
+ return sysrepo::Datastore::FactoryDefault;
|
||||
+ }
|
||||
+
|
||||
+ throw std::runtime_error("Unknown datastore '" + datastore + "'");
|
||||
+}
|
||||
}
|
||||
diff --git a/src/restconf/utils/sysrepo.h b/src/restconf/utils/sysrepo.h
|
||||
index da6c359..0eced9e 100644
|
||||
--- a/src/restconf/utils/sysrepo.h
|
||||
+++ b/src/restconf/utils/sysrepo.h
|
||||
@@ -26,4 +26,5 @@ public:
|
||||
ScopedDatastoreSwitch& operator=(ScopedDatastoreSwitch&&) = delete;
|
||||
};
|
||||
|
||||
+sysrepo::Datastore datastoreFromString(const std::string& datastore);
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From c1d5d143020ddc51c3886b82e87d463cf3211fca Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 21 Nov 2025 10:44:47 +0100
|
||||
Subject: [PATCH 36/38] build: remove duplicate line
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Fixes: 817debab restconf: support replays in subscribed notifications
|
||||
Change-Id: I4d2ea8429a4cb7b77f8739aff7a26a03cf535756
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 -
|
||||
1 file changed, 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index f98a64b..3d79dad 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -223,7 +223,6 @@ if(BUILD_TESTING)
|
||||
--enable-feature encode-json
|
||||
--enable-feature xpath
|
||||
--enable-feature subtree
|
||||
- --enable-feature replay
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/yang/ietf-restconf-subscribed-notifications@2019-11-17.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example.yang --enable-feature f1
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/example-delete.yang
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
From ed62a496744de0e0dc31171167d5d6f69f54af93 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 21 Nov 2025 12:54:44 +0100
|
||||
Subject: [PATCH 37/38] remove obsolete targets
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Idde217a4ae1bc6f03657d9609e0ec1b0a47a9747
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 9 --------
|
||||
src/clock.cpp | 62 --------------------------------------------------
|
||||
src/events.cpp | 33 ---------------------------
|
||||
3 files changed, 104 deletions(-)
|
||||
delete mode 100644 src/clock.cpp
|
||||
delete mode 100644 src/events.cpp
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 3d79dad..4a91128 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -125,14 +125,6 @@ target_link_libraries(rousette-restconf PUBLIC rousette-http rousette-sysrepo ro
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/auth/NacmIdentities.h.in ${CMAKE_CURRENT_BINARY_DIR}/NacmIdentities.h @ONLY)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
-add_executable(clock-demo src/clock.cpp)
|
||||
-target_link_libraries(clock-demo rousette-http Boost::system Threads::Threads)
|
||||
-
|
||||
-add_executable(watch-operational-ds
|
||||
- src/events.cpp
|
||||
-)
|
||||
-target_link_libraries(watch-operational-ds PUBLIC rousette-sysrepo)
|
||||
-
|
||||
add_executable(rousette src/restconf/main.cpp)
|
||||
target_link_libraries(rousette PUBLIC rousette-restconf PkgConfig::DOCOPT)
|
||||
if(SYSTEMD_FOUND)
|
||||
@@ -140,7 +132,6 @@ if(SYSTEMD_FOUND)
|
||||
endif()
|
||||
|
||||
install(TARGETS
|
||||
- # clock-demo
|
||||
# watch-operational-ds
|
||||
rousette
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/)
|
||||
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||
deleted file mode 100644
|
||||
index aa72849..0000000
|
||||
--- a/src/clock.cpp
|
||||
+++ /dev/null
|
||||
@@ -1,62 +0,0 @@
|
||||
-/*
|
||||
- * Copyright (C) 2016-2021 CESNET, https://photonics.cesnet.cz/
|
||||
- *
|
||||
- * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
- *
|
||||
-*/
|
||||
-
|
||||
-#include <boost/lexical_cast.hpp>
|
||||
-#include <chrono>
|
||||
-#include <nghttp2/asio_http2_server.h>
|
||||
-#include <spdlog/spdlog.h>
|
||||
-#include <thread>
|
||||
-#include "http/EventStream.h"
|
||||
-
|
||||
-using namespace std::literals;
|
||||
-
|
||||
-constexpr auto keepAlivePingInterval = std::chrono::seconds{55};
|
||||
-
|
||||
-int main(int argc [[maybe_unused]], char** argv [[maybe_unused]])
|
||||
-{
|
||||
- spdlog::set_level(spdlog::level::trace);
|
||||
-
|
||||
- rousette::http::EventStream::Termination shutdown;
|
||||
- rousette::http::EventStream::EventSignal sig;
|
||||
- std::jthread timer{[&sig]() {
|
||||
- for (int i = 0; /* forever */; ++i) {
|
||||
- std::this_thread::sleep_for(666ms);
|
||||
- spdlog::info("tick: {}", i);
|
||||
- sig("ping #" + std::to_string(i));
|
||||
- }
|
||||
- }};
|
||||
-
|
||||
- nghttp2::asio_http2::server::http2 server;
|
||||
- server.num_threads(4);
|
||||
-
|
||||
- server.handle("/events", [&shutdown, &sig](const auto& req, const auto& res) {
|
||||
- rousette::http::EventStream::create(req, res, shutdown, sig, keepAlivePingInterval);
|
||||
- });
|
||||
-
|
||||
- server.handle("/", [](const auto& req, const auto& resp) {
|
||||
- spdlog::info("{}: {} {}", boost::lexical_cast<std::string>(req.remote_endpoint()), req.method(), req.uri().raw_path);
|
||||
- resp.write_head(200, {{"content-type", {"text/html", false}}});
|
||||
- resp.end(R"(<html><head><title>nghttp2 event stream</title></head>
|
||||
-<body><h1>events</h1><ul id="x"></ul>
|
||||
-<script type="text/javascript">
|
||||
-const ev = new EventSource("/events");
|
||||
-ev.onmessage = function(event) {
|
||||
- const li = document.createElement("li");
|
||||
- li.textContent = event.data;
|
||||
- document.getElementById("x").appendChild(li);
|
||||
-};
|
||||
-</script>
|
||||
-</body>
|
||||
-</html>)");
|
||||
- });
|
||||
-
|
||||
- boost::system::error_code ec;
|
||||
- if (server.listen_and_serve(ec, "::", "10080")) {
|
||||
- return 1;
|
||||
- }
|
||||
- return 0;
|
||||
-}
|
||||
diff --git a/src/events.cpp b/src/events.cpp
|
||||
deleted file mode 100644
|
||||
index dc0477a..0000000
|
||||
--- a/src/events.cpp
|
||||
+++ /dev/null
|
||||
@@ -1,33 +0,0 @@
|
||||
-/*
|
||||
- * Copyright (C) 2016-2021 CESNET, https://photonics.cesnet.cz/
|
||||
- *
|
||||
- * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
- *
|
||||
-*/
|
||||
-
|
||||
-#include <csignal>
|
||||
-#include <spdlog/spdlog.h>
|
||||
-#include <sysrepo-cpp/Session.hpp>
|
||||
-#include <unistd.h>
|
||||
-#include "sr/AllEvents.h"
|
||||
-
|
||||
-using namespace rousette::sr;
|
||||
-
|
||||
-int main(int argc [[maybe_unused]], char* argv [[maybe_unused]] [])
|
||||
-{
|
||||
- spdlog::set_level(spdlog::level::trace);
|
||||
-
|
||||
- auto sess = sysrepo::Connection{}.sessionStart();
|
||||
- auto e = AllEvents{
|
||||
- sess,
|
||||
- /* AllEvents::WithAttributes::All, */
|
||||
- AllEvents::WithAttributes::RemoveEmptyOperationAndOrigin,
|
||||
- /* AllEvents::WithAttributes::None, */
|
||||
- };
|
||||
-
|
||||
- signal(SIGTERM, [](int) {});
|
||||
- signal(SIGINT, [](int) {});
|
||||
- pause();
|
||||
-
|
||||
- return 0;
|
||||
-}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
From 5a1d0dac28c1a78e90d3906693228584adcf3ab1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 20 Nov 2025 16:13:43 +0100
|
||||
Subject: [PATCH 38/38] port to libyang v4.2
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/9012
|
||||
Change-Id: I1ac6b25a3c1034f06917594732f066fd77d4b26b
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
src/restconf/Server.cpp | 10 +++++-----
|
||||
src/restconf/utils/yang.cpp | 2 +-
|
||||
src/sr/AllEvents.cpp | 4 ++--
|
||||
src/sr/OpticalEvents.cpp | 2 +-
|
||||
tests/event_watchers.cpp | 2 +-
|
||||
tests/restconf-subscribed-notifications.cpp | 2 +-
|
||||
7 files changed, 12 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 4a91128..4bb029b 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -76,7 +76,7 @@ find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
pkg_check_modules(SYSREPO REQUIRED sysrepo IMPORTED_TARGET)
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=7)
|
||||
-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=5)
|
||||
+pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=6)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||
pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index d166236..3c7c3c7 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -111,7 +111,7 @@ void rejectWithErrorImpl(libyang::Context ctx, const libyang::DataFormat& dataFo
|
||||
}
|
||||
|
||||
res.write_head(code, headers);
|
||||
- res.end(*parent.printStr(dataFormat, libyang::PrintFlags::WithSiblings));
|
||||
+ res.end(*parent.printStr(dataFormat, libyang::PrintFlags::Siblings));
|
||||
}
|
||||
|
||||
void rejectWithError(libyang::Context ctx, const libyang::DataFormat& dataFormat, const request& req, const response& res, const int code, const std::string errorType, const std::string& errorTag, const std::string& errorMessage, const std::optional<std::string>& errorPath)
|
||||
@@ -543,7 +543,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
contentType(requestCtx->dataFormat.response),
|
||||
CORS,
|
||||
});
|
||||
- requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
+ requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
|
||||
}
|
||||
|
||||
void processPost(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
@@ -709,7 +709,7 @@ void processYangPatch(std::shared_ptr<RequestContext> requestCtx, const std::chr
|
||||
yangPatchStatus->newExtPath(yangPatchStatusExt, "/ietf-yang-patch:yang-patch-status/ok", std::nullopt);
|
||||
|
||||
requestCtx->res.write_head(200, {contentType(requestCtx->dataFormat.response), CORS});
|
||||
- requestCtx->res.end(*yangPatchStatus->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings));
|
||||
+ requestCtx->res.end(*yangPatchStatus->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::Siblings));
|
||||
}
|
||||
|
||||
void processPutOrPlainPatch(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
@@ -842,7 +842,7 @@ libyang::PrintFlags libyangPrintFlags(const libyang::DataNode& dataNode, const s
|
||||
} catch(const libyang::Error& e) {
|
||||
}
|
||||
|
||||
- libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont;
|
||||
+ libyang::PrintFlags ret = libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers;
|
||||
|
||||
if (!withDefaults && node && (node->schema().nodeType() == libyang::NodeType::Leaf || node->schema().nodeType() == libyang::NodeType::Leaflist) && node->asTerm().isImplicitDefault()) {
|
||||
return ret | libyang::PrintFlags::WithDefaultsAll;
|
||||
@@ -1116,7 +1116,7 @@ Server::Server(
|
||||
case RestconfRequest::Type::ListRPC:
|
||||
res.write_head(200, {contentType(dataFormat.response), CORS});
|
||||
res.end(*apiResource(sess.getContext(), restconfRequest.type, dataFormat.response)
|
||||
- .printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
+ .printStr(dataFormat.response, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
|
||||
break;
|
||||
|
||||
case RestconfRequest::Type::GetData: {
|
||||
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||
index 9358840..5c311c1 100644
|
||||
--- a/src/restconf/utils/yang.cpp
|
||||
+++ b/src/restconf/utils/yang.cpp
|
||||
@@ -112,7 +112,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
envelope->insertChild(*eventTime);
|
||||
envelope->insertChild(notification);
|
||||
|
||||
- auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
+ auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||
|
||||
// notification node comes from sysrepo and sysrepo will free this; if not unlinked then envelope destructor would try to free this as well
|
||||
notification.unlink();
|
||||
diff --git a/src/sr/AllEvents.cpp b/src/sr/AllEvents.cpp
|
||||
index 7b6226b..2202b36 100644
|
||||
--- a/src/sr/AllEvents.cpp
|
||||
+++ b/src/sr/AllEvents.cpp
|
||||
@@ -110,10 +110,10 @@ sysrepo::ErrorCode AllEvents::onChange(sysrepo::Session session, const std::stri
|
||||
break;
|
||||
}
|
||||
};
|
||||
- auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
+ auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||
spdlog::info("JSON: {}", json);
|
||||
spdlog::warn("FULL JSON: {}",
|
||||
- *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||
+ *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||
change(module, json);
|
||||
}
|
||||
|
||||
diff --git a/src/sr/OpticalEvents.cpp b/src/sr/OpticalEvents.cpp
|
||||
index 25906a8..3f78ddc 100644
|
||||
--- a/src/sr/OpticalEvents.cpp
|
||||
+++ b/src/sr/OpticalEvents.cpp
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace {
|
||||
std::string dumpDataFrom(sysrepo::Session session, const std::string& module)
|
||||
{
|
||||
- return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
+ return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/tests/event_watchers.cpp b/tests/event_watchers.cpp
|
||||
index 26ba951..38ee7be 100644
|
||||
--- a/tests/event_watchers.cpp
|
||||
+++ b/tests/event_watchers.cpp
|
||||
@@ -23,7 +23,7 @@ void datastoreChanges(auto session, auto& dsChangesMock, auto path)
|
||||
|
||||
void datastoreNewState(auto session, auto& dsChangesMock, auto path)
|
||||
{
|
||||
- dsChangesMock.contentAfterChange(session.getData(path)->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||
+ dsChangesMock.contentAfterChange(session.getData(path)->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-subscribed-notifications.cpp b/tests/restconf-subscribed-notifications.cpp
|
||||
index d42fa96..73b9fab 100644
|
||||
--- a/tests/restconf-subscribed-notifications.cpp
|
||||
+++ b/tests/restconf-subscribed-notifications.cpp
|
||||
@@ -92,7 +92,7 @@ EstablishSubscriptionResult establishSubscription(
|
||||
data->unlinkWithSiblings();
|
||||
envelope->insertChild(*data);
|
||||
|
||||
- auto body = *envelope->printStr(rpcEncoding, libyang::PrintFlags::WithSiblings);
|
||||
+ auto body = *envelope->printStr(rpcEncoding, libyang::PrintFlags::Siblings);
|
||||
auto resp = post(RESTCONF_OPER_ROOT "/ietf-subscribed-notifications:establish-subscription", requestHeaders, body);
|
||||
REQUIRE(resp.equalStatusCodeAndHeaders(Response{200, expectedHeaders, ""}));
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
From ee0ce2ea31f3ecba4a4753a761df08f0698c2726 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 20 Oct 2025 11:47:26 +0200
|
||||
Subject: [PATCH 1/9] CI: pin to libyang v3
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8978
|
||||
Change-Id: I2521ad3a62d6197e757e636b88478deeb3bb5592
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index 7ada6f7..cef174f 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,9 +4,9 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-07-08
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-06-02
|
||||
- name: github/doctest/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
@@ -15,9 +15,9 @@
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-07-08
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-06-02
|
||||
- name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
From e49463aba8b44b932fdd342d1c1d117e70414aba Mon Sep 17 00:00:00 2001
|
||||
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
|
||||
Date: Fri, 17 Oct 2025 18:08:47 +0200
|
||||
Subject: [PATCH 2/9] fix: implicit dependency on optional
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
chrono imports optional in gcc >=13.
|
||||
added the explicit include to compile in gcc <=12.
|
||||
|
||||
Change-Id: I18cc1a45ab522ee6738d4bc7080696cba8284903
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Connection.hpp | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Connection.hpp b/include/sysrepo-cpp/Connection.hpp
|
||||
index bf72bcd..a469a2c 100644
|
||||
--- a/include/sysrepo-cpp/Connection.hpp
|
||||
+++ b/include/sysrepo-cpp/Connection.hpp
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
+#include <optional>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From b8e349aacae4c7063517a1bc652e9e93f90e28ba Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 24 Oct 2025 17:05:52 +0200
|
||||
Subject: [PATCH 3/9] fix #include for for our public headers
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Idcbfe38348e3ca2e4331fc1fad542ae759b8d0f1
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/utils/utils.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index ff4a32c..c13b394 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <sysrepo-cpp/Connection.hpp>
|
||||
-#include "sysrepo-cpp/utils/exception.hpp"
|
||||
+#include <sysrepo-cpp/utils/exception.hpp>
|
||||
extern "C" {
|
||||
#include <sysrepo.h>
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From 753a3e6eb346f2a9f6263b2d9fcd1c6dbb508247 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 24 Oct 2025 17:06:56 +0200
|
||||
Subject: [PATCH 4/9] reduce number of files which are named "utils.hpp"
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We used to have three (one in publicheaders, one in src/utils/, one
|
||||
below tests/). Let's cut that by one to increase our collective sanity.
|
||||
|
||||
Change-Id: I9e3d93c65dffb29ba18b3ae325ddd7712a8be7a4
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/Connection.cpp | 2 +-
|
||||
src/Session.cpp | 2 +-
|
||||
src/Subscription.cpp | 2 +-
|
||||
src/utils/{utils.hpp => misc.hpp} | 0
|
||||
src/utils/utils.cpp | 2 +-
|
||||
5 files changed, 4 insertions(+), 4 deletions(-)
|
||||
rename src/utils/{utils.hpp => misc.hpp} (100%)
|
||||
|
||||
diff --git a/src/Connection.cpp b/src/Connection.cpp
|
||||
index 0297ebe..1717b9a 100644
|
||||
--- a/src/Connection.cpp
|
||||
+++ b/src/Connection.cpp
|
||||
@@ -13,7 +13,7 @@ extern "C" {
|
||||
#include <sysrepo-cpp/utils/exception.hpp>
|
||||
#include "utils/enum.hpp"
|
||||
#include "utils/exception.hpp"
|
||||
-#include "utils/utils.hpp"
|
||||
+#include "utils/misc.hpp"
|
||||
|
||||
namespace sysrepo {
|
||||
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index dfe1044..994ce87 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -21,7 +21,7 @@ extern "C" {
|
||||
#include <utility>
|
||||
#include "utils/enum.hpp"
|
||||
#include "utils/exception.hpp"
|
||||
-#include "utils/utils.hpp"
|
||||
+#include "utils/misc.hpp"
|
||||
|
||||
using namespace std::string_literals;
|
||||
namespace sysrepo {
|
||||
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
|
||||
index 303b54c..5bb724e 100644
|
||||
--- a/src/Subscription.cpp
|
||||
+++ b/src/Subscription.cpp
|
||||
@@ -16,7 +16,7 @@ extern "C" {
|
||||
}
|
||||
#include "utils/enum.hpp"
|
||||
#include "utils/exception.hpp"
|
||||
-#include "utils/utils.hpp"
|
||||
+#include "utils/misc.hpp"
|
||||
|
||||
namespace sysrepo {
|
||||
/**
|
||||
diff --git a/src/utils/utils.hpp b/src/utils/misc.hpp
|
||||
similarity index 100%
|
||||
rename from src/utils/utils.hpp
|
||||
rename to src/utils/misc.hpp
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index c13b394..2e8d8b4 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -12,7 +12,7 @@ extern "C" {
|
||||
#include <sysrepo.h>
|
||||
}
|
||||
#include "enum.hpp"
|
||||
-#include "utils.hpp"
|
||||
+#include "misc.hpp"
|
||||
|
||||
namespace sysrepo {
|
||||
/**
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
From dc118efa44b13508e1af47d924ad02ce58c58cea Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 24 Oct 2025 17:18:49 +0200
|
||||
Subject: [PATCH 5/9] Fix a different function return type
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The prototype that was specified in the header file did not match what
|
||||
was used in the corresponding .cpp implementation. This has not been a
|
||||
problem because that implementation .cpp file was not including the
|
||||
actual header, so this was not detected. Then, at runtime, this was not
|
||||
a problem due to the way how derived types are laid out in memory.
|
||||
|
||||
Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes
|
||||
Change-Id: Ie4cf8c0e39e9c8486a6289c0328510ad6f7360fb
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/utils/utils.cpp | 7 ++++---
|
||||
1 file changed, 4 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index 2e8d8b4..61d0c7f 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <sysrepo-cpp/Connection.hpp>
|
||||
#include <sysrepo-cpp/utils/exception.hpp>
|
||||
+#include <sysrepo-cpp/utils/utils.hpp>
|
||||
extern "C" {
|
||||
#include <sysrepo.h>
|
||||
}
|
||||
@@ -78,7 +79,7 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional<FDHandli
|
||||
* @see Session::operationalChanges()
|
||||
* @see Session::dropForeignOperationalContent()
|
||||
*/
|
||||
-std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
|
||||
+std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
|
||||
{
|
||||
auto discard = root.firstOpaqueSibling();
|
||||
while (discard) {
|
||||
@@ -97,11 +98,11 @@ std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, con
|
||||
/**
|
||||
* @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath
|
||||
*/
|
||||
-std::vector<libyang::DataNode> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||
+std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||
{
|
||||
auto withSlash = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '/') ? xpathPrefix : xpathPrefix + '/';
|
||||
auto withBracket = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '[') ? xpathPrefix : xpathPrefix + '[';
|
||||
- std::vector<libyang::DataNode> res;
|
||||
+ std::vector<libyang::DataNodeOpaque> res;
|
||||
auto discard = root.firstOpaqueSibling();
|
||||
while (discard) {
|
||||
if (discard->name().matches("sysrepo", "discard-items")) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
From b943faea3cb00c5e86f21710647a2ca370e41f33 Mon Sep 17 00:00:00 2001
|
||||
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
|
||||
Date: Fri, 24 Oct 2025 13:45:00 +0200
|
||||
Subject: [PATCH 6/9] fix: raw strings and macros break gcc < 13
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
see bug report:
|
||||
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55971
|
||||
|
||||
Change-Id: I04afc2b3bee86a7e4b038b277aad0e3a150a1940
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/subscriptions-dynamic.cpp | 21 ++++++++++++++-------
|
||||
1 file changed, 14 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||
index 5ed173b..561df0c 100644
|
||||
--- a/tests/subscriptions-dynamic.cpp
|
||||
+++ b/tests/subscriptions-dynamic.cpp
|
||||
@@ -39,21 +39,28 @@
|
||||
REQUIRE(pipeStatus((SUBSCRIPTION).fd(), -1) == PipeStatus::DataReady); \
|
||||
(SUBSCRIPTION).processEvent(cbNotif);
|
||||
|
||||
-#define SUBSCRIPTION_TERMINATED(SUBSCRIPTION) R"({
|
||||
+auto SUBSCRIPTION_TERMINATED(const auto& SUBSCRIPTION)
|
||||
+{
|
||||
+ return R"({
|
||||
"ietf-subscribed-notifications:subscription-terminated": {
|
||||
- "id": )" + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(,
|
||||
+ "id": )"
|
||||
+ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(,
|
||||
"reason": "no-such-subscription"
|
||||
}
|
||||
}
|
||||
-)"
|
||||
+)";
|
||||
+};
|
||||
|
||||
-#define REPLAY_COMPLETED(SUBSCRIPTION) R"({
|
||||
+auto REPLAY_COMPLETED(const auto& SUBSCRIPTION)
|
||||
+{
|
||||
+ return R"({
|
||||
"ietf-subscribed-notifications:replay-completed": {
|
||||
- "id": )" \
|
||||
- + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(
|
||||
+ "id": )"
|
||||
+ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(
|
||||
}
|
||||
}
|
||||
-)"
|
||||
+)";
|
||||
+};
|
||||
|
||||
#define REQUIRE_YANG_PUSH_UPDATE(SUBSCRIPTION, NOTIFICATION) \
|
||||
TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate((SUBSCRIPTION).subscriptionId(), NOTIFICATION)).IN_SEQUENCE(seq);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
From 5fd8cf4fb8db1d65b6a20d2777bad22647e7f5e0 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 23 Oct 2025 16:04:10 +0200
|
||||
Subject: [PATCH 7/9] Port to libyang v4
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I'm rather disappointed with the upstream changes that implement that
|
||||
shared libyang "printed context" mapped to a fixed address, I think it's
|
||||
effectively a step backwards. Upstream is happy with their solution, so
|
||||
here we are.
|
||||
|
||||
Change-Id: I6b89422ec66bc694dc2c6ecd0d40aa1e7f76a741
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8984
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 8 +++---
|
||||
CMakeLists.txt | 6 ++---
|
||||
ci/build.sh | 4 +++
|
||||
include/sysrepo-cpp/Enum.hpp | 18 ++++++++++---
|
||||
include/sysrepo-cpp/utils/utils.hpp | 5 ++++
|
||||
src/Connection.cpp | 3 +--
|
||||
src/Session.cpp | 2 +-
|
||||
src/utils/enum.hpp | 18 +++++++------
|
||||
src/utils/utils.cpp | 18 +++++++++++++
|
||||
tests/session.cpp | 39 +++++++++++++++++++++--------
|
||||
10 files changed, 90 insertions(+), 31 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index cef174f..7ada6f7 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,9 +4,9 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-07-08
|
||||
+ override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2025-06-02
|
||||
+ override-checkout: devel
|
||||
- name: github/doctest/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
@@ -15,9 +15,9 @@
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-07-08
|
||||
+ override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2025-06-02
|
||||
+ override-checkout: devel
|
||||
- name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index f2a9b09..49338dd 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -19,7 +19,7 @@ add_custom_target(sysrepo-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-set(SYSREPO_CPP_PKG_VERSION "6")
|
||||
+set(SYSREPO_CPP_PKG_VERSION "7")
|
||||
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.7.4 IMPORTED_TARGET)
|
||||
+pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
diff --git a/ci/build.sh b/ci/build.sh
|
||||
index 785a811..67bc7d3 100755
|
||||
--- a/ci/build.sh
|
||||
+++ b/ci/build.sh
|
||||
@@ -47,6 +47,10 @@ if [[ $ZUUL_JOB_NAME =~ .*-tsan ]]; then
|
||||
# Our TSAN does not have interceptors for a variety of "less common" functions such as pthread_mutex_clocklock.
|
||||
# Disable all functions which are optional in sysrepo/libnetconf2/Netopeer2.
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DHAVE_PTHREAD_MUTEX_TIMEDLOCK=OFF -DHAVE_PTHREAD_MUTEX_CLOCKLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKRDLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKWRLOCK=OFF -DHAVE_PTHREAD_COND_CLOCKWAIT=OFF"
|
||||
+
|
||||
+ # This is currently broken on TSAN ("Failed to map the printed context (Operation not permitted).").
|
||||
+ # Even if it wasn't broken, I want at least one build in the matrix with this (mis)feature disabled.
|
||||
+ CMAKE_OPTIONS="${CMAKE_OPTIONS} -DPRINTED_CONTEXT_ADDRESS=0"
|
||||
fi
|
||||
|
||||
if [[ $ZUUL_JOB_NAME =~ .*-cover.* ]]; then
|
||||
diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp
|
||||
index 48467c6..505797a 100644
|
||||
--- a/include/sysrepo-cpp/Enum.hpp
|
||||
+++ b/include/sysrepo-cpp/Enum.hpp
|
||||
@@ -176,12 +176,24 @@ enum class NotificationType : uint32_t {
|
||||
};
|
||||
|
||||
/**
|
||||
- * Wraps `sr_conn_flag_e`.
|
||||
+ * Wraps `sr_context_flag_t`.
|
||||
+ */
|
||||
+enum class ContextFlags : uint32_t {
|
||||
+ Default = 0x00, /**< SR_CTX_DEFAULT */
|
||||
+ NoPrinted = 0x01, /**< SR_CTX_NO_PRINTED */
|
||||
+ LibYangPrivParsed = 0x02, /**< SR_CTX_SET_PRIV_PARSED */
|
||||
+};
|
||||
+
|
||||
+constexpr ContextFlags operator|(const ContextFlags a, const ContextFlags b)
|
||||
+{
|
||||
+ return implEnumBitOr(a, b);
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * Wraps `sr_conn_flag_t`.
|
||||
*/
|
||||
enum class ConnectionFlags : uint32_t {
|
||||
Default = 0x00, /**< SR_CONN_DEFAULT */
|
||||
- CacheRunning = 0x01, /**< SR_CONN_CACHE_RUNNING */
|
||||
- LibYangPrivParsed = 0x02, /**< SR_CONN_CTX_SET_PRIV_PARSED */
|
||||
};
|
||||
|
||||
constexpr ConnectionFlags operator|(const ConnectionFlags a, const ConnectionFlags b)
|
||||
diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp
|
||||
index 64b1c2e..6f2446f 100644
|
||||
--- a/include/sysrepo-cpp/utils/utils.hpp
|
||||
+++ b/include/sysrepo-cpp/utils/utils.hpp
|
||||
@@ -12,6 +12,11 @@
|
||||
namespace sysrepo {
|
||||
Session wrapUnmanagedSession(sr_session_ctx_s* session);
|
||||
void setLogLevelStderr(const LogLevel);
|
||||
+enum class GlobalContextEffect {
|
||||
+ Lazy, /**< Set these flags the next time the shared context is applied */
|
||||
+ Immediate, /**< Apply immediately */
|
||||
+};
|
||||
+ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when);
|
||||
std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath);
|
||||
std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix);
|
||||
void unlinkFromForest(std::optional<libyang::DataNode>& root, libyang::DataNode node);
|
||||
diff --git a/src/Connection.cpp b/src/Connection.cpp
|
||||
index 1717b9a..96e340c 100644
|
||||
--- a/src/Connection.cpp
|
||||
+++ b/src/Connection.cpp
|
||||
@@ -26,7 +26,7 @@ Connection::Connection(const ConnectionFlags options)
|
||||
: ctx(nullptr)
|
||||
{
|
||||
sr_conn_ctx_t* ctx;
|
||||
- auto res = sr_connect(static_cast<sr_conn_options_t>(options), &ctx);
|
||||
+ auto res = sr_connect(static_cast<sr_conn_flag_t>(options), &ctx);
|
||||
|
||||
throwIfError(res, "Couldn't connect to sysrepo");
|
||||
this->ctx = std::shared_ptr<sr_conn_ctx_t>(ctx, sr_disconnect);
|
||||
@@ -96,5 +96,4 @@ uint32_t Connection::getId() const
|
||||
{
|
||||
return sr_get_cid(ctx.get());
|
||||
}
|
||||
-
|
||||
}
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 994ce87..95b8f24 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -241,7 +241,7 @@ std::optional<libyang::DataNode> Session::operationalChanges(const std::optional
|
||||
void Session::discardOperationalChanges(const std::optional<std::string>& moduleName, std::chrono::milliseconds timeout)
|
||||
{
|
||||
SYSREPO_CPP_SESSION_MTX;
|
||||
- auto res = sr_discard_oper_changes(nullptr, m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count());
|
||||
+ auto res = sr_discard_oper_changes(m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count());
|
||||
throwIfError(res, "Session::discardOoperationalChanges: Couldn't discard "s + (moduleName ? "for module \"" + *moduleName + "\"" : "globally"s), m_sess.get());
|
||||
}
|
||||
|
||||
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
|
||||
index 2d3a298..e917b21 100644
|
||||
--- a/src/utils/enum.hpp
|
||||
+++ b/src/utils/enum.hpp
|
||||
@@ -151,13 +151,16 @@ static_assert(toNotificationType(SR_EV_NOTIF_MODIFIED) == NotificationType::Modi
|
||||
static_assert(toNotificationType(SR_EV_NOTIF_SUSPENDED) == NotificationType::Suspended);
|
||||
static_assert(toNotificationType(SR_EV_NOTIF_RESUMED) == NotificationType::Resumed);
|
||||
|
||||
-static_assert(std::is_same_v<sr_conn_options_t, std::underlying_type_t<ConnectionFlags>>);
|
||||
+static_assert(std::is_same_v<std::underlying_type_t<sr_conn_flag_t>, std::underlying_type_t<ConnectionFlags>>);
|
||||
static_assert(static_cast<ConnectionFlags>(SR_CONN_DEFAULT) == ConnectionFlags::Default);
|
||||
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CACHE_RUNNING) == ConnectionFlags::CacheRunning);
|
||||
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CTX_SET_PRIV_PARSED) == ConnectionFlags::LibYangPrivParsed);
|
||||
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CACHE_RUNNING | SR_CONN_CTX_SET_PRIV_PARSED) == (ConnectionFlags::CacheRunning | ConnectionFlags::LibYangPrivParsed));
|
||||
|
||||
-static_assert(std::is_same_v<sr_get_options_t, std::underlying_type_t<GetOptions>>);
|
||||
+static_assert(std::is_same_v<std::underlying_type_t<sr_context_flag_t>, std::underlying_type_t<ContextFlags>>);
|
||||
+static_assert(static_cast<ContextFlags>(SR_CTX_DEFAULT) == ContextFlags::Default);
|
||||
+static_assert(static_cast<ContextFlags>(SR_CTX_SET_PRIV_PARSED) == ContextFlags::LibYangPrivParsed);
|
||||
+static_assert(static_cast<ContextFlags>(SR_CTX_NO_PRINTED) == ContextFlags::NoPrinted);
|
||||
+static_assert(static_cast<ContextFlags>(SR_CTX_SET_PRIV_PARSED | SR_CTX_NO_PRINTED) == (ContextFlags::LibYangPrivParsed | ContextFlags::NoPrinted));
|
||||
+
|
||||
+// upstream project provides no dedicated type for these
|
||||
static_assert(static_cast<GetOptions>(SR_OPER_DEFAULT) == GetOptions::Default);
|
||||
static_assert(static_cast<GetOptions>(SR_OPER_NO_STATE) == GetOptions::OperNoState);
|
||||
static_assert(static_cast<GetOptions>(SR_OPER_NO_CONFIG) == GetOptions::OperNoConfig);
|
||||
@@ -168,9 +171,10 @@ static_assert(static_cast<GetOptions>(SR_OPER_NO_POLL_CACHED) == GetOptions::Ope
|
||||
static_assert(static_cast<GetOptions>(SR_OPER_NO_RUN_CACHED) == GetOptions::OperNoRunningCached);
|
||||
static_assert(static_cast<GetOptions>(SR_GET_NO_FILTER) == GetOptions::NoFilter);
|
||||
|
||||
-constexpr sr_get_options_t toGetOptions(const GetOptions opts)
|
||||
+// upstream project provides no dedicated type for these
|
||||
+constexpr uint32_t toGetOptions(const GetOptions opts)
|
||||
{
|
||||
- return static_cast<sr_get_options_t>(opts);
|
||||
+ return static_cast<uint32_t>(opts);
|
||||
}
|
||||
|
||||
static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create);
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index 61d0c7f..dbbc5ce 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -13,6 +13,7 @@ extern "C" {
|
||||
#include <sysrepo.h>
|
||||
}
|
||||
#include "enum.hpp"
|
||||
+#include "exception.hpp"
|
||||
#include "misc.hpp"
|
||||
|
||||
namespace sysrepo {
|
||||
@@ -37,6 +38,23 @@ void setLogLevelStderr(const LogLevel level)
|
||||
sr_log_stderr(toLogLevel(level));
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Set global sysrepo-level context options
|
||||
+ *
|
||||
+ * Be advised of consequences of manipulating a shared global state, especially when using multiple connections.
|
||||
+ *
|
||||
+ * Wraps `sr_context_options`.
|
||||
+ */
|
||||
+ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when)
|
||||
+{
|
||||
+ uint32_t old;
|
||||
+ throwIfError(
|
||||
+ sr_context_options(static_cast<uint32_t>(flags), when == GlobalContextEffect::Immediate, &old),
|
||||
+ "sr_context_options failed",
|
||||
+ nullptr);
|
||||
+ return static_cast<ContextFlags>(old);
|
||||
+}
|
||||
+
|
||||
std::timespec toTimespec(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> tp)
|
||||
{
|
||||
// https://embeddedartistry.com/blog/2019/01/31/converting-between-timespec-stdchrono#-std-chrono-time_point-to-timespec-
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 47b5631..a709a09 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -627,17 +627,34 @@ TEST_CASE("session")
|
||||
|
||||
DOCTEST_SUBCASE("libyang context flags")
|
||||
{
|
||||
- sess.setItem("/test_module:popelnice/s", "666");
|
||||
- REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666");
|
||||
- // Parsed type info is not preserved by libyang unless its context is constructed with a flag,
|
||||
- // and that flag is not used by sysrepo by default...
|
||||
- REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable);
|
||||
-
|
||||
- // ...unless we pass that flag explicitly as a parameter to the connection.
|
||||
- auto sess2 = sysrepo::Connection{sysrepo::ConnectionFlags::LibYangPrivParsed}.sessionStart();
|
||||
- sess2.setItem("/test_module:popelnice/s", "333");
|
||||
- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333");
|
||||
- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10);
|
||||
+ DOCTEST_SUBCASE("default")
|
||||
+ {
|
||||
+ sess.setItem("/test_module:popelnice/s", "666");
|
||||
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666");
|
||||
+ // Parsed type info is not preserved by libyang unless its context is constructed with a flag,
|
||||
+ // and that flag is not used by sysrepo by default.
|
||||
+ REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable);
|
||||
+
|
||||
+ // pending changes hold the context lock, so we cannot change the global context options
|
||||
+ REQUIRE_THROWS_WITH_AS(
|
||||
+ sysrepo::setGlobalContextOptions(
|
||||
+ sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted,
|
||||
+ sysrepo::GlobalContextEffect::Immediate),
|
||||
+ "sr_context_options failed: SR_ERR_TIME_OUT",
|
||||
+ sysrepo::ErrorWithCode);
|
||||
+ }
|
||||
+
|
||||
+ // Since this is actually a shared global state, changing it is "tricky". The best bet is to actually
|
||||
+ // ensure that there are no other connections (which we cannot do here, we already have a connection opened
|
||||
+ // from the common code at the very top of this test case), and especially no internal sysrepo-level locks
|
||||
+ // on the shared libyang context. For example, no uncommited changes -- hence a new subcase.
|
||||
+ DOCTEST_SUBCASE("Connection::setGlobalContextOptions LibYangPrivParsed")
|
||||
+ {
|
||||
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
|
||||
+ sess.setItem("/test_module:popelnice/s", "333");
|
||||
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333");
|
||||
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("replay support")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
From 6e9dbd8513ce75e51f2f714df69c08af5b556d1d Mon Sep 17 00:00:00 2001
|
||||
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
|
||||
Date: Tue, 28 Oct 2025 13:24:13 +0100
|
||||
Subject: [PATCH 8/9] fix: onOperGet unconsistent with C library.
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Fixes: https://github.com/sysrepo/sysrepo-cpp/issues/29
|
||||
Change-Id: I5a21069889c59466df8e9457c3a316b58e2571bb
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 2 +-
|
||||
include/sysrepo-cpp/Subscription.hpp | 2 +-
|
||||
src/Session.cpp | 4 ++--
|
||||
src/Subscription.cpp | 4 ++--
|
||||
4 files changed, 6 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index aae7068..3af4b10 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
[[nodiscard]] Subscription onOperGet(
|
||||
const std::string& moduleName,
|
||||
OperGetCb cb,
|
||||
- const std::optional<std::string>& xpath = std::nullopt,
|
||||
+ const std::string& path,
|
||||
const SubscribeOptions opts = SubscribeOptions::Default,
|
||||
ExceptionHandler handler = nullptr,
|
||||
const std::optional<FDHandling>& callbacks = std::nullopt);
|
||||
diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp
|
||||
index c8f3ff8..14ecce4 100644
|
||||
--- a/include/sysrepo-cpp/Subscription.hpp
|
||||
+++ b/include/sysrepo-cpp/Subscription.hpp
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
Subscription& operator=(Subscription&&) noexcept;
|
||||
|
||||
void onModuleChange(const std::string& moduleName, ModuleChangeCb cb, const std::optional<std::string>& xpath = std::nullopt, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||
- void onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional<std::string>& xpath, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||
+ void onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||
void onRPCAction(const std::string& xpath, RpcActionCb cb, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default);
|
||||
void onNotification(
|
||||
const std::string& moduleName,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 95b8f24..6f0327d 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -505,14 +505,14 @@ Subscription Session::onModuleChange(
|
||||
Subscription Session::onOperGet(
|
||||
const std::string& moduleName,
|
||||
OperGetCb cb,
|
||||
- const std::optional<std::string>& xpath,
|
||||
+ const std::string& path,
|
||||
const SubscribeOptions opts,
|
||||
ExceptionHandler handler,
|
||||
const std::optional<FDHandling>& callbacks)
|
||||
{
|
||||
checkNoThreadFlag(opts, callbacks);
|
||||
auto sub = Subscription{*this, handler, callbacks};
|
||||
- sub.onOperGet(moduleName, cb, xpath, opts);
|
||||
+ sub.onOperGet(moduleName, cb, path, opts);
|
||||
return sub;
|
||||
}
|
||||
|
||||
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
|
||||
index 5bb724e..dca1176 100644
|
||||
--- a/src/Subscription.cpp
|
||||
+++ b/src/Subscription.cpp
|
||||
@@ -215,14 +215,14 @@ void Subscription::onModuleChange(const std::string& moduleName, ModuleChangeCb
|
||||
* @param xpath XPath that identifies which data this subscription is able to provide.
|
||||
* @param opts Options further changing the behavior of this method.
|
||||
*/
|
||||
-void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional<std::string>& xpath, const SubscribeOptions opts)
|
||||
+void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts)
|
||||
{
|
||||
SYSREPO_CPP_SESSION_MTX_OF(m_sess);
|
||||
checkNoThreadFlag(opts, m_customEventLoopCbs);
|
||||
|
||||
auto& privRef = m_operGetCbs.emplace_back(PrivData{cb, m_exceptionHandler.get()});
|
||||
sr_subscription_ctx_s* ctx = m_sub.get();
|
||||
- auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), xpath ? xpath->c_str() : nullptr, operGetItemsCb, reinterpret_cast<void*>(&privRef), toSubscribeOptions(opts), &ctx);
|
||||
+ auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), path.c_str(), operGetItemsCb, reinterpret_cast<void*>(&privRef), toSubscribeOptions(opts), &ctx);
|
||||
throwIfError(res, "Couldn't create operational get items subscription", m_sess.m_sess.get());
|
||||
|
||||
saveContext(ctx);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
From 60d729e4791e5447cf0d969be6da9f8f1878af64 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 20 Nov 2025 15:38:04 +0100
|
||||
Subject: [PATCH 9/9] Update to libyang v4.2
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/9009
|
||||
Change-Id: Ia4c53dba0befa8dade38e728489edc6473aa5f4f
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
tests/session.cpp | 14 +++++++-------
|
||||
tests/subscriptions-dynamic.cpp | 4 ++--
|
||||
3 files changed, 11 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 49338dd..8234464 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET)
|
||||
+pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=6 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.8 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index a709a09..0e92e65 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -105,7 +105,7 @@ TEST_CASE("session")
|
||||
|
||||
auto data = sess.getData("/test_module:popelnice", 0);
|
||||
REQUIRE(data);
|
||||
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||
"test_module:popelnice": {
|
||||
"content": {
|
||||
"trash": [
|
||||
@@ -126,14 +126,14 @@ TEST_CASE("session")
|
||||
|
||||
data = sess.getData("/test_module:popelnice", 1);
|
||||
REQUIRE(data);
|
||||
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||
"test_module:popelnice": {}
|
||||
}
|
||||
)");
|
||||
|
||||
data = sess.getData("/test_module:popelnice", 2);
|
||||
REQUIRE(data);
|
||||
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||
"test_module:popelnice": {
|
||||
"content": {}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ TEST_CASE("session")
|
||||
// If a list should be returned, its keys are always returned as well.
|
||||
data = sess.getData("/test_module:popelnice", 3);
|
||||
REQUIRE(data);
|
||||
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||
"test_module:popelnice": {
|
||||
"content": {
|
||||
"trash": [
|
||||
@@ -161,7 +161,7 @@ TEST_CASE("session")
|
||||
|
||||
data = sess.getData("/test_module:popelnice", 4);
|
||||
REQUIRE(data);
|
||||
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
|
||||
"test_module:popelnice": {
|
||||
"content": {
|
||||
"trash": [
|
||||
@@ -497,7 +497,7 @@ TEST_CASE("session")
|
||||
{
|
||||
sess.switchDatastore(sysrepo::Datastore::FactoryDefault);
|
||||
auto data = sess.getData("/*");
|
||||
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == "{\n\n}\n");
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == "{\n\n}\n");
|
||||
REQUIRE_THROWS_AS(sess.setItem(leaf, "123"), sysrepo::ErrorWithCode);
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ TEST_CASE("session")
|
||||
}
|
||||
|
||||
// the original tree is not corrupted
|
||||
- REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) != "");
|
||||
+ REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) != "");
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("libyang context flags")
|
||||
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||
index 561df0c..0d4ce7c 100644
|
||||
--- a/tests/subscriptions-dynamic.cpp
|
||||
+++ b/tests/subscriptions-dynamic.cpp
|
||||
@@ -140,12 +140,12 @@ TEST_CASE("Dynamic subscriptions")
|
||||
const auto subId = std::get<uint32_t>(idNode->asTerm().value());
|
||||
idNode->unlink();
|
||||
|
||||
- rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||
+ rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||
};
|
||||
|
||||
auto cbNotif = [&](const std::optional<libyang::DataNode>& tree, auto) {
|
||||
REQUIRE(tree);
|
||||
- rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||
+ rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
|
||||
};
|
||||
|
||||
// write some initial data
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,6 +1,77 @@
|
||||
diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
||||
--- frr-9.1.3.orig/lib/yang.c 2024-12-27 22:06:42.000000000 +0100
|
||||
+++ frr-9.1.3/lib/yang.c 2025-03-04 12:23:06.723640114 +0100
|
||||
diff --git a/lib/northbound.c b/lib/northbound.c
|
||||
index 6ff5c24bd1..c6cca523b2 100644
|
||||
--- a/lib/northbound.c
|
||||
+++ b/lib/northbound.c
|
||||
@@ -581,7 +581,7 @@ void nb_config_diff(const struct nb_config *config1,
|
||||
char *s;
|
||||
|
||||
if (!lyd_print_mem(&s, diff, LYD_JSON,
|
||||
- LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)) {
|
||||
+ LYD_PRINT_SIBLINGS | LYD_PRINT_WD_ALL)) {
|
||||
zlog_debug("%s: %s", __func__, s);
|
||||
free(s);
|
||||
}
|
||||
@@ -2283,7 +2283,7 @@ bool nb_operation_is_valid(enum nb_operation operation,
|
||||
if (sleaf->when)
|
||||
return true;
|
||||
if (CHECK_FLAG(sleaf->flags, LYS_MAND_TRUE)
|
||||
- || sleaf->dflt)
|
||||
+ || sleaf->dflt.str)
|
||||
return false;
|
||||
break;
|
||||
case LYS_CONTAINER:
|
||||
diff --git a/lib/northbound_cli.c b/lib/northbound_cli.c
|
||||
index 8003679ed5..0997b5f8c5 100644
|
||||
--- a/lib/northbound_cli.c
|
||||
+++ b/lib/northbound_cli.c
|
||||
@@ -634,7 +634,7 @@ static int nb_cli_show_config_libyang(struct vty *vty, LYD_FORMAT format,
|
||||
return CMD_WARNING;
|
||||
}
|
||||
|
||||
- SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
|
||||
+ SET_FLAG(options, LYD_PRINT_SIBLINGS);
|
||||
if (with_defaults)
|
||||
SET_FLAG(options, LYD_PRINT_WD_ALL);
|
||||
else
|
||||
@@ -1443,7 +1443,7 @@ DEFPY (show_yang_operational_data,
|
||||
struct ly_ctx *ly_ctx;
|
||||
struct lyd_node *dnode;
|
||||
char *strp;
|
||||
- uint32_t print_options = LYD_PRINT_WITHSIBLINGS;
|
||||
+ uint32_t print_options = LYD_PRINT_SIBLINGS;
|
||||
int ret;
|
||||
|
||||
if (xml)
|
||||
diff --git a/lib/northbound_db.c b/lib/northbound_db.c
|
||||
index 74abcde955..7d51f39291 100644
|
||||
--- a/lib/northbound_db.c
|
||||
+++ b/lib/northbound_db.c
|
||||
@@ -79,7 +79,7 @@ int nb_db_transaction_save(const struct nb_transaction *transaction,
|
||||
* values too, as this covers the case where defaults may change.
|
||||
*/
|
||||
if (lyd_print_mem(&config_str, transaction->config->dnode, LYD_XML,
|
||||
- LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL)
|
||||
+ LYD_PRINT_SIBLINGS | LYD_PRINT_WD_ALL)
|
||||
!= 0)
|
||||
goto exit;
|
||||
|
||||
diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp
|
||||
index 6c33351cef..b3e14828f3 100644
|
||||
--- a/lib/northbound_grpc.cpp
|
||||
+++ b/lib/northbound_grpc.cpp
|
||||
@@ -370,7 +370,7 @@ static LY_ERR data_tree_from_dnode(frr::DataTree *dt,
|
||||
char *strp;
|
||||
int options = 0;
|
||||
|
||||
- SET_FLAG(options, LYD_PRINT_WITHSIBLINGS);
|
||||
+ SET_FLAG(options, LYD_PRINT_SIBLINGS);
|
||||
if (with_defaults)
|
||||
SET_FLAG(options, LYD_PRINT_WD_ALL);
|
||||
else
|
||||
diff --git a/lib/yang.c b/lib/yang.c
|
||||
index 4dd8654217..44563d4922 100644
|
||||
--- a/lib/yang.c
|
||||
+++ b/lib/yang.c
|
||||
@@ -10,11 +10,23 @@
|
||||
#include "lib_errors.h"
|
||||
#include "yang.h"
|
||||
@@ -25,7 +96,18 @@ diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
||||
/* libyang container. */
|
||||
struct ly_ctx *ly_native_ctx;
|
||||
|
||||
@@ -657,7 +669,12 @@
|
||||
@@ -329,9 +341,7 @@ const char *yang_snode_get_default(const struct lysc_node *snode)
|
||||
switch (snode->nodetype) {
|
||||
case LYS_LEAF:
|
||||
sleaf = (const struct lysc_node_leaf *)snode;
|
||||
- return sleaf->dflt ? lyd_value_get_canonical(sleaf->module->ctx,
|
||||
- sleaf->dflt)
|
||||
- : NULL;
|
||||
+ return sleaf->dflt.str;
|
||||
case LYS_LEAFLIST:
|
||||
/* TODO: check leaf-list default values */
|
||||
return NULL;
|
||||
@@ -657,7 +667,12 @@ struct yang_data *yang_data_list_find(const struct list *list,
|
||||
}
|
||||
|
||||
/* Make libyang log its errors using FRR logging infrastructure. */
|
||||
@@ -39,7 +121,7 @@ diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
||||
{
|
||||
int priority = LOG_ERR;
|
||||
|
||||
@@ -674,8 +691,14 @@
|
||||
@@ -674,8 +689,14 @@ static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -56,7 +138,7 @@ diff -urN frr-9.1.3.orig/lib/yang.c frr-9.1.3/lib/yang.c
|
||||
else
|
||||
zlog(priority, "libyang: %s", msg);
|
||||
}
|
||||
@@ -752,7 +775,7 @@
|
||||
@@ -752,7 +773,7 @@ struct ly_ctx *yang_ctx_new_setup(bool embedded_modules, bool explicit_compile)
|
||||
void yang_init(bool embedded_modules, bool defer_compile)
|
||||
{
|
||||
/* Initialize libyang global parameters that affect all containers. */
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
diff --git a/src/server_config.c b/src/server_config.c
|
||||
index 48d362e..dc9f59d 100644
|
||||
--- a/src/server_config.c
|
||||
+++ b/src/server_config.c
|
||||
@@ -4151,6 +4151,8 @@ config_asymmetric_key_certs(const struct lyd_node *node, enum nc_operation paren
|
||||
struct lyd_node *n;
|
||||
enum nc_operation op;
|
||||
|
||||
+ if (!node)
|
||||
+ return 0;
|
||||
NC_NODE_GET_OP(node, parent_op, &op);
|
||||
|
||||
/* configure all certificates */
|
||||
@@ -4225,7 +4227,7 @@ config_asymmetric_key(const struct lyd_node *node, enum nc_operation parent_op,
|
||||
}
|
||||
|
||||
/* config asymmetric key certificates */
|
||||
- NC_CHECK_RET(nc_lyd_find_child(node, "certificates", 1, &n));
|
||||
+ NC_CHECK_RET(nc_lyd_find_child(node, "certificates", 0, &n));
|
||||
NC_CHECK_RET(config_asymmetric_key_certs(n, op, entry));
|
||||
|
||||
/* config generate csr */
|
||||
+741
@@ -0,0 +1,741 @@
|
||||
From 545473073552cd4cf5e0fc839e7170d4678ef646 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Wed, 10 Dec 2025 09:52:13 +0100
|
||||
Subject: [PATCH 1/6] plugins ext BUGFIX printed context align all sizes and
|
||||
mem
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Refs #2455
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/plugins_exts.h | 17 +++-
|
||||
src/plugins_exts/metadata.c | 4 +-
|
||||
src/plugins_exts/schema_mount.c | 24 +++---
|
||||
src/plugins_exts/structure.c | 8 +-
|
||||
src/printer_context.c | 148 +++++++++++++++-----------------
|
||||
5 files changed, 100 insertions(+), 101 deletions(-)
|
||||
|
||||
diff --git a/src/plugins_exts.h b/src/plugins_exts.h
|
||||
index f260ff787..e912fe908 100644
|
||||
--- a/src/plugins_exts.h
|
||||
+++ b/src/plugins_exts.h
|
||||
@@ -953,13 +953,26 @@ LIBYANG_API_DECL void lyplg_ext_cfree_instance_substatements(const struct ly_ctx
|
||||
* compiled size
|
||||
*/
|
||||
|
||||
+/**
|
||||
+ * @brief Alignment of all the printed data, ensures all memory access is aligned to this number (B)
|
||||
+ */
|
||||
+#define LY_CTXP_MEM_ALIGN 8
|
||||
+
|
||||
+/**
|
||||
+ * @brief Adjust data size to an aligned size to make sure the following data is aligned.
|
||||
+ *
|
||||
+ * @param[in,out] SIZE Data size that is adjusted.
|
||||
+ */
|
||||
+#define LY_CTXP_MEM_SIZE(SIZE) ((SIZE) + ((~(SIZE) + 1) & (LY_CTXP_MEM_ALIGN - 1)))
|
||||
+
|
||||
/**
|
||||
* @brief Callback to return the size of the custom compiled structure and substmts array. If there are none, do not
|
||||
* define this callback.
|
||||
*
|
||||
* @param[in] ext Compiled extension structure.
|
||||
* @param[in,out] addr_ht Hash table with addresses of shared structures that were already accounted for, can be added to.
|
||||
- * @return Total size of the custom compiled structures and the substmts array;
|
||||
+ * @return Total size of the custom compiled structures and the substmts array. Always use ::LY_CTXP_MEM_SIZE when
|
||||
+ * adding to the size for correct alignment.
|
||||
* @return -1 on error.
|
||||
*/
|
||||
typedef int (*lyplg_ext_compiled_size_clb)(const struct lysc_ext_instance *ext, struct ly_ht *addr_ht);
|
||||
@@ -984,7 +997,7 @@ LIBYANG_API_DECL int lyplg_ext_compiled_stmts_storage_size(const struct lysc_ext
|
||||
* @param[in] orig_ext Compiled extension structure to print.
|
||||
* @param[in,out] ext Printed extension structure to modify.
|
||||
* @param[in,out] mem Memory chunk of the size returned by ::lyplg_ext_compiled_size_clb() to print into, is moved
|
||||
- * after all the printed data.
|
||||
+ * after all the printed data. Always use ::LY_CTXP_MEM_SIZE when moving @p mem for correct alignment.
|
||||
* @param[in,out] ptr_set Set with pointers to set to printed addresses.
|
||||
* @param[in,out] addr_ht Hash table with pairs of addresses of shared structures to be printed and their printed
|
||||
* addresses, can be added to.
|
||||
diff --git a/src/plugins_exts/metadata.c b/src/plugins_exts/metadata.c
|
||||
index d5492839e..c73c8f9a8 100644
|
||||
--- a/src/plugins_exts/metadata.c
|
||||
+++ b/src/plugins_exts/metadata.c
|
||||
@@ -220,7 +220,7 @@ annotation_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *addr
|
||||
{
|
||||
int size = 0;
|
||||
|
||||
- size += sizeof(struct lysc_ext_metadata);
|
||||
+ size += LY_CTXP_MEM_SIZE(sizeof(struct lysc_ext_metadata));
|
||||
size += lyplg_ext_compiled_stmts_storage_size(ext->substmts, addr_ht);
|
||||
|
||||
return size;
|
||||
@@ -233,7 +233,7 @@ annotation_compiled_print(const struct lysc_ext_instance *orig_ext, struct lysc_
|
||||
struct lysc_ext_metadata *ann_cdata;
|
||||
|
||||
ann_cdata = ext->compiled = *mem;
|
||||
- *mem = (char *)*mem + sizeof *ann_cdata;
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *ann_cdata);
|
||||
memset(ann_cdata, 0, sizeof *ann_cdata);
|
||||
|
||||
ext->substmts[1].storage_p = (void **)&ann_cdata->units;
|
||||
diff --git a/src/plugins_exts/schema_mount.c b/src/plugins_exts/schema_mount.c
|
||||
index df736e5ad..3dd23f992 100644
|
||||
--- a/src/plugins_exts/schema_mount.c
|
||||
+++ b/src/plugins_exts/schema_mount.c
|
||||
@@ -79,8 +79,6 @@ struct sprinter_tree_priv {
|
||||
lyplg_ext_compile_log(cctx, ext, LY_LLERR, LY_EINT, "Internal error (%s:%d).", __FILE__, __LINE__); \
|
||||
return LY_EINT
|
||||
|
||||
-#define CTXP_MEM_SIZE(SIZE) ((SIZE) + ((~(SIZE) + 1) & (8 - 1)))
|
||||
-
|
||||
/**
|
||||
* @brief Check if given mount point is unique among its siblings
|
||||
*
|
||||
@@ -1468,7 +1466,7 @@ schema_mount_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *ad
|
||||
return 0;
|
||||
}
|
||||
|
||||
- size += CTXP_MEM_SIZE(sizeof *sm_data);
|
||||
+ size += LY_CTXP_MEM_SIZE(sizeof *sm_data);
|
||||
|
||||
/* ht addr check, make sure the shared context is stored only once */
|
||||
hash = lyht_hash((const char *)&sm_data->shared, sizeof sm_data->shared);
|
||||
@@ -1476,12 +1474,12 @@ schema_mount_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *ad
|
||||
return size;
|
||||
}
|
||||
|
||||
- size += CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||
- size += CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||
+ size += LY_CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||
+ size += LY_CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||
for (i = 0; i < sm_data->shared->schema_count; ++i) {
|
||||
- size += CTXP_MEM_SIZE(ly_ctx_compiled_size(sm_data->shared->schemas[i].ctx));
|
||||
- size += CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||
- size += CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||
+ size += LY_CTXP_MEM_SIZE(ly_ctx_compiled_size(sm_data->shared->schemas[i].ctx));
|
||||
+ size += LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||
+ size += LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||
}
|
||||
|
||||
/* inlined contexts cannot be reused and will not be printed */
|
||||
@@ -1501,7 +1499,7 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||
|
||||
/* sm_data */
|
||||
ext->compiled = sm_data = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *sm_data);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *sm_data);
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
|
||||
@@ -1517,7 +1515,7 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||
|
||||
/* sm_data->shared */
|
||||
sm_data->shared = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *sm_data->shared);
|
||||
|
||||
sm_data->shared->schema_count = orig_sm_data->shared->schema_count;
|
||||
sm_data->shared->ref_count = orig_sm_data->shared->ref_count;
|
||||
@@ -1525,7 +1523,7 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||
/* sm_data->shared->schemas */
|
||||
if (orig_sm_data->shared->schemas) {
|
||||
sm_data->shared->schemas = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sm_data->shared->schema_count * sizeof *sm_data->shared->schemas);
|
||||
|
||||
for (i = 0; i < sm_data->shared->schema_count; ++i) {
|
||||
/* ctx */
|
||||
@@ -1538,12 +1536,12 @@ schema_mount_compiled_print(const struct lysc_ext_instance *orig_ext, struct lys
|
||||
/* mount_point */
|
||||
strcpy(*mem, orig_sm_data->shared->schemas[i].mount_point);
|
||||
sm_data->shared->schemas[i].mount_point = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].mount_point) + 1);
|
||||
|
||||
/* content_id */
|
||||
strcpy(*mem, orig_sm_data->shared->schemas[i].content_id);
|
||||
sm_data->shared->schemas[i].content_id = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(strlen(sm_data->shared->schemas[i].content_id) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/plugins_exts/structure.c b/src/plugins_exts/structure.c
|
||||
index b88c9a9a5..6309a2c30 100644
|
||||
--- a/src/plugins_exts/structure.c
|
||||
+++ b/src/plugins_exts/structure.c
|
||||
@@ -329,9 +329,9 @@ structure_compiled_size(const struct lysc_ext_instance *ext, struct ly_ht *addr_
|
||||
|
||||
struct_cdata = ext->compiled;
|
||||
|
||||
- size += sizeof *struct_cdata;
|
||||
+ size += LY_CTXP_MEM_SIZE(sizeof *struct_cdata);
|
||||
size += lyplg_ext_compiled_stmts_storage_size(ext->substmts, addr_ht);
|
||||
- size += sizeof *struct_cdata->top_cont;
|
||||
+ size += LY_CTXP_MEM_SIZE(sizeof *struct_cdata->top_cont);
|
||||
|
||||
return size;
|
||||
}
|
||||
@@ -347,12 +347,12 @@ structure_compiled_print(const struct lysc_ext_instance *orig_ext, struct lysc_e
|
||||
|
||||
/* ext structure */
|
||||
struct_cdata = ext->compiled = *mem;
|
||||
- *mem = (char *)*mem + sizeof *struct_cdata;
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *struct_cdata);
|
||||
memset(struct_cdata, 0, sizeof *struct_cdata);
|
||||
|
||||
/* top_cont */
|
||||
struct_cdata->top_cont = *mem;
|
||||
- *mem = (char *)*mem + sizeof *struct_cdata->top_cont;
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *struct_cdata->top_cont);
|
||||
lyplg_ext_compiled_print_add_addr(addr_ht, orig_cdata->top_cont, struct_cdata->top_cont);
|
||||
memset(struct_cdata->top_cont, 0, sizeof *struct_cdata->top_cont);
|
||||
|
||||
diff --git a/src/printer_context.c b/src/printer_context.c
|
||||
index 57d5f90e1..3fb50ac0a 100644
|
||||
--- a/src/printer_context.c
|
||||
+++ b/src/printer_context.c
|
||||
@@ -46,10 +46,10 @@
|
||||
if (ORIG_ARRAY) { \
|
||||
LY_ARRAY_COUNT_TYPE count = LY_ARRAY_COUNT(ORIG_ARRAY); \
|
||||
memcpy(*MEM, &count, sizeof count); \
|
||||
- *MEM = (char *)*MEM + CTXP_MEM_SIZE(sizeof count); \
|
||||
+ *MEM = (char *)*MEM + LY_CTXP_MEM_SIZE(sizeof count); \
|
||||
\
|
||||
ARRAY = *MEM; \
|
||||
- *MEM = (char *)*MEM + CTXP_MEM_SIZE(count * sizeof *ORIG_ARRAY); \
|
||||
+ *MEM = (char *)*MEM + LY_CTXP_MEM_SIZE(count * sizeof *ORIG_ARRAY); \
|
||||
} else { \
|
||||
ARRAY = NULL; \
|
||||
}
|
||||
@@ -67,24 +67,12 @@
|
||||
#define CTXP_OPTIONAL_STRUCT(FUNC, ORIG_PTR, PTR, ADDR_HT, PTR_SET, MEM) \
|
||||
if (ORIG_PTR) { \
|
||||
PTR = *MEM; \
|
||||
- *MEM = (char *)*MEM + CTXP_MEM_SIZE(sizeof *PTR); \
|
||||
+ *MEM = (char *)*MEM + LY_CTXP_MEM_SIZE(sizeof *PTR); \
|
||||
FUNC(ORIG_PTR, PTR, ADDR_HT, PTR_SET, MEM); \
|
||||
} else { \
|
||||
PTR = NULL; \
|
||||
}
|
||||
|
||||
-/**
|
||||
- * @brief Alignment of all the printed data, ensures all memory access is aligned to this number (B)
|
||||
- */
|
||||
-#define CTXP_MEM_ALIGN 8
|
||||
-
|
||||
-/**
|
||||
- * @brief Adjust data size to an aligned size to make sure the following data is aligned.
|
||||
- *
|
||||
- * @param[in,out] SIZE Data size that is adjusted.
|
||||
- */
|
||||
-#define CTXP_MEM_SIZE(SIZE) ((SIZE) + ((~(SIZE) + 1) & (CTXP_MEM_ALIGN - 1)))
|
||||
-
|
||||
static void
|
||||
ctxs_dict_strings(const struct ly_ht *ht, int *size)
|
||||
{
|
||||
@@ -96,7 +84,7 @@ ctxs_dict_strings(const struct ly_ht *ht, int *size)
|
||||
dict_rec = (struct ly_dict_rec *)&rec->val;
|
||||
|
||||
/* strings */
|
||||
- *size += CTXP_MEM_SIZE(strlen(dict_rec->value) + 1);
|
||||
+ *size += LY_CTXP_MEM_SIZE(strlen(dict_rec->value) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +105,7 @@ ctxs_exts(const struct lysc_ext_instance *exts, struct ly_ht *ht, int *size)
|
||||
|
||||
/* compiled, substmts storage */
|
||||
if (exts[u].def->plugin_ref && (ext_plg = LYSC_GET_EXT_PLG(exts[u].def->plugin_ref)) && ext_plg->compiled_size) {
|
||||
- *size += CTXP_MEM_SIZE(ext_plg->compiled_size(&exts[u], ht));
|
||||
+ *size += LY_CTXP_MEM_SIZE(ext_plg->compiled_size(&exts[u], ht));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +119,7 @@ ctxs_prefixes(const struct lysc_prefix *prefixes, int *size)
|
||||
LY_ARRAY_FOR(prefixes, u) {
|
||||
/* string not in the dictionary */
|
||||
if (prefixes[u].prefix) {
|
||||
- *size += CTXP_MEM_SIZE(strlen(prefixes[u].prefix) + 1);
|
||||
+ *size += LY_CTXP_MEM_SIZE(strlen(prefixes[u].prefix) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,20 +129,20 @@ ctxs_expr(const struct lyxp_expr *exp, int *size)
|
||||
{
|
||||
uint32_t i, j;
|
||||
|
||||
- *size += CTXP_MEM_SIZE(sizeof *exp);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *exp);
|
||||
|
||||
- *size += CTXP_MEM_SIZE(strlen(exp->expr) + 1);
|
||||
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->tokens);
|
||||
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_pos);
|
||||
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_len);
|
||||
- *size += CTXP_MEM_SIZE(exp->used * sizeof *exp->repeat);
|
||||
+ *size += LY_CTXP_MEM_SIZE(strlen(exp->expr) + 1);
|
||||
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->tokens);
|
||||
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_pos);
|
||||
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->tok_len);
|
||||
+ *size += LY_CTXP_MEM_SIZE(exp->used * sizeof *exp->repeat);
|
||||
for (i = 0; i < exp->used; ++i) {
|
||||
if (!exp->repeat[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; exp->repeat[i][j]; ++j) {}
|
||||
- *size += CTXP_MEM_SIZE((j + 1) * sizeof **exp->repeat);
|
||||
+ *size += LY_CTXP_MEM_SIZE((j + 1) * sizeof **exp->repeat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +170,7 @@ ctxs_when(const struct lysc_when *when, struct ly_ht *ht, int *size)
|
||||
return;
|
||||
}
|
||||
|
||||
- *size += CTXP_MEM_SIZE(sizeof *when);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *when);
|
||||
|
||||
ctxs_expr(when->cond, size);
|
||||
ctxs_prefixes(when->prefixes, size);
|
||||
@@ -207,7 +195,7 @@ ctxs_range(const struct lysc_range *range, struct ly_ht *ht, int *size)
|
||||
return;
|
||||
}
|
||||
|
||||
- *size += CTXP_MEM_SIZE(sizeof *range);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *range);
|
||||
|
||||
*size += CTXS_SIZED_ARRAY(range->parts);
|
||||
ctxs_exts(range->exts, ht, size);
|
||||
@@ -227,7 +215,7 @@ ctxs_patterns(const struct lysc_pattern **patterns, struct ly_ht *ht, int *size)
|
||||
continue;
|
||||
}
|
||||
|
||||
- *size += CTXP_MEM_SIZE(sizeof *patterns[u]);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *patterns[u]);
|
||||
ctxs_exts(patterns[u]->exts, ht, size);
|
||||
}
|
||||
}
|
||||
@@ -270,7 +258,7 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||
switch (type->basetype) {
|
||||
case LY_TYPE_BINARY:
|
||||
type_bin = (const struct lysc_type_bin *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_bin);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_bin);
|
||||
|
||||
ctxs_range(type_bin->length, ht, size);
|
||||
break;
|
||||
@@ -283,13 +271,13 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||
case LY_TYPE_INT32:
|
||||
case LY_TYPE_INT64:
|
||||
type_num = (const struct lysc_type_num *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_num);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_num);
|
||||
|
||||
ctxs_range(type_num->range, ht, size);
|
||||
break;
|
||||
case LY_TYPE_STRING:
|
||||
type_str = (const struct lysc_type_str *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_str);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_str);
|
||||
|
||||
ctxs_range(type_str->length, ht, size);
|
||||
ctxs_patterns((const struct lysc_pattern **)type_str->patterns, ht, size);
|
||||
@@ -297,33 +285,33 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||
case LY_TYPE_BITS:
|
||||
case LY_TYPE_ENUM:
|
||||
type_enum_bits = (const struct lysc_type_enum *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_enum_bits);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_enum_bits);
|
||||
|
||||
ctxs_enums(type_enum_bits->enums, ht, size);
|
||||
break;
|
||||
case LY_TYPE_BOOL:
|
||||
case LY_TYPE_EMPTY:
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type);
|
||||
break;
|
||||
case LY_TYPE_DEC64:
|
||||
type_dec = (const struct lysc_type_dec *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_dec);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_dec);
|
||||
|
||||
ctxs_range(type_dec->range, ht, size);
|
||||
break;
|
||||
case LY_TYPE_IDENT:
|
||||
type_identref = (const struct lysc_type_identityref *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_identref);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_identref);
|
||||
|
||||
*size += CTXS_SIZED_ARRAY(type_identref->bases);
|
||||
break;
|
||||
case LY_TYPE_INST:
|
||||
type_instid = (const struct lysc_type_instanceid *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_instid);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_instid);
|
||||
break;
|
||||
case LY_TYPE_LEAFREF:
|
||||
type_lref = (const struct lysc_type_leafref *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_lref);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_lref);
|
||||
|
||||
ctxs_expr(type_lref->path, size);
|
||||
ctxs_prefixes(type_lref->prefixes, size);
|
||||
@@ -331,7 +319,7 @@ ctxs_type(const struct lysc_type *type, struct ly_ht *ht, int *size)
|
||||
break;
|
||||
case LY_TYPE_UNION:
|
||||
type_union = (const struct lysc_type_union *)type;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *type_union);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *type_union);
|
||||
|
||||
*size += CTXS_SIZED_ARRAY(type_union->types);
|
||||
LY_ARRAY_FOR(type_union->types, u) {
|
||||
@@ -366,7 +354,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
switch (node->nodetype) {
|
||||
case LYS_CONTAINER:
|
||||
cont = (const struct lysc_node_container *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *cont);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *cont);
|
||||
|
||||
LY_LIST_FOR(cont->child, child) {
|
||||
ctxs_node(child, ht, size);
|
||||
@@ -382,7 +370,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
break;
|
||||
case LYS_CHOICE:
|
||||
choic = (const struct lysc_node_choice *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *choic);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *choic);
|
||||
|
||||
LY_LIST_FOR((const struct lysc_node *)choic->cases, child) {
|
||||
ctxs_node(child, ht, size);
|
||||
@@ -391,7 +379,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
break;
|
||||
case LYS_LEAF:
|
||||
leaf = (const struct lysc_node_leaf *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *leaf);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *leaf);
|
||||
|
||||
ctxs_musts(leaf->musts, ht, size);
|
||||
ctxs_whens((const struct lysc_when **)leaf->when, ht, size);
|
||||
@@ -400,7 +388,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
break;
|
||||
case LYS_LEAFLIST:
|
||||
llist = (const struct lysc_node_leaflist *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *llist);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *llist);
|
||||
|
||||
ctxs_musts(llist->musts, ht, size);
|
||||
ctxs_whens((const struct lysc_when **)llist->when, ht, size);
|
||||
@@ -412,7 +400,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
break;
|
||||
case LYS_LIST:
|
||||
list = (const struct lysc_node_list *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *list);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *list);
|
||||
|
||||
LY_LIST_FOR(list->child, child) {
|
||||
ctxs_node(child, ht, size);
|
||||
@@ -427,21 +415,21 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
}
|
||||
*size += CTXS_SIZED_ARRAY(list->uniques);
|
||||
LY_ARRAY_FOR(list->uniques, u) {
|
||||
- *size += CTXP_MEM_SIZE(sizeof(LY_ARRAY_COUNT_TYPE)) +
|
||||
- CTXP_MEM_SIZE(LY_ARRAY_COUNT(list->uniques[u]) * sizeof **list->uniques);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof(LY_ARRAY_COUNT_TYPE)) +
|
||||
+ LY_CTXP_MEM_SIZE(LY_ARRAY_COUNT(list->uniques[u]) * sizeof **list->uniques);
|
||||
}
|
||||
break;
|
||||
case LYS_ANYXML:
|
||||
case LYS_ANYDATA:
|
||||
any = (const struct lysc_node_anydata *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *any);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *any);
|
||||
|
||||
ctxs_musts(any->musts, ht, size);
|
||||
ctxs_whens((const struct lysc_when **)any->when, ht, size);
|
||||
break;
|
||||
case LYS_CASE:
|
||||
cas = (const struct lysc_node_case *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *cas);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *cas);
|
||||
|
||||
LY_LIST_FOR(cas->child, child) {
|
||||
ctxs_node(child, ht, size);
|
||||
@@ -451,7 +439,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
case LYS_RPC:
|
||||
case LYS_ACTION:
|
||||
act = (const struct lysc_node_action *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *act);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *act);
|
||||
|
||||
ctxs_whens((const struct lysc_when **)act->when, ht, size);
|
||||
ctxs_node((struct lysc_node *)&act->input, ht, size);
|
||||
@@ -469,7 +457,7 @@ ctxs_node(const struct lysc_node *node, struct ly_ht *ht, int *size)
|
||||
break;
|
||||
case LYS_NOTIF:
|
||||
notif = (const struct lysc_node_notif *)node;
|
||||
- *size += CTXP_MEM_SIZE(sizeof *notif);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *notif);
|
||||
|
||||
LY_LIST_FOR(notif->child, child) {
|
||||
ctxs_node(child, ht, size);
|
||||
@@ -493,7 +481,7 @@ ctxs_compiled(const struct lysc_module *compiled, struct ly_ht *ht, int *size)
|
||||
}
|
||||
|
||||
/* compiled module */
|
||||
- *size += CTXP_MEM_SIZE(sizeof *compiled);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *compiled);
|
||||
|
||||
*size += CTXS_SIZED_ARRAY(compiled->features);
|
||||
LY_LIST_FOR(compiled->data, node) {
|
||||
@@ -539,7 +527,7 @@ static void
|
||||
ctxs_module(const struct lys_module *mod, struct ly_ht *ht, int *size)
|
||||
{
|
||||
/* module */
|
||||
- *size += CTXP_MEM_SIZE(sizeof *mod);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *mod);
|
||||
|
||||
/* compiled module */
|
||||
ctxs_compiled(mod->compiled, ht, size);
|
||||
@@ -561,13 +549,13 @@ ly_ctx_compiled_size_context(const struct ly_ctx *ctx, struct ly_ht *addr_ht, in
|
||||
const struct lys_module *mod;
|
||||
|
||||
/* context */
|
||||
- *size += CTXP_MEM_SIZE(sizeof *ctx);
|
||||
+ *size += LY_CTXP_MEM_SIZE(sizeof *ctx);
|
||||
|
||||
/* all the strings in the dictionary */
|
||||
ctxs_dict_strings(ctx->dict.hash_tab, size);
|
||||
|
||||
/* module set */
|
||||
- *size += CTXP_MEM_SIZE(ctx->modules.count * sizeof ctx->modules.objs);
|
||||
+ *size += LY_CTXP_MEM_SIZE(ctx->modules.count * sizeof ctx->modules.objs);
|
||||
for (i = 0; i < ctx->modules.count; ++i) {
|
||||
mod = ctx->modules.objs[i];
|
||||
|
||||
@@ -720,7 +708,7 @@ ctxp_set(const struct ly_set *orig_set, struct ly_set *set, void **mem)
|
||||
set->size = orig_set->count;
|
||||
set->count = orig_set->count;
|
||||
set->objs = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(set->count * sizeof set->objs);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(set->count * sizeof set->objs);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -741,7 +729,7 @@ ctxp_dict_strings(const struct ly_ht *orig_dict, struct ly_ht *addr_ht, void **m
|
||||
orig_str = dict_rec->value;
|
||||
|
||||
str = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(len);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(len);
|
||||
|
||||
memcpy(str, orig_str, len);
|
||||
|
||||
@@ -801,30 +789,30 @@ ctxp_expr(const struct lyxp_expr *orig_exp, struct lyxp_expr *exp, void **mem)
|
||||
uint32_t i, len;
|
||||
|
||||
exp->expr = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(strlen(orig_exp->expr) + 1);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(strlen(orig_exp->expr) + 1);
|
||||
memcpy(exp->expr, orig_exp->expr, strlen(orig_exp->expr) + 1);
|
||||
|
||||
exp->tokens = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tokens);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tokens);
|
||||
memcpy(exp->tokens, orig_exp->tokens, orig_exp->used * sizeof *exp->tokens);
|
||||
|
||||
exp->tok_pos = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_pos);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_pos);
|
||||
memcpy(exp->tok_pos, orig_exp->tok_pos, orig_exp->used * sizeof *exp->tok_pos);
|
||||
|
||||
exp->tok_len = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_len);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->tok_len);
|
||||
memcpy(exp->tok_len, orig_exp->tok_len, orig_exp->used * sizeof *exp->tok_len);
|
||||
|
||||
exp->repeat = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->repeat);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(orig_exp->used * sizeof *exp->repeat);
|
||||
for (i = 0; i < orig_exp->used; ++i) {
|
||||
if (orig_exp->repeat[i]) {
|
||||
for (len = 0; orig_exp->repeat[i][len]; ++len) {}
|
||||
++len;
|
||||
|
||||
exp->repeat[i] = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(len * sizeof **exp->repeat);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(len * sizeof **exp->repeat);
|
||||
memcpy(exp->repeat[i], orig_exp->repeat[i], len * sizeof **exp->repeat);
|
||||
} else {
|
||||
exp->repeat[i] = NULL;
|
||||
@@ -845,7 +833,7 @@ ctxp_prefix(const struct lysc_prefix *orig_prefix, struct lysc_prefix *prefix, s
|
||||
size = strlen(orig_prefix->prefix) + 1;
|
||||
|
||||
prefix->prefix = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(size);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(size);
|
||||
memcpy(prefix->prefix, orig_prefix->prefix, size);
|
||||
} else {
|
||||
prefix->prefix = NULL;
|
||||
@@ -867,7 +855,7 @@ ctxp_must(const struct lysc_must *orig_must, struct lysc_must *must, struct ly_h
|
||||
}
|
||||
|
||||
must->cond = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *must->cond);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *must->cond);
|
||||
ctxp_expr(orig_must->cond, must->cond, mem);
|
||||
|
||||
CTXP_SIZED_ARRAY(orig_must->prefixes, must->prefixes, mem);
|
||||
@@ -901,7 +889,7 @@ ctxp_when(const struct lysc_when *orig_when, struct lysc_when **when, struct ly_
|
||||
}
|
||||
|
||||
w = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *w);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *w);
|
||||
|
||||
if (orig_when->exts) {
|
||||
/* may be referenced in the parent */
|
||||
@@ -909,7 +897,7 @@ ctxp_when(const struct lysc_when *orig_when, struct lysc_when **when, struct ly_
|
||||
}
|
||||
|
||||
w->cond = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *w->cond);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *w->cond);
|
||||
ctxp_expr(orig_when->cond, w->cond, mem);
|
||||
|
||||
w->context = ly_ctx_compiled_addr_ht_get(addr_ht, orig_when->context, 0);
|
||||
@@ -980,7 +968,7 @@ ctxp_children(const struct lysc_node *orig_child, struct lysc_node **child, stru
|
||||
}
|
||||
|
||||
ch = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(node_size);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(node_size);
|
||||
|
||||
/* referenced by pointers */
|
||||
ly_ctx_compiled_addr_ht_add(addr_ht, orig_child, ch);
|
||||
@@ -1035,7 +1023,7 @@ ctxp_pattern(const struct lysc_pattern *orig_pattern, struct lysc_pattern **patt
|
||||
}
|
||||
|
||||
p = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *p);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *p);
|
||||
|
||||
if (orig_pattern->exts) {
|
||||
/* may be referenced in the parent */
|
||||
@@ -1122,7 +1110,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_BINARY:
|
||||
orig_type_bin = (const struct lysc_type_bin *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_bin);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_bin);
|
||||
t_bin = (struct lysc_type_bin *)t;
|
||||
|
||||
CTXP_OPTIONAL_STRUCT(ctxp_range, orig_type_bin->length, t_bin->length, addr_ht, ptr_set, mem);
|
||||
@@ -1137,7 +1125,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_INT64:
|
||||
orig_type_num = (const struct lysc_type_num *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_num);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_num);
|
||||
t_num = (struct lysc_type_num *)t;
|
||||
|
||||
CTXP_OPTIONAL_STRUCT(ctxp_range, orig_type_num->range, t_num->range, addr_ht, ptr_set, mem);
|
||||
@@ -1145,7 +1133,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_STRING:
|
||||
orig_type_str = (const struct lysc_type_str *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_str);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_str);
|
||||
t_str = (struct lysc_type_str *)t;
|
||||
|
||||
CTXP_OPTIONAL_STRUCT(ctxp_range, orig_type_str->length, t_str->length, addr_ht, ptr_set, mem);
|
||||
@@ -1158,7 +1146,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_ENUM:
|
||||
orig_type_enum_bits = (const struct lysc_type_enum *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_enum_bits);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_enum_bits);
|
||||
t_enum_bits = (struct lysc_type_enum *)t;
|
||||
|
||||
CTXP_SIZED_ARRAY(orig_type_enum_bits->enums, t_enum_bits->enums, mem);
|
||||
@@ -1170,12 +1158,12 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_EMPTY:
|
||||
/* no additional members */
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type);
|
||||
break;
|
||||
case LY_TYPE_DEC64:
|
||||
orig_type_dec = (const struct lysc_type_dec *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_dec);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_dec);
|
||||
t_dec = (struct lysc_type_dec *)t;
|
||||
|
||||
t_dec->fraction_digits = orig_type_dec->fraction_digits;
|
||||
@@ -1184,7 +1172,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_IDENT:
|
||||
orig_type_identref = (const struct lysc_type_identityref *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_identref);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_identref);
|
||||
t_identref = (struct lysc_type_identityref *)t;
|
||||
|
||||
CTXP_SIZED_ARRAY(orig_type_identref->bases, t_identref->bases, mem);
|
||||
@@ -1197,7 +1185,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_INST:
|
||||
orig_type_instid = (const struct lysc_type_instanceid *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_instid);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_instid);
|
||||
t_instid = (struct lysc_type_instanceid *)t;
|
||||
|
||||
t_instid->require_instance = orig_type_instid->require_instance;
|
||||
@@ -1205,11 +1193,11 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_LEAFREF:
|
||||
orig_type_lref = (const struct lysc_type_leafref *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_lref);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_lref);
|
||||
t_lref = (struct lysc_type_leafref *)t;
|
||||
|
||||
t_lref->path = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *t_lref->path);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *t_lref->path);
|
||||
ctxp_expr(orig_type_lref->path, t_lref->path, mem);
|
||||
|
||||
CTXP_SIZED_ARRAY(orig_type_lref->prefixes, t_lref->prefixes, mem);
|
||||
@@ -1223,7 +1211,7 @@ ctxp_type(const struct lysc_type *orig_type, struct lysc_type **type, struct ly_
|
||||
case LY_TYPE_UNION:
|
||||
orig_type_union = (const struct lysc_type_union *)orig_type;
|
||||
t = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof *orig_type_union);
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof *orig_type_union);
|
||||
t_union = (struct lysc_type_union *)t;
|
||||
|
||||
CTXP_SIZED_ARRAY(orig_type_union->types, t_union->types, mem);
|
||||
@@ -1736,7 +1724,7 @@ ly_ctx_compiled_print_context(const struct ly_ctx *orig_ctx, struct ly_ctx *ctx,
|
||||
for (i = 0; i < ctx->modules.count; ++i) {
|
||||
/* allocate the shared module and store its new address in HT so it can be referenced */
|
||||
ctx->modules.objs[i] = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof(struct lys_module));
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof(struct lys_module));
|
||||
ly_ctx_compiled_addr_ht_add(addr_ht, orig_ctx->modules.objs[i], ctx->modules.objs[i]);
|
||||
}
|
||||
for (i = 0; i < ctx->modules.count; ++i) {
|
||||
@@ -1864,7 +1852,7 @@ ly_ctx_compiled_ext_stmts_storage_print(const struct lysc_ext_substmt *orig_subs
|
||||
case LY_STMT_LENGTH:
|
||||
case LY_STMT_RANGE:
|
||||
*substmts[u].storage_p = *mem;
|
||||
- *mem = (char *)*mem + CTXP_MEM_SIZE(sizeof(struct lysc_range));
|
||||
+ *mem = (char *)*mem + LY_CTXP_MEM_SIZE(sizeof(struct lysc_range));
|
||||
ctxp_range(*orig_substmts[u].storage_p, *substmts[u].storage_p, addr_ht, ptr_set, mem);
|
||||
break;
|
||||
case LY_STMT_MAX_ELEMENTS:
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
From 4d32b003a7aaad5ecca9a013db2dbaa36ef79692 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Wed, 10 Dec 2025 10:13:54 +0100
|
||||
Subject: [PATCH 2/6] printer context BUGFIX missing mem alignment
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Refs #2455
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/context.c | 2 +-
|
||||
src/printer_context.c | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/context.c b/src/context.c
|
||||
index 88f4678b3..52a4ae46d 100644
|
||||
--- a/src/context.c
|
||||
+++ b/src/context.c
|
||||
@@ -1459,7 +1459,7 @@ ly_ctx_compiled_print(const struct ly_ctx *ctx, void *mem, void **mem_end)
|
||||
|
||||
/* context, referenced */
|
||||
pctx = mem;
|
||||
- mem = (char *)mem + sizeof *pctx;
|
||||
+ mem = (char *)mem + LY_CTXP_MEM_SIZE(sizeof *pctx);
|
||||
ly_ctx_compiled_addr_ht_add(addr_ht, ctx, pctx);
|
||||
|
||||
/* members */
|
||||
diff --git a/src/printer_context.c b/src/printer_context.c
|
||||
index 3fb50ac0a..ade0a210d 100644
|
||||
--- a/src/printer_context.c
|
||||
+++ b/src/printer_context.c
|
||||
@@ -33,7 +33,7 @@
|
||||
* @return Size of @p ARRAY.
|
||||
*/
|
||||
#define CTXS_SIZED_ARRAY(ARRAY) \
|
||||
- (ARRAY ? sizeof(LY_ARRAY_COUNT_TYPE) : 0) + LY_ARRAY_COUNT(ARRAY) * sizeof *ARRAY
|
||||
+ (ARRAY ? LY_CTXP_MEM_SIZE(sizeof(LY_ARRAY_COUNT_TYPE)) : 0) + LY_CTXP_MEM_SIZE(LY_ARRAY_COUNT(ARRAY) * sizeof *ARRAY)
|
||||
|
||||
/**
|
||||
* @brief Print (serialize) a sized array.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
From 51f257fe100adde66efc4d93a5e40b2e5a9a9fab Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Thu, 11 Dec 2025 12:05:02 +0100
|
||||
Subject: [PATCH 3/6] lyb BUGFIX big-endian fixes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Refs #2455
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/parser_lyb.c | 8 +++++++-
|
||||
src/printer_lyb.c | 18 ++++++++++++------
|
||||
2 files changed, 19 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/parser_lyb.c b/src/parser_lyb.c
|
||||
index 43ab02f95..82c759c3d 100644
|
||||
--- a/src/parser_lyb.c
|
||||
+++ b/src/parser_lyb.c
|
||||
@@ -1425,7 +1425,10 @@ lyb_parse_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_n
|
||||
char *mod_name = NULL, mod_rev[LY_REV_SIZE];
|
||||
|
||||
/* read node type */
|
||||
- lyb_read((uint64_t *)&lyb_type, LYB_NODE_TYPE_BITS, lybctx->parse_ctx);
|
||||
+ lyb_read(&lyb_type, LYB_NODE_TYPE_BITS, lybctx->parse_ctx);
|
||||
+
|
||||
+ /* correct byte order */
|
||||
+ lyb_type = le32toh(lyb_type);
|
||||
|
||||
switch (lyb_type) {
|
||||
case LYB_NODE_END:
|
||||
@@ -1543,6 +1546,9 @@ lyb_parse_header(struct lyd_lyb_ctx *lybctx)
|
||||
data_hash = 0;
|
||||
lyb_read((uint8_t *)&data_hash, LYB_HEADER_CTX_HASH_BITS, pctx);
|
||||
|
||||
+ /* correct byte order */
|
||||
+ data_hash = le32toh(data_hash);
|
||||
+
|
||||
if (!data_hash) {
|
||||
/* fine for no data */
|
||||
pctx->empty_hash = 1;
|
||||
diff --git a/src/printer_lyb.c b/src/printer_lyb.c
|
||||
index b35d3c0fe..7094467a7 100644
|
||||
--- a/src/printer_lyb.c
|
||||
+++ b/src/printer_lyb.c
|
||||
@@ -447,12 +447,12 @@ lyb_write_count(uint32_t count, struct lylyb_print_ctx *lybctx)
|
||||
return LY_EINT;
|
||||
}
|
||||
|
||||
- /* correct byte order */
|
||||
- count = htole32(count);
|
||||
-
|
||||
/* copy count to buf */
|
||||
buf |= count << prefix_b;
|
||||
|
||||
+ /* correct byte order */
|
||||
+ buf = htole32(buf);
|
||||
+
|
||||
return lyb_write(&buf, prefix_b + num_b, lybctx);
|
||||
}
|
||||
|
||||
@@ -492,12 +492,12 @@ lyb_write_size(uint32_t size, struct lylyb_print_ctx *lybctx)
|
||||
num_b = 32;
|
||||
}
|
||||
|
||||
- /* correct byte order */
|
||||
- size = htole32(size);
|
||||
-
|
||||
/* copy size to buf */
|
||||
buf |= size << prefix_b;
|
||||
|
||||
+ /* correct byte order */
|
||||
+ buf = htole32(buf);
|
||||
+
|
||||
return lyb_write(&buf, prefix_b + num_b, lybctx);
|
||||
}
|
||||
|
||||
@@ -635,6 +635,9 @@ lyb_print_header(struct lylyb_print_ctx *lybctx)
|
||||
/* context hash (is truncated), if not printing empty data */
|
||||
if (lybctx->ctx) {
|
||||
hash = lyb_truncate_hash_nonzero(ly_ctx_get_modules_hash(lybctx->ctx), LYB_HEADER_CTX_HASH_BITS);
|
||||
+
|
||||
+ /* correct byte order */
|
||||
+ hash = htole32(hash);
|
||||
} else {
|
||||
hash = 0;
|
||||
}
|
||||
@@ -1019,6 +1022,9 @@ lyb_print_lyb_type(const struct lyd_node *node, struct lylyb_print_ctx *lybctx)
|
||||
lyb_type = LYB_NODE_CHILD;
|
||||
}
|
||||
|
||||
+ /* correct byte order */
|
||||
+ lyb_type = htole32(lyb_type);
|
||||
+
|
||||
LY_CHECK_RET(lyb_write(&lyb_type, LYB_NODE_TYPE_BITS, lybctx));
|
||||
|
||||
return LY_SUCCESS;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
From a44103219029ba70b67a87d5a33cfb8aec1e03f3 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Thu, 11 Dec 2025 12:05:51 +0100
|
||||
Subject: [PATCH 4/6] bits BUGFIX operator priority
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Refs #2455
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/plugins_types/bits.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/plugins_types/bits.c b/src/plugins_types/bits.c
|
||||
index ab7acb527..f5fa6dccb 100644
|
||||
--- a/src/plugins_types/bits.c
|
||||
+++ b/src/plugins_types/bits.c
|
||||
@@ -61,7 +61,7 @@ lyplg_type_bits_is_bit_set(const char *bitmap, uint32_t size_bits, uint32_t bit_
|
||||
uint32_t size;
|
||||
|
||||
/* get size in bytes */
|
||||
- size = size_bits / 8 + (size_bits % 8) ? 1 : 0;
|
||||
+ size = size_bits / 8 + ((size_bits % 8) ? 1 : 0);
|
||||
|
||||
/* find the byte with our bit */
|
||||
(void)size;
|
||||
@@ -93,7 +93,7 @@ bits_bit_set(char *bitmap, uint32_t size_bits, uint32_t bit_position)
|
||||
uint32_t size;
|
||||
|
||||
/* get size in bytes */
|
||||
- size = size_bits / 8 + (size_bits % 8) ? 1 : 0;
|
||||
+ size = size_bits / 8 + ((size_bits % 8) ? 1 : 0);
|
||||
|
||||
/* find the byte with our bit */
|
||||
(void)size;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
From e2c06476b886642411181e0a236b95de14145940 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Thu, 11 Dec 2025 12:06:15 +0100
|
||||
Subject: [PATCH 5/6] integer BUGFIX big-endian print fixes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Refs #2455
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/plugins_types/integer.c | 96 ++++++++++---------------------------
|
||||
1 file changed, 24 insertions(+), 72 deletions(-)
|
||||
|
||||
diff --git a/src/plugins_types/integer.c b/src/plugins_types/integer.c
|
||||
index 945d93f86..f05096970 100644
|
||||
--- a/src/plugins_types/integer.c
|
||||
+++ b/src/plugins_types/integer.c
|
||||
@@ -291,64 +291,6 @@ lyplg_type_sort_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *va
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static const void *
|
||||
-lyplg_type_print_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format,
|
||||
- void *UNUSED(prefix_data), ly_bool *dynamic, uint32_t *value_size_bits)
|
||||
-{
|
||||
- int64_t prev_num = 0, num = 0;
|
||||
- void *buf;
|
||||
-
|
||||
- if (format == LY_VALUE_LYB) {
|
||||
- switch (value->realtype->basetype) {
|
||||
- case LY_TYPE_INT8:
|
||||
- prev_num = num = value->int8;
|
||||
- break;
|
||||
- case LY_TYPE_INT16:
|
||||
- prev_num = num = value->int16;
|
||||
- break;
|
||||
- case LY_TYPE_INT32:
|
||||
- prev_num = num = value->int32;
|
||||
- break;
|
||||
- case LY_TYPE_INT64:
|
||||
- prev_num = num = value->int64;
|
||||
- break;
|
||||
- default:
|
||||
- break;
|
||||
- }
|
||||
-
|
||||
- num = htole64(num);
|
||||
- if (num == prev_num) {
|
||||
- /* values are equal, little-endian or int8 */
|
||||
- *dynamic = 0;
|
||||
- if (value_size_bits) {
|
||||
- /* the least amount of bits that can hold the number */
|
||||
- *value_size_bits = lyplg_type_get_highest_set_bit_pos(num);
|
||||
- }
|
||||
- return &value->int64;
|
||||
- } else {
|
||||
- /* values differ, big-endian */
|
||||
- buf = calloc(1, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||
- LY_CHECK_RET(!buf, NULL);
|
||||
-
|
||||
- *dynamic = 1;
|
||||
- if (value_size_bits) {
|
||||
- *value_size_bits = lyplg_type_get_highest_set_bit_pos(num);
|
||||
- }
|
||||
- memcpy(buf, &num, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||
- return buf;
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- /* use the cached canonical value */
|
||||
- if (dynamic) {
|
||||
- *dynamic = 0;
|
||||
- }
|
||||
- if (value_size_bits) {
|
||||
- *value_size_bits = strlen(value->_canonical) * 8;
|
||||
- }
|
||||
- return value->_canonical;
|
||||
-}
|
||||
-
|
||||
static LY_ERR
|
||||
lyplg_type_store_uint(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, uint32_t value_size_bits,
|
||||
uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints,
|
||||
@@ -573,31 +515,37 @@ lyplg_type_sort_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *v
|
||||
}
|
||||
|
||||
static const void *
|
||||
-lyplg_type_print_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format,
|
||||
+lyplg_type_print_u_int(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *value, LY_VALUE_FORMAT format,
|
||||
void *UNUSED(prefix_data), ly_bool *dynamic, uint32_t *value_size_bits)
|
||||
{
|
||||
uint64_t num = 0;
|
||||
+ uint8_t bytes_used;
|
||||
+ uint16_t bits_used;
|
||||
void *buf;
|
||||
|
||||
if (format == LY_VALUE_LYB) {
|
||||
switch (value->realtype->basetype) {
|
||||
case LY_TYPE_UINT8:
|
||||
+ case LY_TYPE_INT8:
|
||||
num = value->uint8;
|
||||
break;
|
||||
case LY_TYPE_UINT16:
|
||||
+ case LY_TYPE_INT16:
|
||||
num = value->uint16;
|
||||
break;
|
||||
case LY_TYPE_UINT32:
|
||||
+ case LY_TYPE_INT32:
|
||||
num = value->uint32;
|
||||
break;
|
||||
case LY_TYPE_UINT64:
|
||||
+ case LY_TYPE_INT64:
|
||||
num = value->uint64;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
- num = htole64(num);
|
||||
- if (num == value->uint64) {
|
||||
+
|
||||
+ if (htole64(num) == value->uint64) {
|
||||
/* values are equal, little-endian or uint8 */
|
||||
*dynamic = 0;
|
||||
if (value_size_bits) {
|
||||
@@ -607,14 +555,18 @@ lyplg_type_print_uint(const struct ly_ctx *UNUSED(ctx), const struct lyd_value *
|
||||
return &value->uint64;
|
||||
} else {
|
||||
/* values differ, big-endian */
|
||||
- buf = calloc(1, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||
+ bits_used = lyplg_type_get_highest_set_bit_pos(num);
|
||||
+ bytes_used = LYPLG_BITS2BYTES(bits_used);
|
||||
+
|
||||
+ buf = calloc(1, bytes_used);
|
||||
LY_CHECK_RET(!buf, NULL);
|
||||
+ num = htole64(num);
|
||||
+ memcpy(buf, &num, bytes_used);
|
||||
|
||||
*dynamic = 1;
|
||||
if (value_size_bits) {
|
||||
- *value_size_bits = lyplg_type_get_highest_set_bit_pos(num);
|
||||
+ *value_size_bits = bits_used;
|
||||
}
|
||||
- memcpy(buf, &num, LYPLG_BITS2BYTES(lyplg_type_get_highest_set_bit_pos(num)));
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
@@ -649,7 +601,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_uint,
|
||||
.plugin.sort = lyplg_type_sort_uint,
|
||||
- .plugin.print = lyplg_type_print_uint,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -664,7 +616,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_uint,
|
||||
.plugin.sort = lyplg_type_sort_uint,
|
||||
- .plugin.print = lyplg_type_print_uint,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -679,7 +631,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_uint,
|
||||
.plugin.sort = lyplg_type_sort_uint,
|
||||
- .plugin.print = lyplg_type_print_uint,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -694,7 +646,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_uint,
|
||||
.plugin.sort = lyplg_type_sort_uint,
|
||||
- .plugin.print = lyplg_type_print_uint,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -709,7 +661,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_int,
|
||||
.plugin.sort = lyplg_type_sort_int,
|
||||
- .plugin.print = lyplg_type_print_int,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -724,7 +676,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_int,
|
||||
.plugin.sort = lyplg_type_sort_int,
|
||||
- .plugin.print = lyplg_type_print_int,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -739,7 +691,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_int,
|
||||
.plugin.sort = lyplg_type_sort_int,
|
||||
- .plugin.print = lyplg_type_print_int,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
}, {
|
||||
@@ -754,7 +706,7 @@ const struct lyplg_type_record plugins_integer[] = {
|
||||
.plugin.validate_tree = NULL,
|
||||
.plugin.compare = lyplg_type_compare_int,
|
||||
.plugin.sort = lyplg_type_sort_int,
|
||||
- .plugin.print = lyplg_type_print_int,
|
||||
+ .plugin.print = lyplg_type_print_u_int,
|
||||
.plugin.duplicate = lyplg_type_dup_simple,
|
||||
.plugin.free = lyplg_type_free_simple,
|
||||
},
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 0e911b67177252160f7f1361e2481a88200debc3 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Thu, 11 Dec 2025 15:20:28 +0100
|
||||
Subject: [PATCH 6/6] context BUGFIX unexpected context node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Fixes #2457
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/context.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/context.c b/src/context.c
|
||||
index 52a4ae46d..1c287d9f2 100644
|
||||
--- a/src/context.c
|
||||
+++ b/src/context.c
|
||||
@@ -370,7 +370,7 @@ ly_ctx_new_yl_legacy(struct ly_ctx *ctx, const struct lyd_node *yltree)
|
||||
LY_ERR ret = LY_SUCCESS;
|
||||
uint32_t i, j;
|
||||
|
||||
- LY_CHECK_RET(ret = lyd_find_xpath(yltree, "modules-state/module", &set));
|
||||
+ LY_CHECK_RET(ret = lyd_find_xpath(yltree, "/ietf-yang-library:modules-state/module", &set));
|
||||
|
||||
if (!set->count) {
|
||||
ret = LY_ENOTFOUND;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
From 8406098242751deb3de1172ceda382251fd2c8f5 Mon Sep 17 00:00:00 2001
|
||||
From 84a8003b97caaf602be1406e8291cea10d5cb7c1 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 22 Jun 2023 10:24:57 +0200
|
||||
Subject: [PATCH 1/2] Allow 'factory' as copy-from (only) in rpc copy-config
|
||||
Subject: [PATCH 1/4] Allow 'factory' as copy-from (only) in rpc copy-config
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -14,7 +14,7 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/src/netconf.c b/src/netconf.c
|
||||
index 1d9ba79..2a6f1a4 100644
|
||||
index 764f1f6..084e1d4 100644
|
||||
--- a/src/netconf.c
|
||||
+++ b/src/netconf.c
|
||||
@@ -334,6 +334,8 @@ np2srv_rpc_copyconfig_cb(const struct lyd_node *rpc, struct np_user_sess *user_s
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
From 6ad71e6359453b8dcecbf9f33f59bd736111ecf3 Mon Sep 17 00:00:00 2001
|
||||
From 010621423617d87b87f3484188ce8d6b960b50b6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Tue, 4 Feb 2025 20:14:50 +0100
|
||||
Subject: [PATCH 2/2] Do not generate data in sysrepo
|
||||
Subject: [PATCH 2/4] Do not generate data in sysrepo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -16,7 +16,7 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 40 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index cf97275..95b82ae 100644
|
||||
index 71c8bdd..594363f 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -395,47 +395,7 @@ if(SYSREPO_SETUP)
|
||||
@@ -0,0 +1,56 @@
|
||||
From d3a5114761d077ad72171998045d37cbf46bb7d0 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Thu, 4 Dec 2025 14:13:10 +0100
|
||||
Subject: [PATCH 3/4] main UPDATE support having local users disabled
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/main.c | 21 +++++++++++++++------
|
||||
1 file changed, 15 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/src/main.c b/src/main.c
|
||||
index 64ffaaa..7492b43 100644
|
||||
--- a/src/main.c
|
||||
+++ b/src/main.c
|
||||
@@ -1158,6 +1158,8 @@ server_data_subscribe(void)
|
||||
{
|
||||
int rc;
|
||||
const char *mod_name, *xpath;
|
||||
+ const struct lys_module *mod;
|
||||
+ const struct ly_ctx *ly_ctx;
|
||||
|
||||
#define SR_OPER_SUBSCR(mod_name, xpath, cb) \
|
||||
rc = sr_oper_get_subscribe(np2srv.sr_sess, mod_name, xpath, cb, NULL, 0, &np2srv.sr_data_sub); \
|
||||
@@ -1203,12 +1205,19 @@ server_data_subscribe(void)
|
||||
mod_name = "ietf-tls-common";
|
||||
SR_OPER_SUBSCR(mod_name, "/ietf-tls-common:supported-algorithms", np2srv_tls_algs_oper_cb);
|
||||
|
||||
- /* password last modified oper data for both listen + call-home SSH users */
|
||||
- mod_name = "ietf-netconf-server";
|
||||
- SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint/ssh/"
|
||||
- "ssh-server-parameters/client-authentication/users/user/password/last-modified", np2srv_password_last_modified_oper_cb);
|
||||
- SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/call-home/netconf-client/endpoints/endpoint/ssh/"
|
||||
- "ssh-server-parameters/client-authentication/users/user/password/last-modified", np2srv_password_last_modified_oper_cb);
|
||||
+ ly_ctx = sr_acquire_context(np2srv.sr_conn);
|
||||
+ mod = ly_ctx_get_module_implemented(ly_ctx, "ietf-ssh-server");
|
||||
+ sr_release_context(np2srv.sr_conn);
|
||||
+ if (!lys_feature_value(mod, "local-users-supported")) {
|
||||
+ /* password last modified oper data for both listen + call-home SSH users */
|
||||
+ mod_name = "ietf-netconf-server";
|
||||
+ SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/listen/endpoints/endpoint/ssh/"
|
||||
+ "ssh-server-parameters/client-authentication/users/user/password/last-modified",
|
||||
+ np2srv_password_last_modified_oper_cb);
|
||||
+ SR_OPER_SUBSCR(mod_name, "/ietf-netconf-server:netconf-server/call-home/netconf-client/endpoints/endpoint/ssh/"
|
||||
+ "ssh-server-parameters/client-authentication/users/user/password/last-modified",
|
||||
+ np2srv_password_last_modified_oper_cb);
|
||||
+ }
|
||||
#endif /* NC_ENABLED_SSH_TLS */
|
||||
|
||||
/* subscriptions to running DS */
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
From d23806c941a16a8a12e006de240fd16afd963f47 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Tue, 16 Dec 2025 08:15:54 +0100
|
||||
Subject: [PATCH 4/4] Do not install YANG models, we do it in confd.
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 594363f..d449230 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -86,7 +86,7 @@ set(YANG_MODULE_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/yang/modul
|
||||
set(DATA_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/netopeer2" CACHE STRING "Directory with shared netopeer2 data")
|
||||
|
||||
# script options
|
||||
-option(SYSREPO_SETUP "Install required modules with their default configuration into sysrepo using a script" ON)
|
||||
+option(SYSREPO_SETUP "Install required modules with their default configuration into sysrepo using a script" OFF)
|
||||
set(MODULES_PERMS 600 CACHE STRING "File access permissions set for all the server modules")
|
||||
if(NOT MODULES_OWNER)
|
||||
execute_process(COMMAND id -un RESULT_VARIABLE RET
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
From e1ebfda6318d25804f3344caa89ce3f723b576e4 Mon Sep 17 00:00:00 2001
|
||||
From efe7706fd7397c2feb384afea00ee97e74287df0 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 28 Mar 2023 10:37:53 +0200
|
||||
Subject: [PATCH 1/7] sysrepo-plugind: add support for running in foreground
|
||||
Subject: [PATCH 1/8] sysrepo-plugind: add support for running in foreground
|
||||
with syslog
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
+16
-16
@@ -1,7 +1,7 @@
|
||||
From a190da4e967fb7ed65bc06794e8d775b4e1a7b84 Mon Sep 17 00:00:00 2001
|
||||
From 11b9938206cf2bafc456bb22e14c7f85a604760c Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Tue, 7 May 2024 15:41:53 +0200
|
||||
Subject: [PATCH 2/7] Allow SR_EV_DONE to return any error to sysrepocfg
|
||||
Subject: [PATCH 2/8] Allow SR_EV_DONE to return any error to sysrepocfg
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -28,10 +28,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
3 files changed, 34 insertions(+), 12 deletions(-)
|
||||
|
||||
diff --git a/src/shm_sub.c b/src/shm_sub.c
|
||||
index 61aab4c6..6ba1cd4d 100644
|
||||
index db0a2e0f..f64b85d6 100644
|
||||
--- a/src/shm_sub.c
|
||||
+++ b/src/shm_sub.c
|
||||
@@ -1832,7 +1832,7 @@ cleanup:
|
||||
@@ -1775,7 +1775,7 @@ cleanup:
|
||||
|
||||
sr_error_info_t *
|
||||
sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name, const void *orig_data,
|
||||
@@ -40,7 +40,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
{
|
||||
sr_error_info_t *err_info = NULL;
|
||||
struct sr_mod_info_mod_s *mod = NULL;
|
||||
@@ -1980,12 +1980,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *
|
||||
@@ -1923,12 +1923,19 @@ sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *
|
||||
sr_rwunlock(&nsub->sub_shm->lock, 0, SR_LOCK_WRITE, cid, __func__);
|
||||
nsub->lock = SR_LOCK_NONE;
|
||||
|
||||
@@ -63,7 +63,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
nsub->pending_event = 0;
|
||||
}
|
||||
} while (1);
|
||||
@@ -3557,7 +3564,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc
|
||||
@@ -3562,7 +3569,7 @@ sr_shmsub_change_listen_relock(sr_sub_shm_t *sub_shm, sr_lock_mode_t mode, struc
|
||||
sr_error_info_t *
|
||||
sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_subs, sr_conn_ctx_t *conn)
|
||||
{
|
||||
@@ -72,7 +72,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
uint32_t i, data_len = 0, valid_subscr_count, remaining_subs;
|
||||
char *data = NULL, *shm_data_ptr;
|
||||
int ret = SR_ERR_OK, filter_valid;
|
||||
@@ -3579,7 +3586,6 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub
|
||||
@@ -3584,7 +3591,6 @@ sr_shmsub_change_listen_process_module_events(struct modsub_change_s *change_sub
|
||||
}
|
||||
}
|
||||
if (i == change_subs->sub_count) {
|
||||
@@ -80,7 +80,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -3686,6 +3692,11 @@ process_event:
|
||||
@@ -3697,6 +3703,11 @@ process_event:
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
}
|
||||
|
||||
/* subscription processed this event */
|
||||
@@ -3718,6 +3729,11 @@ process_event:
|
||||
@@ -3729,6 +3740,11 @@ process_event:
|
||||
}
|
||||
break;
|
||||
case SR_SUB_EV_DONE:
|
||||
@@ -104,7 +104,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
case SR_SUB_EV_ABORT:
|
||||
/* nothing to do */
|
||||
break;
|
||||
@@ -3742,14 +3758,20 @@ process_event:
|
||||
@@ -3753,14 +3769,20 @@ process_event:
|
||||
|
||||
/* SUB WRITE URGE LOCK */
|
||||
if (sr_shmsub_change_listen_relock(sub_shm, SR_LOCK_WRITE_URGE, &sub_info, change_sub, change_subs->module_name,
|
||||
@@ -127,7 +127,7 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
} else {
|
||||
SR_LOG_DBG("EV LISTEN: \"%s\" \"%s\" ID %" PRIu32 " priority %" PRIu32 " success (remaining %" PRIu32 " subscribers).",
|
||||
change_subs->module_name, sr_ev2str(sub_info.event), sub_info.operation_id, sub_info.priority, remaining_subs);
|
||||
@@ -4164,7 +4186,7 @@ finish_iter:
|
||||
@@ -4176,7 +4198,7 @@ finish_iter:
|
||||
sr_errinfo_free(&cb_err_info);
|
||||
|
||||
/* publish "done" event */
|
||||
@@ -137,10 +137,10 @@ index 61aab4c6..6ba1cd4d 100644
|
||||
}
|
||||
|
||||
diff --git a/src/shm_sub.h b/src/shm_sub.h
|
||||
index 0cd22884..bd697ccc 100644
|
||||
index de21a30f..66fc7b69 100644
|
||||
--- a/src/shm_sub.h
|
||||
+++ b/src/shm_sub.h
|
||||
@@ -159,7 +159,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info,
|
||||
@@ -147,7 +147,7 @@ sr_error_info_t *sr_shmsub_change_notify_change(struct sr_mod_info_s *mod_info,
|
||||
* @return err_info, NULL on success.
|
||||
*/
|
||||
sr_error_info_t *sr_shmsub_change_notify_change_done(struct sr_mod_info_s *mod_info, const char *orig_name,
|
||||
@@ -150,10 +150,10 @@ index 0cd22884..bd697ccc 100644
|
||||
/**
|
||||
* @brief Notify about (generate) a change "abort" event.
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 068585d7..72dad00c 100644
|
||||
index fa7b5360..542a55ba 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -4313,7 +4313,7 @@ sr_error_info_t *
|
||||
@@ -4397,7 +4397,7 @@ sr_error_info_t *
|
||||
sr_changes_notify_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del,
|
||||
uint32_t timeout_ms, sr_lock_mode_t has_change_sub_lock, sr_error_info_t **err_info2)
|
||||
{
|
||||
@@ -162,7 +162,7 @@ index 068585d7..72dad00c 100644
|
||||
struct sr_denied denied = {0};
|
||||
sr_lock_mode_t change_sub_lock = has_change_sub_lock;
|
||||
uint32_t sid = 0, err_count;
|
||||
@@ -4470,7 +4470,7 @@ store:
|
||||
@@ -4559,7 +4559,7 @@ store:
|
||||
}
|
||||
|
||||
/* publish "done" event, all changes were applied */
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
From 81b0b781fa41a9e1c7ab2a94e6fc5aa4c52d365d Mon Sep 17 00:00:00 2001
|
||||
From 9e0267d4f20733b2a26df6d0ee0bc4019db8b13f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Wed, 8 May 2024 17:00:50 +0200
|
||||
Subject: [PATCH 3/7] Allow to copy from factory default
|
||||
Subject: [PATCH 3/8] Allow to copy from factory default
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -13,12 +13,12 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 72dad00c..1b54319f 100644
|
||||
index 542a55ba..80bfb42c 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -4895,7 +4895,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||
struct sr_mod_info_s mod_info;
|
||||
@@ -5023,7 +5023,7 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||
const struct lys_module *ly_mod = NULL;
|
||||
sr_datastore_t ds1, ds2;
|
||||
|
||||
- SR_CHECK_ARG_APIRET(!session || !SR_IS_CONVENTIONAL_DS(src_datastore) || !SR_IS_CONVENTIONAL_DS(session->ds),
|
||||
+ SR_CHECK_ARG_APIRET(!session || (!SR_IS_CONVENTIONAL_DS(src_datastore) && (src_datastore != SR_DS_FACTORY_DEFAULT)) || !SR_IS_CONVENTIONAL_DS(session->ds),
|
||||
+20
-20
@@ -1,7 +1,7 @@
|
||||
From 496305b14423d0e0344cd4e52a80699773cd1a34 Mon Sep 17 00:00:00 2001
|
||||
From e0c899ba266b959544d7cc08c917cebba7ac91c7 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Mon, 6 May 2024 14:49:32 +0200
|
||||
Subject: [PATCH 4/7] Add -z switch to sysrepoctl to install factory config
|
||||
Subject: [PATCH 4/8] Add -z switch to sysrepoctl to install factory config
|
||||
from a json file
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
@@ -19,10 +19,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
4 files changed, 83 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/executables/sysrepoctl.c b/src/executables/sysrepoctl.c
|
||||
index b33a60cd..b60d17af 100644
|
||||
index 510eec2a..497c2cbf 100644
|
||||
--- a/src/executables/sysrepoctl.c
|
||||
+++ b/src/executables/sysrepoctl.c
|
||||
@@ -664,6 +664,7 @@ main(int argc, char **argv)
|
||||
@@ -659,6 +659,7 @@ main(int argc, char **argv)
|
||||
{"uninstall", required_argument, NULL, 'u'},
|
||||
{"change", required_argument, NULL, 'c'},
|
||||
{"update", required_argument, NULL, 'U'},
|
||||
@@ -30,7 +30,7 @@ index b33a60cd..b60d17af 100644
|
||||
{"plugin-list", no_argument, NULL, 'L'},
|
||||
{"plugin-install", required_argument, NULL, 'P'},
|
||||
{"search-dirs", required_argument, NULL, 's'},
|
||||
@@ -688,7 +689,7 @@ main(int argc, char **argv)
|
||||
@@ -683,7 +684,7 @@ main(int argc, char **argv)
|
||||
|
||||
/* process options */
|
||||
opterr = 0;
|
||||
@@ -39,7 +39,7 @@ index b33a60cd..b60d17af 100644
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
/* help */
|
||||
@@ -880,6 +881,15 @@ main(int argc, char **argv)
|
||||
@@ -875,6 +876,15 @@ main(int argc, char **argv)
|
||||
goto cleanup;
|
||||
}
|
||||
break;
|
||||
@@ -55,7 +55,7 @@ index b33a60cd..b60d17af 100644
|
||||
case 'I':
|
||||
/* init-data */
|
||||
if (operation == 'i') {
|
||||
@@ -939,9 +949,15 @@ main(int argc, char **argv)
|
||||
@@ -934,9 +944,15 @@ main(int argc, char **argv)
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ index b33a60cd..b60d17af 100644
|
||||
/* list */
|
||||
if ((r = srctl_list(conn))) {
|
||||
diff --git a/src/lyd_mods.h b/src/lyd_mods.h
|
||||
index f3a9b215..b86f24b4 100644
|
||||
index c1c2ea7a..1cdf2436 100644
|
||||
--- a/src/lyd_mods.h
|
||||
+++ b/src/lyd_mods.h
|
||||
@@ -103,4 +103,11 @@ sr_error_info_t *sr_lydmods_change_chng_feature(const struct ly_ctx *ly_ctx, con
|
||||
@@ -89,10 +89,10 @@ index f3a9b215..b86f24b4 100644
|
||||
+
|
||||
#endif
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 1b54319f..ca2026b2 100644
|
||||
index 80bfb42c..22fcbc3c 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -1626,6 +1626,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count)
|
||||
@@ -1689,6 +1689,55 @@ sr_free_int_install_mods(sr_int_install_mod_t *new_mods, uint32_t new_mod_count)
|
||||
free(new_mods);
|
||||
}
|
||||
|
||||
@@ -103,15 +103,15 @@ index 1b54319f..ca2026b2 100644
|
||||
+ struct lyd_node *sr_mods = NULL;
|
||||
+ int initialized = 0;
|
||||
+ struct lyd_node *mod_data = NULL;
|
||||
+ struct sr_data_update_s data_info = {0};
|
||||
+ struct sr_lycc_ds_data_s data_info = {0};
|
||||
+
|
||||
+ /* create new temporary context */
|
||||
+ if ((err_info = sr_ly_ctx_init(conn, &new_ctx))) {
|
||||
+ if ((err_info = sr_ly_ctx_new(&new_ctx))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* use temporary context to load current modules */
|
||||
+ if ((err_info = sr_shmmod_ctx_load_modules(SR_CONN_MOD_SHM(conn), new_ctx, NULL))) {
|
||||
+ if ((err_info = sr_shmmod_ctx_load_modules(SR_CTX_MOD_SHM(sr_yang_ctx), new_ctx, NULL))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
@@ -130,18 +130,18 @@ index 1b54319f..ca2026b2 100644
|
||||
+ }
|
||||
+ data_info.new.fdflt = mod_data;
|
||||
+ mod_data = NULL;
|
||||
+ if ((err_info = sr_lycc_store_data_if_differ(conn, new_ctx, sr_mods, &data_info))) {
|
||||
+ if ((err_info = sr_lycc_store_ds_data_if_differ(conn, new_ctx, sr_mods, &data_info))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* update content ID and safely switch the context */
|
||||
+ /* update content ID */
|
||||
+ SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
||||
+ sr_conn_ctx_switch(conn, &new_ctx, NULL);
|
||||
+cleanup:
|
||||
+ ly_ctx_destroy(new_ctx);
|
||||
+ sr_lycc_update_data_clear(&data_info);
|
||||
+ sr_lycc_ds_data_set_clear(&data_info.old);
|
||||
+ sr_lycc_ds_data_set_clear(&data_info.new);
|
||||
+ lyd_free_siblings(sr_mods);
|
||||
+ lyd_free_siblings(mod_data);
|
||||
+ ly_ctx_destroy(new_ctx);
|
||||
+ return sr_api_ret(NULL, err_info);
|
||||
+}
|
||||
+
|
||||
@@ -149,7 +149,7 @@ index 1b54319f..ca2026b2 100644
|
||||
sr_install_module(sr_conn_ctx_t *conn, const char *schema_path, const char *search_dirs, const char **features)
|
||||
{
|
||||
diff --git a/src/sysrepo.h b/src/sysrepo.h
|
||||
index 466c8294..ee1b47a9 100644
|
||||
index 82620c80..6fdf2b29 100644
|
||||
--- a/src/sysrepo.h
|
||||
+++ b/src/sysrepo.h
|
||||
@@ -34,7 +34,6 @@ extern "C" {
|
||||
@@ -160,7 +160,7 @@ index 466c8294..ee1b47a9 100644
|
||||
/**
|
||||
* @defgroup log_api Logging API
|
||||
* @{
|
||||
@@ -725,6 +724,15 @@ int sr_get_module_info(sr_conn_ctx_t *conn, sr_data_t **sysrepo_data);
|
||||
@@ -787,6 +786,15 @@ int sr_get_module_info(sr_conn_ctx_t *conn, sr_data_t **sysrepo_data);
|
||||
*/
|
||||
int sr_is_module_internal(const struct lys_module *ly_mod);
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
From 6543a03c98fe32180dc95a47d113a3bd2dd369de Mon Sep 17 00:00:00 2001
|
||||
From c7602dc8eabb941e0a163208aaf4de92dd5ef526 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Wed, 21 Aug 2024 16:00:35 +0200
|
||||
Subject: [PATCH 5/7] Introduce new log level [SEC] for audit trails
|
||||
Subject: [PATCH 5/8] Introduce new log level [SEC] for audit trails
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -86,7 +86,7 @@ index b4f62e7c..62279675 100644
|
||||
|
||||
#define SR_CHECK_MEM_GOTO(cond, err_info, go) if (cond) { SR_ERRINFO_MEM(&(err_info)); goto go; }
|
||||
diff --git a/src/sysrepo_types.h b/src/sysrepo_types.h
|
||||
index 165e255f..60df80cf 100644
|
||||
index d71af2d4..32990fac 100644
|
||||
--- a/src/sysrepo_types.h
|
||||
+++ b/src/sysrepo_types.h
|
||||
@@ -65,7 +65,8 @@ typedef enum {
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
From 0ff2b7562154289de39abb10bcec2bc0d744a64d Mon Sep 17 00:00:00 2001
|
||||
From a86dfdd4a5cb74c1f8c90c8d5aea6f5505c1b88c Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Wed, 21 Aug 2024 16:04:43 +0200
|
||||
Subject: [PATCH 6/7] Add audit trail for high priority system changes
|
||||
Subject: [PATCH 6/8] Add audit trail for high priority system changes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -22,10 +22,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 12 insertions(+)
|
||||
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index ca2026b2..2ea546e9 100644
|
||||
index 22fcbc3c..a1d14eda 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -4518,6 +4518,9 @@ store:
|
||||
@@ -4607,6 +4607,9 @@ store:
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ index ca2026b2..2ea546e9 100644
|
||||
/* publish "done" event, all changes were applied */
|
||||
if ((err_info = sr_shmsub_change_notify_change_done(mod_info, orig_name, orig_data, timeout_ms, &cb_err_info))) {
|
||||
goto cleanup;
|
||||
@@ -4529,6 +4532,9 @@ store:
|
||||
@@ -4618,6 +4621,9 @@ store:
|
||||
}
|
||||
|
||||
cleanup:
|
||||
@@ -45,7 +45,7 @@ index ca2026b2..2ea546e9 100644
|
||||
if (change_sub_lock && !has_change_sub_lock) {
|
||||
assert(change_sub_lock == SR_LOCK_READ);
|
||||
|
||||
@@ -5031,6 +5037,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||
@@ -5164,6 +5170,9 @@ sr_copy_config(sr_session_ctx_t *session, const char *module_name, sr_datastore_
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ index ca2026b2..2ea546e9 100644
|
||||
cleanup:
|
||||
/* MODULES UNLOCK */
|
||||
sr_shmmod_modinfo_unlock(&mod_info);
|
||||
@@ -7562,6 +7571,9 @@ sr_rpc_send_tree(sr_session_ctx_t *session, struct lyd_node *input, uint32_t tim
|
||||
@@ -7772,6 +7781,9 @@ sr_rpc_send_tree_internal(sr_session_ctx_t *session, struct lyd_node *input, uin
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
From 83e58e43fc1c968a6e007ab0c39d99aeffc8fbce Mon Sep 17 00:00:00 2001
|
||||
From dbf08c67d8f17bdf98466b18fd72a230269e5d46 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Fri, 23 Aug 2024 12:22:06 +0200
|
||||
Subject: [PATCH 7/7] On error in sr_shmsub_listen_thread(), exit process
|
||||
Subject: [PATCH 7/8] On error in sr_shmsub_listen_thread(), exit process
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -18,10 +18,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/src/shm_sub.c b/src/shm_sub.c
|
||||
index 6ba1cd4d..5c006a82 100644
|
||||
index f64b85d6..c1a4b960 100644
|
||||
--- a/src/shm_sub.c
|
||||
+++ b/src/shm_sub.c
|
||||
@@ -5049,5 +5049,7 @@ error:
|
||||
@@ -5061,5 +5061,7 @@ error:
|
||||
/* free our own resources */
|
||||
ATOMIC_STORE_RELAXED(subscr->thread_running, 0);
|
||||
pthread_detach(pthread_self());
|
||||
@@ -0,0 +1,37 @@
|
||||
From 2549c966c090dd38a7a09907d27d13107d15aedd Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Tue, 16 Dec 2025 08:18:32 +0100
|
||||
Subject: [PATCH 8/8] Cross compile fixes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeModules/SetupPrintedContext.cmake | 9 +++++++++
|
||||
1 file changed, 9 insertions(+)
|
||||
|
||||
diff --git a/CMakeModules/SetupPrintedContext.cmake b/CMakeModules/SetupPrintedContext.cmake
|
||||
index f31ddff6..bfce7a5f 100644
|
||||
--- a/CMakeModules/SetupPrintedContext.cmake
|
||||
+++ b/CMakeModules/SetupPrintedContext.cmake
|
||||
@@ -59,6 +59,15 @@ function(SETUP_PRINTED_CONTEXT)
|
||||
return()
|
||||
endif()
|
||||
|
||||
+ # cannot run test programs when cross-compiling
|
||||
+ if(CMAKE_CROSSCOMPILING)
|
||||
+ message(WARNING "Cannot calculate printed context address when cross-compiling. "
|
||||
+ "Provide PRINTED_CONTEXT_ADDRESS manually to enable printed context.")
|
||||
+ message(STATUS "Printed context: disabled")
|
||||
+ set(PRINTED_CONTEXT_ADDRESS 0 CACHE STRING "${PRINTED_CONTEXT_ADDRESS_DOCSTRING}" FORCE)
|
||||
+ return()
|
||||
+ endif()
|
||||
+
|
||||
# compile and run the address calculator
|
||||
try_run(
|
||||
exec_result compile_result
|
||||
--
|
||||
2.43.0
|
||||
|
||||
Reference in New Issue
Block a user