Add extra subscription params support

OriginResource allows filtering messages on a per-device basis.  This
was already listed as supported in our docs.

RegistryPrefixes is also added.

Tested: Unit tests pass.

Change-Id: Idfde8416f2f466ce11957177e052b540fc669888
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/Redfish.md b/Redfish.md
index 9c6d153..2ffaa5e 100644
--- a/Redfish.md
+++ b/Redfish.md
@@ -479,6 +479,7 @@
 - EventTypes
 - Context
 - OriginResources
+- RegistryPrefixes
 - Protocol
 
 ### /redfish/v1/JsonSchemas/
diff --git a/include/event_service_store.hpp b/include/event_service_store.hpp
index 8501fff..9761eb5 100644
--- a/include/event_service_store.hpp
+++ b/include/event_service_store.hpp
@@ -25,6 +25,7 @@
     std::vector<std::string> resourceTypes;
     boost::beast::http::fields httpHeaders;
     std::vector<std::string> metricReportDefinitions;
+    std::vector<std::string> originResources;
 
     static std::shared_ptr<UserSubscription> fromJson(
         const nlohmann::json::object_t& j, const bool loadFromOldConfig = false)
@@ -215,6 +216,25 @@
                     subvalue->metricReportDefinitions.emplace_back(*value);
                 }
             }
+            else if (element.first == "OriginResources")
+            {
+                const nlohmann::json::array_t* obj =
+                    element.second.get_ptr<const nlohmann::json::array_t*>();
+                if (obj == nullptr)
+                {
+                    continue;
+                }
+                for (const auto& val : *obj)
+                {
+                    const std::string* value =
+                        val.get_ptr<const std::string*>();
+                    if (value == nullptr)
+                    {
+                        continue;
+                    }
+                    subvalue->originResources.emplace_back(*value);
+                }
+            }
             else
             {
                 BMCWEB_LOG_ERROR(
diff --git a/include/persistent_data.hpp b/include/persistent_data.hpp
index 0f14143..bcecf9f 100644
--- a/include/persistent_data.hpp
+++ b/include/persistent_data.hpp
@@ -297,6 +297,7 @@
             subscription["MessageIds"] = subValue.registryMsgIds;
             subscription["Protocol"] = subValue.protocol;
             subscription["RegistryPrefixes"] = subValue.registryPrefixes;
+            subscription["OriginResources"] = subValue.originResources;
             subscription["ResourceTypes"] = subValue.resourceTypes;
             subscription["SubscriptionType"] = subValue.subscriptionType;
             subscription["MetricReportDefinitions"] =
diff --git a/meson.build b/meson.build
index b10bc2a..299ce55 100644
--- a/meson.build
+++ b/meson.build
@@ -414,6 +414,7 @@
     'test/include/ossl_random.cpp',
     'test/include/ssl_key_handler_test.cpp',
     'test/include/str_utility_test.cpp',
+    'test/redfish-core/include/event_service_manager_test.cpp',
     'test/redfish-core/include/filter_expr_executor_test.cpp',
     'test/redfish-core/include/filter_expr_parser_test.cpp',
     'test/redfish-core/include/privileges_test.cpp',
diff --git a/redfish-core/include/event_service_manager.hpp b/redfish-core/include/event_service_manager.hpp
index 6a936cb..8fd0157 100644
--- a/redfish-core/include/event_service_manager.hpp
+++ b/redfish-core/include/event_service_manager.hpp
@@ -43,10 +43,12 @@
 #include <algorithm>
 #include <cstdlib>
 #include <ctime>
+#include <format>
 #include <fstream>
 #include <memory>
 #include <ranges>
 #include <span>
+#include <string>
 
 namespace redfish
 {
@@ -336,6 +338,60 @@
                              resType);
         }
 
+        // If registryPrefixes list is empty, don't filter events
+        // send everything.
+        if (!registryPrefixes.empty())
+        {
+            auto eventJson = eventMessage.find("MessageId");
+            if (eventJson == eventMessage.end())
+            {
+                return false;
+            }
+
+            const std::string* messageId =
+                eventJson->second.get_ptr<const std::string*>();
+            if (messageId == nullptr)
+            {
+                BMCWEB_LOG_ERROR("MessageId wasn't a string???");
+                return false;
+            }
+
+            std::string registry;
+            std::string messageKey;
+            event_log::getRegistryAndMessageKey(*messageId, registry,
+                                                messageKey);
+
+            auto obj = std::ranges::find(registryPrefixes, registry);
+            if (obj == registryPrefixes.end())
+            {
+                return false;
+            }
+        }
+
+        if (!originResources.empty())
+        {
+            auto eventJson = eventMessage.find("OriginOfCondition");
+            if (eventJson == eventMessage.end())
+            {
+                return false;
+            }
+
+            const std::string* originOfCondition =
+                eventJson->second.get_ptr<const std::string*>();
+            if (originOfCondition == nullptr)
+            {
+                BMCWEB_LOG_ERROR("OriginOfCondition wasn't a string???");
+                return false;
+            }
+
+            auto obj = std::ranges::find(originResources, *originOfCondition);
+
+            if (obj == originResources.end())
+            {
+                return false;
+            }
+        }
+
         // If registryMsgIds list is empty, assume all
         if (!registryMsgIds.empty())
         {
@@ -362,7 +418,8 @@
             BMCWEB_LOG_DEBUG("extracted registry {}", registry);
             BMCWEB_LOG_DEBUG("extracted message key {}", messageKey);
 
-            auto obj = std::ranges::find(registryMsgIds, registry);
+            auto obj = std::ranges::find(
+                registryMsgIds, std::format("{}.{}", registry, messageKey));
             if (obj == registryMsgIds.end())
             {
                 BMCWEB_LOG_DEBUG("did not find registry {} in registryMsgIds",
@@ -648,6 +705,7 @@
             subValue->resourceTypes = newSub->resourceTypes;
             subValue->httpHeaders = newSub->httpHeaders;
             subValue->metricReportDefinitions = newSub->metricReportDefinitions;
+            subValue->originResources = newSub->originResources;
 
             if (subValue->id.empty())
             {
@@ -909,6 +967,8 @@
         newSub->resourceTypes = subValue->resourceTypes;
         newSub->httpHeaders = subValue->httpHeaders;
         newSub->metricReportDefinitions = subValue->metricReportDefinitions;
+        newSub->originResources = subValue->originResources;
+
         persistent_data::EventServiceStore::getInstance()
             .subscriptionsConfigMap.emplace(newSub->id, newSub);
 
diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp
index aa88b09..76f3c5a 100644
--- a/redfish-core/lib/event_service.hpp
+++ b/redfish-core/lib/event_service.hpp
@@ -34,6 +34,7 @@
 #include <ranges>
 #include <span>
 #include <string>
+#include <vector>
 
 namespace redfish
 {
@@ -296,6 +297,7 @@
             std::optional<std::string> retryPolicy;
             std::optional<std::vector<std::string>> msgIds;
             std::optional<std::vector<std::string>> regPrefixes;
+            std::optional<std::vector<std::string>> originResources;
             std::optional<std::vector<std::string>> resTypes;
             std::optional<std::vector<nlohmann::json::object_t>> headers;
             std::optional<std::vector<nlohmann::json::object_t>> mrdJsonArray;
@@ -305,7 +307,8 @@
                     context, "Protocol", protocol, "SubscriptionType",
                     subscriptionType, "EventFormatType", eventFormatType2,
                     "HttpHeaders", headers, "RegistryPrefixes", regPrefixes,
-                    "MessageIds", msgIds, "DeliveryRetryPolicy", retryPolicy,
+                    "OriginResources", originResources, "MessageIds", msgIds,
+                    "DeliveryRetryPolicy", retryPolicy,
                     "MetricReportDefinitions", mrdJsonArray, "ResourceTypes",
                     resTypes, "VerifyCertificate", verifyCertificate))
             {
@@ -533,6 +536,11 @@
                 subValue->registryPrefixes = *regPrefixes;
             }
 
+            if (originResources)
+            {
+                subValue->originResources = *originResources;
+            }
+
             if (resTypes)
             {
                 for (const std::string& it : *resTypes)
diff --git a/test/redfish-core/include/event_service_manager_test.cpp b/test/redfish-core/include/event_service_manager_test.cpp
new file mode 100644
index 0000000..bb86e77
--- /dev/null
+++ b/test/redfish-core/include/event_service_manager_test.cpp
@@ -0,0 +1,102 @@
+#include "event_service_manager.hpp"
+#include "filter_expr_printer.hpp"
+
+#include <boost/asio/io_context.hpp>
+#include <boost/url/url.hpp>
+#include <nlohmann/json.hpp>
+
+#include <optional>
+#include <string_view>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+
+TEST(EventServiceManager, eventMatchesFilter)
+{
+    boost::asio::io_context io;
+    boost::urls::url url;
+
+    {
+        Subscription sub(url, io);
+        nlohmann::json::object_t event;
+
+        // Default constructed should always pass
+        EXPECT_TRUE(sub.eventMatchesFilter(event, "Event"));
+    }
+    {
+        nlohmann::json::object_t event;
+        // Resource types filter
+        Subscription sub(url, io);
+        sub.resourceTypes.emplace_back("Task");
+        EXPECT_FALSE(sub.eventMatchesFilter(event, "Event"));
+        EXPECT_TRUE(sub.eventMatchesFilter(event, "Task"));
+    }
+    {
+        nlohmann::json::object_t event;
+        // Resource types filter
+        Subscription sub(url, io);
+        sub.registryMsgIds.emplace_back("OpenBMC.PostComplete");
+
+        // Correct message registry
+        event["MessageId"] = "OpenBMC.0.1.PostComplete";
+        EXPECT_TRUE(sub.eventMatchesFilter(event, "Event"));
+
+        // Different message registry
+        event["MessageId"] = "Task.0.1.PostComplete";
+        EXPECT_FALSE(sub.eventMatchesFilter(event, "Event"));
+
+        // Different MessageId
+        event["MessageId"] = "OpenBMC.0.1.NoMatch";
+        EXPECT_FALSE(sub.eventMatchesFilter(event, "Event"));
+    }
+    {
+        nlohmann::json::object_t event;
+        // Resource types filter
+        Subscription sub(url, io);
+        event["MessageId"] = "OpenBMC.0.1.PostComplete";
+
+        // Correct message registry
+        sub.filter = parseFilter("MessageId eq 'OpenBMC.0.1.PostComplete'");
+        EXPECT_TRUE(sub.eventMatchesFilter(event, "Event"));
+
+        // Different message registry
+        sub.filter = parseFilter("MessageId ne 'OpenBMC.0.1.PostComplete'");
+        EXPECT_FALSE(sub.eventMatchesFilter(event, "Event"));
+    }
+    {
+        nlohmann::json::object_t event;
+        // Resource types filter
+        Subscription sub(url, io);
+        event["MessageId"] = "OpenBMC.0.1.PostComplete";
+
+        // Correct message registry
+        sub.registryPrefixes.emplace_back("OpenBMC");
+        EXPECT_TRUE(sub.eventMatchesFilter(event, "Event"));
+
+        // Different message registry
+        event["MessageId"] = "Task.0.1.PostComplete";
+        EXPECT_FALSE(sub.eventMatchesFilter(event, "Event"));
+    }
+    {
+        nlohmann::json::object_t event;
+        // Resource types filter
+        {
+            Subscription sub(url, io);
+            event["OriginOfCondition"] = "/redfish/v1/Managers/bmc";
+
+            // Correct origin
+            sub.originResources.emplace_back("/redfish/v1/Managers/bmc");
+            EXPECT_TRUE(sub.eventMatchesFilter(event, "Event"));
+        }
+        {
+            Subscription sub(url, io);
+            // Incorrect origin
+            sub.originResources.clear();
+            sub.originResources.emplace_back("/redfish/v1/Managers/bmc_not");
+            EXPECT_FALSE(sub.eventMatchesFilter(event, "Event"));
+        }
+    }
+}
+} // namespace redfish