From b960058e37c471084538bb5db8b3b5e9e992c46c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
Date: Wed, 2 Apr 2025 21:47:21 +0200
Subject: [PATCH 14/20] add excluded changes to YANG push on-change wrapper
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires

Change-Id: Idb7bb48a3c60ff70286441cd4d0a5c1c28acee15
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
 include/sysrepo-cpp/Enum.hpp    | 12 +++++++
 include/sysrepo-cpp/Session.hpp |  1 +
 src/Session.cpp                 | 12 ++++++-
 src/utils/enum.hpp              |  8 +++++
 tests/subscriptions-dynamic.cpp | 60 +++++++++++++++++++++++++++++++++
 5 files changed, 92 insertions(+), 1 deletion(-)

diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp
index cac1f2e..48467c6 100644
--- a/include/sysrepo-cpp/Enum.hpp
+++ b/include/sysrepo-cpp/Enum.hpp
@@ -209,6 +209,18 @@ constexpr GetOptions operator|(const GetOptions a, const GetOptions b)
     return implEnumBitOr(a, b);
 }
 
+/**
+ * @brief Wraps `srsn_yang_push_change_t`
+ */
+enum class YangPushChange : uint32_t {
+    Create = 0, /**< SRSN_YP_CHANGE_CREATE */
+    Delete = 1, /**< SRSN_YP_CHANGE_DELETE */
+    Insert = 2, /**< SRSN_YP_CHANGE_INSERT */
+    Move = 3, /**< SRSN_YP_CHANGE_MOVE */
+    Replace = 4, /**< SRSN_YP_CHANGE_REPLACE */
+    EnumCount = 5, /**< SRSN_COUNT_YP_CHANGE */
+};
+
 std::ostream& operator<<(std::ostream& os, const NotificationType& type);
 std::ostream& operator<<(std::ostream& os, const Event& event);
 std::ostream& operator<<(std::ostream& os, const ChangeOperation& changeOp);
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
index 0781890..b7cad72 100644
--- a/include/sysrepo-cpp/Session.hpp
+++ b/include/sysrepo-cpp/Session.hpp
@@ -145,6 +145,7 @@ public:
         const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
         const std::optional<std::chrono::milliseconds>& dampeningPeriod = std::nullopt,
         SyncOnStart syncOnStart = SyncOnStart::No,
+        const std::set<YangPushChange>& excludedChanges = {},
         const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
     [[nodiscard]] DynamicSubscription subscribeNotifications(
         const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
diff --git a/src/Session.cpp b/src/Session.cpp
index 1d65d70..40c3df3 100644
--- a/src/Session.cpp
+++ b/src/Session.cpp
@@ -607,6 +607,7 @@ DynamicSubscription Session::yangPushOnChange(
     const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
     const std::optional<std::chrono::milliseconds>& dampeningPeriod,
     SyncOnStart syncOnStart,
+    const std::set<YangPushChange>& excludedChanges,
     const std::optional<NotificationTimeStamp>& stopTime)
 {
     int fd;
@@ -614,12 +615,21 @@ DynamicSubscription Session::yangPushOnChange(
     auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
     auto xpathFilter = constructXPathFilter(filter);
 
+    /* The enum values are not used as the other enum flags in sysrepo-cpp.
+     * srsn_yang_push_on_change expects an integer array of size EnumCount with 0 or 1 values.
+     */
+    using YangPushChangeUnderlying = std::underlying_type_t<YangPushChange>;
+    std::array<int, static_cast<YangPushChangeUnderlying>(YangPushChange::EnumCount)> excludedChangesArray{};
+    for (const auto& change: excludedChanges) {
+        excludedChangesArray[static_cast<YangPushChangeUnderlying>(change)] = 1;
+    }
+
     auto res = srsn_yang_push_on_change(m_sess.get(),
                                         toDatastore(activeDatastore()),
                                         xpathFilter ? xpathFilter->c_str() : nullptr,
                                         dampeningPeriod ? dampeningPeriod->count() : 0,
                                         syncOnStart == SyncOnStart::Yes,
-                                        nullptr,
+                                        excludedChangesArray.data(),
                                         stopSpec ? &stopSpec.value() : nullptr,
                                         0,
                                         nullptr,
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
index a2dcf7a..2d3a298 100644
--- a/src/utils/enum.hpp
+++ b/src/utils/enum.hpp
@@ -9,6 +9,7 @@
 #include <type_traits>
 extern "C" {
 #include <sysrepo.h>
+#include <sysrepo/subscribed_notifications.h>
 }
 #include <sysrepo-cpp/Enum.hpp>
 
@@ -171,4 +172,11 @@ constexpr sr_get_options_t toGetOptions(const GetOptions opts)
 {
     return static_cast<sr_get_options_t>(opts);
 }
+
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create);
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_DELETE) == YangPushChange::Delete);
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_INSERT) == YangPushChange::Insert);
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_MOVE) == YangPushChange::Move);
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_REPLACE) == YangPushChange::Replace);
+static_assert(static_cast<YangPushChange>(SRSN_COUNT_YP_CHANGE) == YangPushChange::EnumCount);
 }
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
index 84a0880..5ed173b 100644
--- a/tests/subscriptions-dynamic.cpp
+++ b/tests/subscriptions-dynamic.cpp
@@ -497,6 +497,66 @@ TEST_CASE("Dynamic subscriptions")
             sub.terminate();
             REQUIRE_PIPE_HANGUP(sub);
         }
+
+        DOCTEST_SUBCASE("Excluded changes")
+        {
+            auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::No, {sysrepo::YangPushChange::Create});
+
+            client.setItem("/test_module:leafInt32", "123");
+            client.applyChanges(); // excluded (create)
+            client.setItem("/test_module:leafInt32", "124");
+            client.applyChanges();
+            client.setItem("/test_module:leafInt32", "125");
+            client.applyChanges();
+
+            REQUIRE_YANG_PUSH_UPDATE(sub, R"({
+  "ietf-yang-push:push-change-update": {
+    "datastore-changes": {
+      "yang-patch": {
+        "patch-id": "patch-1",
+        "edit": [
+          {
+            "edit-id": "edit-1",
+            "operation": "replace",
+            "target": "/test_module:leafInt32",
+            "value": {
+              "test_module:leafInt32": 124
+            }
+          }
+        ]
+      }
+    }
+  }
+}
+)");
+
+            READ_YANG_PUSH_UPDATE(sub);
+
+            REQUIRE_YANG_PUSH_UPDATE(sub, R"({
+  "ietf-yang-push:push-change-update": {
+    "datastore-changes": {
+      "yang-patch": {
+        "patch-id": "patch-2",
+        "edit": [
+          {
+            "edit-id": "edit-1",
+            "operation": "replace",
+            "target": "/test_module:leafInt32",
+            "value": {
+              "test_module:leafInt32": 125
+            }
+          }
+        ]
+      }
+    }
+  }
+}
+)");
+            READ_YANG_PUSH_UPDATE(sub);
+
+            sub.terminate();
+            REQUIRE_PIPE_HANGUP(sub);
+        }
     }
 
     DOCTEST_SUBCASE("YANG Push periodic")
-- 
2.43.0

